/// <summary> /// Parses SearchGroup or SearchItem from reader. If reader starts with (, then parses searchGroup, otherwise SearchItem. /// </summary> /// <param name="reader"></param> /// <returns></returns> internal static object ParseSearchKey(StringReader reader) { //Remove spaces from string start reader.ReadToFirstChar(); // SearchGroup if (reader.StartsWith("(")) { SearchGroup searchGroup = new SearchGroup(); searchGroup.Parse(reader); return(searchGroup); } // SearchItem else { return(SearchKey.Parse(reader)); } }
private void Search(string cmdTag,string argsText,bool uidSearch) { /* RFC 3501 6.4.4 SEARCH Command Arguments: OPTIONAL [CHARSET] specification searching criteria (one or more) Responses: REQUIRED untagged response: SEARCH Result: OK - search completed NO - search error: can't search that [CHARSET] or criteria BAD - command unknown or arguments invalid The SEARCH command searches the mailbox for messages that match the given searching criteria. Searching criteria consist of one or more search keys. The untagged SEARCH response from the server contains a listing of message sequence numbers corresponding to those messages that match the searching criteria. When multiple keys are specified, the result is the intersection (AND function) of all the messages that match those keys. For example, the criteria DELETED FROM "SMITH" SINCE 1-Feb-1994 refers to all deleted messages from Smith that were placed in the mailbox since February 1, 1994. A search key can also be a parenthesized list of one or more search keys (e.g., for use with the OR and NOT keys). Server implementations MAY exclude [MIME-IMB] body parts with terminal content media types other than TEXT and MESSAGE from consideration in SEARCH matching. The OPTIONAL [CHARSET] specification consists of the word "CHARSET" followed by a registered [CHARSET]. It indicates the [CHARSET] of the strings that appear in the search criteria. [MIME-IMB] content transfer encodings, and [MIME-HDRS] strings in [RFC-2822]/[MIME-IMB] headers, MUST be decoded before comparing text in a [CHARSET] other than US-ASCII. US-ASCII MUST be supported; other [CHARSET]s MAY be supported. If the server does not support the specified [CHARSET], it MUST return a tagged NO response (not a BAD). This response SHOULD contain the BADCHARSET response code, which MAY list the [CHARSET]s supported by the server. In all search keys that use strings, a message matches the key if the string is a substring of the field. The matching is case-insensitive. The defined search keys are as follows. Refer to the Formal Syntax section for the precise syntactic definitions of the arguments. <sequence set> Messages with message sequence numbers corresponding to the specified message sequence number set. ALL All messages in the mailbox; the default initial key for ANDing. ANSWERED Messages with the \Answered flag set. BCC <string> Messages that contain the specified string in the envelope structure's BCC field. BEFORE <date> Messages whose internal date (disregarding time and timezone) is earlier than the specified date. BODY <string> Messages that contain the specified string in the body of the message. CC <string> Messages that contain the specified string in the envelope structure's CC field. DELETED Messages with the \Deleted flag set. DRAFT Messages with the \Draft flag set. FLAGGED Messages with the \Flagged flag set. FROM <string> Messages that contain the specified string in the envelope structure's FROM field. HEADER <field-name> <string> Messages that have a header with the specified field-name (as defined in [RFC-2822]) and that contains the specified string in the text of the header (what comes after the colon). If the string to search is zero-length, this matches all messages that have a header line with the specified field-name regardless of the contents. KEYWORD <flag> Messages with the specified keyword flag set. LARGER <n> Messages with an [RFC-2822] size larger than the specified number of octets. NEW Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to "(RECENT UNSEEN)". NOT <search-key> Messages that do not match the specified search key. OLD Messages that do not have the \Recent flag set. This is functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW"). ON <date> Messages whose internal date (disregarding time and timezone) is within the specified date. OR <search-key1> <search-key2> Messages that match either search key. RECENT Messages that have the \Recent flag set. SEEN Messages that have the \Seen flag set. SENTBEFORE <date> Messages whose [RFC-2822] Date: header (disregarding time and timezone) is earlier than the specified date. SENTON <date> Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within the specified date. SENTSINCE <date> Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within or later than the specified date. SINCE <date> Messages whose internal date (disregarding time and timezone) is within or later than the specified date. SMALLER <n> Messages with an [RFC-2822] size smaller than the specified number of octets. SUBJECT <string> Messages that contain the specified string in the envelope structure's SUBJECT field. TEXT <string> Messages that contain the specified string in the header or body of the message. TO <string> Messages that contain the specified string in the envelope structure's TO field. UID <sequence set> Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted. UNANSWERED Messages that do not have the \Answered flag set. UNDELETED Messages that do not have the \Deleted flag set. UNDRAFT Messages that do not have the \Draft flag set. UNFLAGGED Messages that do not have the \Flagged flag set. UNKEYWORD <flag> Messages that do not have the specified keyword flag set. UNSEEN Messages that do not have the \Seen flag set. Example: C: A282 SEARCH FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith" S: * SEARCH 2 84 882 S: A282 OK SEARCH completed C: A283 SEARCH TEXT "string not in mailbox" S: * SEARCH S: A283 OK SEARCH completed C: A284 SEARCH CHARSET UTF-8 TEXT {6} S: + Continue ### THIS IS UNDOCUMENTED !!! C: XXXXXX[arg conitnue]<CRLF> S: * SEARCH 43 S: A284 OK SEARCH completed Note: Since this document is restricted to 7-bit ASCII text, it is not possible to show actual UTF-8 data. The "XXXXXX" is a placeholder for what would be 6 octets of 8-bit data in an actual transaction. */ if(!this.Authenticated){ this.Socket.WriteLine(cmdTag + " NO Authenticate first !"); return; } if(m_SelectedMailbox.Length == 0){ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !"); return; } // Store start time long startTime = DateTime.Now.Ticks; //--- Get Optional charset, if specified -----------------------------------------------------------------// string charset = "ASCII"; // CHARSET charset if(argsText.ToUpper().StartsWith("CHARSET")){ // Remove CHARSET from argsText argsText = argsText.Substring(7).Trim(); string charsetValueString = IMAP_Utils.ParseQuotedParam(ref argsText); try{ System.Text.Encoding.GetEncoding(charsetValueString); charset = charsetValueString; } catch{ this.Socket.WriteLine(cmdTag + " NO [BADCHARSET UTF-8] " + charsetValueString + " is not supported"); return; } } //---------------------------------------------------------------------------------------------------------// /* If multiline command, read all lines C: A284 SEARCH CHARSET UTF-8 TEXT {6} S: + Continue ### THIS IS UNDOCUMENTED !!! C: XXXXXX[arg conitnue]<CRLF> */ argsText = argsText.Trim(); while(argsText.EndsWith("}") && argsText.IndexOf("{") > -1){ long dataLength = 0; try{ // Get data length from {xxx} dataLength = Convert.ToInt64(argsText.Substring(argsText.LastIndexOf("{") + 1 ,argsText.Length - argsText.LastIndexOf("{") - 2)); } // There is no valid numeric value between {}, just skip and allow SearchGroup parser to handle this value catch{ break; } MemoryStream dataStream = new MemoryStream(); this.Socket.WriteLine("+ Continue"); this.Socket.ReadSpecifiedLength((int)dataLength,dataStream); string argsContinueLine = this.Socket.ReadLine().TrimEnd(); // Append readed data + [args conitnue] line argsText += System.Text.Encoding.GetEncoding(charset).GetString(dataStream.ToArray()) + argsContinueLine; // There is no more argumets, stop getting. // We must check this because if length = 0 and no args returned, last line ends with {0}, // leaves this into endless loop. if(argsContinueLine == ""){ break; } } //--- Parse search criteria ------------------------// SearchGroup searchCriteria = new SearchGroup(); try{ searchCriteria.Parse(new StringReader(argsText)); } catch(Exception x){ this.Socket.WriteLine(cmdTag + " NO " + x.Message); return; } //--------------------------------------------------// /* string searchResponse = "* SEARCH"; // No header and body text needed, can do search on internal messages info data if(!searchCriteria.IsHeaderNeeded() && !searchCriteria.IsBodyTextNeeded()){ // Loop internal messages info, see what messages match for(int i=0;i<m_Messages.Count;i++){ IMAP_Message messageInfo = m_Messages[i]; // See if message matches if(searchCriteria.Match(i,messageInfo.MessageUID,(int)messageInfo.Size,messageInfo.Date,messageInfo.Flags,null,"")){ // For UID search we must return message UID's if(uidSearch){ searchResponse += " " + messageInfo.MessageUID.ToString(); } // For normal search we must return message index numbers. else{ searchResponse += " " + messageInfo.MessageNo.ToString(); } } } } // Can't search on iternal messages info, need to do header or body text search, call Search event else{ // Call 'Search' event, get search criteria matching messages IMAP_eArgs_Search eArgs = m_pServer.OnSearch(this,Core.Decode_IMAP_UTF7_String(this.SelectedMailbox),new IMAP_SearchMatcher(searchCriteria)); // Constuct matching messages search response for(int i=0;i<eArgs.MatchingMessages.Count;i++){ IMAP_Message messageInfo = eArgs.MatchingMessages[i]; // For UID search we must return message UID's if(uidSearch){ searchResponse += " " + messageInfo.MessageUID.ToString(); } // For normal search we must return message index numbers. else{ // Map search returnded message to internal message number int no = m_Messages.IndexFromUID(messageInfo.MessageUID); if(no > -1){ searchResponse += " " + no.ToString(); } } } } searchResponse += "\r\n"; searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"; */ ProcessMailboxChanges(); // Just loop messages headers or full messages (depends on search type) // string searchResponse = "* SEARCH"; this.Socket.Write("* SEARCH"); string searchResponse = ""; IMAP_MessageItems_enum messageItems = IMAP_MessageItems_enum.None; if(searchCriteria.IsBodyTextNeeded()){ messageItems |= IMAP_MessageItems_enum.Message; } else if(searchCriteria.IsHeaderNeeded()){ messageItems |= IMAP_MessageItems_enum.Header; } for(int i=0;i<m_pSelectedFolder.Messages.Count;i++){ IMAP_Message msg = m_pSelectedFolder.Messages[i]; //-- Get message only if matching needs it ------------------------// LumiSoft.Net.Mime.Mime parser = null; if((messageItems & IMAP_MessageItems_enum.Message) != 0 || (messageItems & IMAP_MessageItems_enum.Header) != 0){ // Raise event GetMessageItems, get requested message items. IMAP_eArgs_MessageItems eArgs = m_pServer.OnGetMessageItems(this,msg,messageItems); // Message data is null if no such message available, just skip that message if(!eArgs.MessageExists){ continue; } try{ // Ensure that all requested items were provided. eArgs.Validate(); } catch(Exception x){ m_pServer.OnSysError(x.Message,x); this.Socket.WriteLine(cmdTag + " NO Internal IMAP server component error: " + x.Message); return; } try{ if(eArgs.MessageStream != null){ parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.MessageStream); } else{ parser = LumiSoft.Net.Mime.Mime.Parse(eArgs.Header); } } // Message parsing failed, bad message. Just make new warning message. catch(Exception x){ parser = LumiSoft.Net.Mime.Mime.CreateSimple(new AddressList(),new AddressList(),"[BAD MESSAGE] Bad message, message parsing failed !","NOTE: Bad message, message parsing failed !\r\n\r\n" + x.Message,""); } } //-----------------------------------------------------------------// string bodyText = ""; if(searchCriteria.IsBodyTextNeeded()){ bodyText = parser.BodyText; } // See if message matches to search criteria if(searchCriteria.Match(i,msg.UID,(int)msg.Size,msg.InternalDate,msg.Flags,parser,bodyText)){ if(uidSearch){ this.Socket.Write(" " + msg.UID.ToString()); // searchResponse += " " + msg.MessageUID.ToString(); } else{ this.Socket.Write(" " + (i + 1).ToString()); // searchResponse += " " + i.ToString(); } } } searchResponse += "\r\n"; searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"; // Send search server response this.Socket.Write(searchResponse); }
/// <summary> /// Parses SearchGroup or SearchItem from reader. If reader starts with (, then parses searchGroup, otherwise SearchItem. /// </summary> /// <param name="reader"></param> /// <returns></returns> internal static object ParseSearchKey(StringReader reader) { //Remove spaces from string start reader.ReadToFirstChar(); // SearchGroup if(reader.StartsWith("(")){ SearchGroup searchGroup = new SearchGroup(); searchGroup.Parse(reader); return searchGroup; } // SearchItem else{ return SearchKey.Parse(reader); } }