/// <summary> /// Returns parsed IMAP SEARCH <b>KEYWORD (string)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>KEYWORD (string)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_Keyword Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"KEYWORD",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'KEYWORD' key."); } string value = r.ReadWord(); if(value == null){ throw new ParseException("Parse error: Invalid 'KEYWORD' value."); } return new IMAP_Search_Key_Keyword(value); }
/// <summary> /// Returns parsed IMAP SEARCH <b>LARGER (string)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>LARGER (string)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_Larger Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"LARGER",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'LARGER' key."); } string value = r.ReadWord(); if(value == null){ throw new ParseException("Parse error: Invalid 'LARGER' value."); } int size = 0; if(!int.TryParse(value,out size)){ throw new ParseException("Parse error: Invalid 'LARGER' value."); } return new IMAP_Search_Key_Larger(size); }
/// <summary> /// Returns parsed IMAP SEARCH <b>DRAFT</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>DRAFT</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_Draft Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"DRAFT",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'DRAFT' key."); } return new IMAP_Search_Key_Draft(); }
/// <summary> /// Returns parsed IMAP SEARCH <b>SENTON (string)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>SENTON (string)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_SentOn Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"SENTON",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'SENTON' key."); } string value = r.ReadWord(); if(value == null){ throw new ParseException("Parse error: Invalid 'SENTON' value."); } DateTime date; try{ date = IMAP_Utils.ParseDate(value); } catch{ throw new ParseException("Parse error: Invalid 'SENTON' value."); } return new IMAP_Search_Key_SentOn(date); }
/// <summary> /// Returns parsed IMAP SEARCH <b>FROM (string)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>FROM (string)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_From Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"FROM",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'FROM' key."); } string value = IMAP_Utils.ReadString(r); if(value == null){ throw new ParseException("Parse error: Invalid 'FROM' value."); } return new IMAP_Search_Key_From(value); }
/// <summary> /// Returns parsed IMAP SEARCH <b>HEADER (field-name) (string)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>HEADER (field-name) (string)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_Header Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"HEADER",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'HEADER' key."); } string fieldName = IMAP_Utils.ReadString(r); if(fieldName == null){ throw new ParseException("Parse error: Invalid 'HEADER' field-name value."); } string value = IMAP_Utils.ReadString(r); if(value == null){ throw new ParseException("Parse error: Invalid 'HEADER' string value."); } return new IMAP_Search_Key_Header(fieldName,value); }
/// <summary> /// Returns parsed IMAP SEARCH <b>UID (sequence set)</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>UID (sequence set)</b> key.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>r</b> is null reference.</exception> /// <exception cref="ParseException">Is raised when parsing fails.</exception> internal static IMAP_Search_Key_Uid Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } string word = r.ReadWord(); if(!string.Equals(word,"UID",StringComparison.InvariantCultureIgnoreCase)){ throw new ParseException("Parse error: Not a SEARCH 'UID' key."); } r.ReadToFirstChar(); string value = r.QuotedReadToDelimiter(' '); if(value == null){ throw new ParseException("Parse error: Invalid 'UID' value."); } try{ return new IMAP_Search_Key_Uid(IMAP_t_SeqSet.Parse(value)); } catch{ throw new ParseException("Parse error: Invalid 'UID' value."); } }
private void SEARCH(bool uid,string cmdTag,string cmdText) { /* 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} C: XXXXXX S: * SEARCH 43 S: A284 OK SEARCH completed */ if(!this.IsAuthenticated){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required.")); return; } if(m_pSelectedFolder == null){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state.")); return; } // Store start time long startTime = DateTime.Now.Ticks; #region Parse arguments _CmdReader cmdReader = new _CmdReader(this,cmdText,Encoding.UTF8); cmdReader.Start(); StringReader r = new StringReader(cmdReader.CmdLine); // See if we have optional CHARSET argument. if(r.StartsWith("CHARSET",false)){ r.ReadWord(); string charset = r.ReadWord(); if(!(string.Equals(charset,"US-ASCII",StringComparison.InvariantCultureIgnoreCase) || string.Equals(charset,"UTF-8",StringComparison.InvariantCultureIgnoreCase))){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO",new IMAP_t_orc_BadCharset(new string[]{"US-ASCII","UTF-8"}),"Not supported charset.")); return; } } #endregion try{ IMAP_Search_Key_Group criteria = IMAP_Search_Key_Group.Parse(r); UpdateSelectedFolderAndSendChanges(); List<int> matchedValues = new List<int>(); IMAP_e_Search searchArgs = new IMAP_e_Search(criteria,new IMAP_r_ServerStatus(cmdTag,"OK","SEARCH completed in %exectime seconds.")); searchArgs.Matched += new EventHandler<EventArgs<long>>(delegate(object s,EventArgs<long> e){ if(uid){ matchedValues.Add((int)e.Value); } else{ // Search sequence-number for that message. int seqNo = m_pSelectedFolder.GetSeqNo(e.Value); if(seqNo != -1){ matchedValues.Add((int)e.Value); } } }); OnSearch(searchArgs); m_pResponseSender.SendResponseAsync(new IMAP_r_u_Search(matchedValues.ToArray())); m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(searchArgs.Response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2")))); } catch{ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments.")); } }
// #region method FETCH private void FETCH(bool uid,string cmdTag,string cmdText) { /* RFC 3501. 6.4.5. FETCH Command. Arguments: sequence set message data item names or macro Responses: untagged responses: FETCH Result: OK - fetch completed NO - fetch error: can't fetch that data BAD - command unknown or arguments invalid The FETCH command retrieves data associated with a message in the mailbox. The data items to be fetched can be either a single atom or a parenthesized list. Most data items, identified in the formal syntax under the msg-att-static rule, are static and MUST NOT change for any particular message. Other data items, identified in the formal syntax under the msg-att-dynamic rule, MAY change, either as a result of a STORE command or due to external events. For example, if a client receives an ENVELOPE for a message when it already knows the envelope, it can safely ignore the newly transmitted envelope. There are three macros which specify commonly-used sets of data items, and can be used instead of data items. A macro must be used by itself, and not in conjunction with other macros or data items. ALL Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE) FAST Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE) FULL Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY) The currently defined data items that can be fetched are: BODY Non-extensible form of BODYSTRUCTURE. BODY[<section>]<<partial>> The text of a particular body section. The section specification is a set of zero or more part specifiers delimited by periods. A part specifier is either a part number or one of the following: HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, and TEXT. An empty section specification refers to the entire message, including the header. Every message has at least one part number. Non-[MIME-IMB] messages, and non-multipart [MIME-IMB] messages with no encapsulated message, only have a part 1. Multipart messages are assigned consecutive part numbers, as they occur in the message. If a particular part is of type message or multipart, its parts MUST be indicated by a period followed by the part number within that nested multipart part. A part of type MESSAGE/RFC822 also has nested part numbers, referring to parts of the MESSAGE part's body. The HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, and TEXT part specifiers can be the sole part specifier or can be prefixed by one or more numeric part specifiers, provided that the numeric part specifier refers to a part of type MESSAGE/RFC822. The MIME part specifier MUST be prefixed by one or more numeric part specifiers. The HEADER, HEADER.FIELDS, and HEADER.FIELDS.NOT part specifiers refer to the [RFC-2822] header of the message or of an encapsulated [MIME-IMT] MESSAGE/RFC822 message. HEADER.FIELDS and HEADER.FIELDS.NOT are followed by a list of field-name (as defined in [RFC-2822]) names, and return a subset of the header. The subset returned by HEADER.FIELDS contains only those header fields with a field-name that matches one of the names in the list; similarly, the subset returned by HEADER.FIELDS.NOT contains only the header fields with a non-matching field-name. The field-matching is case-insensitive but otherwise exact. Subsetting does not exclude the [RFC-2822] delimiting blank line between the header and the body; the blank line is included in all header fetches, except in the case of a message which has no body and no blank line. The MIME part specifier refers to the [MIME-IMB] header for this part. The TEXT part specifier refers to the text body of the message, omitting the [RFC-2822] header. Here is an example of a complex message with some of its part specifiers: HEADER ([RFC-2822] header of the message) TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED 1 TEXT/PLAIN 2 APPLICATION/OCTET-STREAM 3 MESSAGE/RFC822 3.HEADER ([RFC-2822] header of the message) 3.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED 3.1 TEXT/PLAIN 3.2 APPLICATION/OCTET-STREAM 4 MULTIPART/MIXED 4.1 IMAGE/GIF 4.1.MIME ([MIME-IMB] header for the IMAGE/GIF) 4.2 MESSAGE/RFC822 4.2.HEADER ([RFC-2822] header of the message) 4.2.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED 4.2.1 TEXT/PLAIN 4.2.2 MULTIPART/ALTERNATIVE 4.2.2.1 TEXT/PLAIN 4.2.2.2 TEXT/RICHTEXT It is possible to fetch a substring of the designated text. This is done by appending an open angle bracket ("<"), the octet position of the first desired octet, a period, the maximum number of octets desired, and a close angle bracket (">") to the part specifier. If the starting octet is beyond the end of the text, an empty string is returned. Any partial fetch that attempts to read beyond the end of the text is truncated as appropriate. A partial fetch that starts at octet 0 is returned as a partial fetch, even if this truncation happened. Note: This means that BODY[]<0.2048> of a 1500-octet message will return BODY[]<0> with a literal of size 1500, not BODY[]. Note: A substring fetch of a HEADER.FIELDS or HEADER.FIELDS.NOT part specifier is calculated after subsetting the header. The \Seen flag is implicitly set; if this causes the flags to change, they SHOULD be included as part of the FETCH responses. BODY.PEEK[<section>]<<partial>> An alternate form of BODY[<section>] that does not implicitly set the \Seen flag. BODYSTRUCTURE The [MIME-IMB] body structure of the message. This is computed by the server by parsing the [MIME-IMB] header fields in the [RFC-2822] header and [MIME-IMB] headers. ENVELOPE The envelope structure of the message. This is computed by the server by parsing the [RFC-2822] header into the component parts, defaulting various fields as necessary. FLAGS The flags that are set for this message. INTERNALDATE The internal date of the message. RFC822 Functionally equivalent to BODY[], differing in the syntax of the resulting untagged FETCH data (RFC822 is returned). RFC822.HEADER Functionally equivalent to BODY.PEEK[HEADER], differing in the syntax of the resulting untagged FETCH data (RFC822.HEADER is returned). RFC822.SIZE The [RFC-2822] size of the message. RFC822.TEXT Functionally equivalent to BODY[TEXT], differing in the syntax of the resulting untagged FETCH data (RFC822.TEXT is returned). UID The unique identifier for the message. Example: C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) S: * 2 FETCH .... S: * 3 FETCH .... S: * 4 FETCH .... S: A654 OK FETCH completed */ // Store start time long startTime = DateTime.Now.Ticks; if(!this.IsAuthenticated){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required.")); return; } if(m_pSelectedFolder == null){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Error: This command is valid only in selected state.")); return; } string[] parts = cmdText.Split(new char[]{' '},2); if(parts.Length != 2){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments.")); return; } IMAP_t_SeqSet seqSet = null; try{ seqSet = IMAP_t_SeqSet.Parse(parts[0]); } catch{ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments: Invalid 'sequence-set' value.")); return; } #region Parse data-items List<IMAP_t_Fetch_i> dataItems = new List<IMAP_t_Fetch_i>(); bool msgDataNeeded = false; // Remove parenthesizes. string dataItemsString = parts[1].Trim(); if(dataItemsString.StartsWith("(") && dataItemsString.EndsWith(")")){ dataItemsString = dataItemsString.Substring(1,dataItemsString.Length - 2).Trim(); } // Replace macros. dataItemsString = dataItemsString.Replace("ALL","FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"); dataItemsString = dataItemsString.Replace("FAST","FLAGS INTERNALDATE RFC822.SIZE"); dataItemsString = dataItemsString.Replace("FULL","FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"); StringReader r = new StringReader(dataItemsString); IMAP_Fetch_DataType fetchDataType = IMAP_Fetch_DataType.MessageHeader; // Parse data-items. while(r.Available > 0){ r.ReadToFirstChar(); #region BODYSTRUCTURE if(r.StartsWith("BODYSTRUCTURE",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_BodyStructure()); msgDataNeeded = true; if(fetchDataType != IMAP_Fetch_DataType.FullMessage){ fetchDataType = IMAP_Fetch_DataType.MessageStructure; } } #endregion #region BODY[<section>]<<partial>> and BODY.PEEK[<section>]<<partial>> else if(r.StartsWith("BODY[",false) || r.StartsWith("BODY.PEEK[",false)){ bool peek = r.StartsWith("BODY.PEEK[",false); r.ReadWord(); #region Parse <section> string section = r.ReadParenthesized(); // Full message wanted. if(string.IsNullOrEmpty(section)){ fetchDataType = IMAP_Fetch_DataType.FullMessage; } else{ // Left-side part-items must be numbers, only last one may be (HEADER,HEADER.FIELDS,HEADER.FIELDS.NOT,MIME,TEXT). StringReader rSection = new StringReader(section); string remainingSection = rSection.ReadWord(); while(remainingSection.Length > 0){ string[] section_parts = remainingSection.Split(new char[]{'.'},2); // Not part number. if(!Net_Utils.IsInteger(section_parts[0])){ // We must have one of the following values here (HEADER,HEADER.FIELDS,HEADER.FIELDS.NOT,MIME,TEXT). if(remainingSection.Equals("HEADER",StringComparison.InvariantCultureIgnoreCase)){ if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){ fetchDataType = IMAP_Fetch_DataType.MessageHeader; } } else if(remainingSection.Equals("HEADER.FIELDS",StringComparison.InvariantCultureIgnoreCase)){ rSection.ReadToFirstChar(); if(!rSection.StartsWith("(")){ WriteLine(cmdTag + " BAD Error in arguments."); return; } rSection.ReadParenthesized(); if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){ fetchDataType = IMAP_Fetch_DataType.MessageHeader; } } else if(remainingSection.Equals("HEADER.FIELDS.NOT",StringComparison.InvariantCultureIgnoreCase)){ rSection.ReadToFirstChar(); if(!rSection.StartsWith("(")){ WriteLine(cmdTag + " BAD Error in arguments."); return; } rSection.ReadParenthesized(); if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){ fetchDataType = IMAP_Fetch_DataType.MessageHeader; } } else if(remainingSection.Equals("MIME",StringComparison.InvariantCultureIgnoreCase)){ fetchDataType = IMAP_Fetch_DataType.FullMessage; } else if(remainingSection.Equals("TEXT",StringComparison.InvariantCultureIgnoreCase)){ fetchDataType = IMAP_Fetch_DataType.FullMessage; } // Unknown parts specifier. else{ WriteLine(cmdTag + " BAD Error in arguments."); return; } break; } else{ // For parts specifier, minimum is message structure. if(fetchDataType != IMAP_Fetch_DataType.FullMessage){ fetchDataType = IMAP_Fetch_DataType.MessageStructure; } } if(section_parts.Length == 2){ remainingSection = section_parts[1]; } else{ remainingSection = ""; } } } #endregion #region Parse <partial> int offset = -1; int maxCount = -1; // Partial data wanted. if(r.StartsWith("<")){ string[] origin = r.ReadParenthesized().Split('.'); if(origin.Length > 2){ WriteLine(cmdTag + " BAD Error in arguments."); return; } if(!int.TryParse(origin[0],out offset)){ WriteLine(cmdTag + " BAD Error in arguments."); return; } if(origin.Length == 2){ if(!int.TryParse(origin[1],out maxCount)){ WriteLine(cmdTag + " BAD Error in arguments."); return; } } } #endregion if(peek){ dataItems.Add(new IMAP_t_Fetch_i_BodyPeek(section,offset,maxCount)); } else{ dataItems.Add(new IMAP_t_Fetch_i_Body(section,offset,maxCount)); } msgDataNeeded = true; } #endregion #region BODY else if(r.StartsWith("BODY",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_BodyS()); msgDataNeeded = true; if(fetchDataType != IMAP_Fetch_DataType.FullMessage){ fetchDataType = IMAP_Fetch_DataType.MessageStructure; } } #endregion #region ENVELOPE else if(r.StartsWith("ENVELOPE",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Envelope()); msgDataNeeded = true; if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){ fetchDataType = IMAP_Fetch_DataType.MessageHeader; } } #endregion #region FLAGS else if(r.StartsWith("FLAGS",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Flags()); } #endregion #region INTERNALDATE else if(r.StartsWith("INTERNALDATE",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_InternalDate()); } #endregion #region RFC822.HEADER else if(r.StartsWith("RFC822.HEADER",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Rfc822Header()); msgDataNeeded = true; if(fetchDataType != IMAP_Fetch_DataType.FullMessage && fetchDataType != IMAP_Fetch_DataType.MessageStructure){ fetchDataType = IMAP_Fetch_DataType.MessageHeader; } } #endregion #region RFC822.SIZE else if(r.StartsWith("RFC822.SIZE",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Rfc822Size()); } #endregion #region RFC822.TEXT else if(r.StartsWith("RFC822.TEXT",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Rfc822Text()); msgDataNeeded = true; fetchDataType = IMAP_Fetch_DataType.FullMessage; } #endregion #region RFC822 else if(r.StartsWith("RFC822",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Rfc822()); msgDataNeeded = true; fetchDataType = IMAP_Fetch_DataType.FullMessage; } #endregion #region UID else if(r.StartsWith("UID",false)){ r.ReadWord(); dataItems.Add(new IMAP_t_Fetch_i_Uid()); } #endregion #region Unknown data-item. else{ WriteLine(cmdTag + " BAD Error in arguments: Unknown FETCH data-item."); return; } #endregion } #endregion // UID FETCH must always return UID data-item, even if user didn't request it. if(uid){ bool add = true; foreach(IMAP_t_Fetch_i item in dataItems){ if(item is IMAP_t_Fetch_i_Uid){ add = false; break; } } if(add){ dataItems.Add(new IMAP_t_Fetch_i_Uid()); } } UpdateSelectedFolderAndSendChanges(); IMAP_e_Fetch fetchEArgs = new IMAP_e_Fetch( m_pSelectedFolder.Filter(uid,seqSet), fetchDataType, new IMAP_r_ServerStatus(cmdTag,"OK","FETCH command completed in %exectime seconds.") ); fetchEArgs.NewMessageData += new EventHandler<IMAP_e_Fetch.e_NewMessageData>(delegate(object s,IMAP_e_Fetch.e_NewMessageData e){ StringBuilder reponseBuffer = new StringBuilder(); reponseBuffer.Append("* " + e.MessageInfo.SeqNo + " FETCH ("); Mail_Message message = e.MessageData; // Return requested data-items for the returned message. for(int i=0;i<dataItems.Count;i++){ IMAP_t_Fetch_i dataItem = dataItems[i]; // Add data-items separator. if(i > 0){ reponseBuffer.Append(" "); } #region BODY if(dataItem is IMAP_t_Fetch_i_BodyS){ reponseBuffer.Append(ConstructBodyStructure(message,false)); } #endregion #region BODY[<section>]<<partial>> and BODY.PEEK[<section>]<<partial>> else if(dataItem is IMAP_t_Fetch_i_Body || dataItem is IMAP_t_Fetch_i_BodyPeek){ string section = ""; int offset = -1; int maxCount = -1; if(dataItem is IMAP_t_Fetch_i_Body){ section = ((IMAP_t_Fetch_i_Body)dataItem).Section; offset = ((IMAP_t_Fetch_i_Body)dataItem).Offset; maxCount = ((IMAP_t_Fetch_i_Body)dataItem).MaxCount; } else{ section = ((IMAP_t_Fetch_i_BodyPeek)dataItem).Section; offset = ((IMAP_t_Fetch_i_BodyPeek)dataItem).Offset; maxCount = ((IMAP_t_Fetch_i_BodyPeek)dataItem).MaxCount; } using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){ // Empty section, full message wanted. if(string.IsNullOrEmpty(section)){ message.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8); tmpFs.Position = 0; } // Message data part wanted. else{ // Get specified MIME part. MIME_Entity entity = GetMimeEntity(message,ParsePartNumberFromSection(section)); if(entity != null){ string partSpecifier = ParsePartSpecifierFromSection(section); #region HEADER if(string.Equals(partSpecifier,"HEADER",StringComparison.InvariantCultureIgnoreCase)){ entity.Header.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8); // All header fetches must include header terminator(CRLF). if(tmpFs.Length >0 ){ tmpFs.WriteByte((byte)'\r'); tmpFs.WriteByte((byte)'\n'); } tmpFs.Position = 0; } #endregion #region HEADER.FIELDS else if(string.Equals(partSpecifier,"HEADER.FIELDS",StringComparison.InvariantCultureIgnoreCase)){ string fieldsString = section.Split(new char[]{' '},2)[1]; string[] fieldNames = fieldsString.Substring(1,fieldsString.Length - 2).Split(' '); foreach(string filedName in fieldNames){ MIME_h[] fields = entity.Header[filedName]; if(fields != null){ foreach(MIME_h field in fields){ byte[] fieldBytes = Encoding.UTF8.GetBytes(field.ToString(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8)); tmpFs.Write(fieldBytes,0,fieldBytes.Length); } } } // All header fetches must include header terminator(CRLF). if(tmpFs.Length > 0){ tmpFs.WriteByte((byte)'\r'); tmpFs.WriteByte((byte)'\n'); } tmpFs.Position = 0; } #endregion #region HEADER.FIELDS.NOT else if(string.Equals(partSpecifier,"HEADER.FIELDS.NOT",StringComparison.InvariantCultureIgnoreCase)){ string fieldsString = section.Split(new char[]{' '},2)[1]; string[] fieldNames = fieldsString.Substring(1,fieldsString.Length - 2).Split(' '); foreach(MIME_h field in entity.Header){ bool contains = false; foreach(string fieldName in fieldNames){ if(string.Equals(field.Name,fieldName,StringComparison.InvariantCultureIgnoreCase)){ contains = true; break; } } if(!contains){ byte[] fieldBytes = Encoding.UTF8.GetBytes(field.ToString(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8)); tmpFs.Write(fieldBytes,0,fieldBytes.Length); } } // All header fetches must include header terminator(CRLF). if(tmpFs.Length >0 ){ tmpFs.WriteByte((byte)'\r'); tmpFs.WriteByte((byte)'\n'); } tmpFs.Position = 0; } #endregion #region MIME else if(string.Equals(partSpecifier,"MIME",StringComparison.InvariantCultureIgnoreCase)){ entity.Header.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8); // All header fetches must include header terminator(CRLF). if(tmpFs.Length >0 ){ tmpFs.WriteByte((byte)'\r'); tmpFs.WriteByte((byte)'\n'); } tmpFs.Position = 0; } #endregion #region TEXT else if(string.Equals(partSpecifier,"TEXT",StringComparison.InvariantCultureIgnoreCase)){ entity.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false); tmpFs.Position = 0; } #endregion #region part-number only else{ entity.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false); tmpFs.Position = 0; } #endregion } } #region Send data // All data wanted. if(offset < 0){ reponseBuffer.Append("BODY[" + section + "] {" + tmpFs.Length + "}\r\n"); WriteLine(reponseBuffer.ToString()); reponseBuffer = new StringBuilder(); this.TcpStream.WriteStream(tmpFs); LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes."); } // Partial data wanted. else{ // Offet out of range. if(offset >= tmpFs.Length){ reponseBuffer.Append("BODY[" + section + "]<" + offset + "> \"\""); } else{ tmpFs.Position = offset; int count = maxCount > -1 ? (int)Math.Min(maxCount,tmpFs.Length - tmpFs.Position) : (int)(tmpFs.Length - tmpFs.Position); reponseBuffer.Append("BODY[" + section + "]<" + offset + "> {" + count + "}"); WriteLine(reponseBuffer.ToString()); reponseBuffer = new StringBuilder(); this.TcpStream.WriteStream(tmpFs,count); LogAddWrite(tmpFs.Length,"Wrote " + count + " bytes."); } } #endregion } // Set Seen flag. if(!m_pSelectedFolder.IsReadOnly && dataItem is IMAP_t_Fetch_i_Body){ try{ OnStore(e.MessageInfo,IMAP_Flags_SetType.Add,new string[]{"Seen"},new IMAP_r_ServerStatus("dummy","OK","This is FETCH set Seen flag, this response not used.")); } catch{ } } } #endregion #region BODYSTRUCTURE else if(dataItem is IMAP_t_Fetch_i_BodyStructure){ reponseBuffer.Append(ConstructBodyStructure(message,true)); } #endregion #region ENVELOPE else if(dataItem is IMAP_t_Fetch_i_Envelope){ reponseBuffer.Append(IMAP_t_Fetch_r_i_Envelope.ConstructEnvelope(message)); } #endregion #region FLAGS else if(dataItem is IMAP_t_Fetch_i_Flags){ reponseBuffer.Append("FLAGS " + e.MessageInfo.FlagsToImapString()); } #endregion #region INTERNALDATE else if(dataItem is IMAP_t_Fetch_i_InternalDate){ reponseBuffer.Append("INTERNALDATE \"" + IMAP_Utils.DateTimeToString(e.MessageInfo.InternalDate) + "\""); } #endregion #region RFC822 else if(dataItem is IMAP_t_Fetch_i_Rfc822){ using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){ message.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8); tmpFs.Position = 0; reponseBuffer.Append("RFC822 {" + tmpFs.Length + "}\r\n"); WriteLine(reponseBuffer.ToString()); reponseBuffer = new StringBuilder(); this.TcpStream.WriteStream(tmpFs); LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes."); } } #endregion #region RFC822.HEADER else if(dataItem is IMAP_t_Fetch_i_Rfc822Header){ MemoryStream ms = new MemoryStream(); message.Header.ToStream(ms,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8); ms.Position = 0; reponseBuffer.Append("RFC822.HEADER {" + ms.Length + "}\r\n"); WriteLine(reponseBuffer.ToString()); reponseBuffer = new StringBuilder(); this.TcpStream.WriteStream(ms); LogAddWrite(ms.Length,"Wrote " + ms.Length + " bytes."); } #endregion #region RFC822.SIZE else if(dataItem is IMAP_t_Fetch_i_Rfc822Size){ reponseBuffer.Append("RFC822.SIZE " + e.MessageInfo.Size); } #endregion #region RFC822.TEXT else if(dataItem is IMAP_t_Fetch_i_Rfc822Text){ using(MemoryStreamEx tmpFs = new MemoryStreamEx(32000)){ message.Body.ToStream(tmpFs,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.B,Encoding.UTF8),Encoding.UTF8,false); tmpFs.Position = 0; reponseBuffer.Append("RFC822.TEXT {" + tmpFs.Length + "}\r\n"); WriteLine(reponseBuffer.ToString()); reponseBuffer = new StringBuilder(); this.TcpStream.WriteStream(tmpFs); LogAddWrite(tmpFs.Length,"Wrote " + tmpFs.Length + " bytes."); } } #endregion #region UID else if(dataItem is IMAP_t_Fetch_i_Uid){ reponseBuffer.Append("UID " + e.MessageInfo.UID); } #endregion } reponseBuffer.Append(")\r\n"); WriteLine(reponseBuffer.ToString()); }); // We have all needed data in message info. if(!msgDataNeeded){ foreach(IMAP_MessageInfo msgInfo in m_pSelectedFolder.Filter(uid,seqSet)){ fetchEArgs.AddData(msgInfo); } } // Request messages data. else{ OnFetch(fetchEArgs); } WriteLine(fetchEArgs.Response.ToString().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2"))); }
private void APPEND(string cmdTag,string cmdText) { /* RFC 3501 6.3.11. APPEND Command. Arguments: mailbox name OPTIONAL flag parenthesized list OPTIONAL date/time string message literal Responses: no specific responses for this command Result: OK - append completed NO - append error: can't append to that mailbox, error in flags or date/time or message text BAD - command unknown or arguments invalid The APPEND command appends the literal argument as a new message to the end of the specified destination mailbox. This argument SHOULD be in the format of an [RFC-2822] message. 8-bit characters are permitted in the message. A server implementation that is unable to preserve 8-bit data properly MUST be able to reversibly convert 8-bit APPEND data to 7-bit using a [MIME-IMB] content transfer encoding. Note: There MAY be exceptions, e.g., draft messages, in which required [RFC-2822] header lines are omitted in the message literal argument to APPEND. The full implications of doing so MUST be understood and carefully weighed. If a flag parenthesized list is specified, the flags SHOULD be set in the resulting message; otherwise, the flag list of the resulting message is set to empty by default. In either case, the Recent flag is also set. If a date-time is specified, the internal date SHOULD be set in the resulting message; otherwise, the internal date of the resulting message is set to the current date and time by default. If the append is unsuccessful for any reason, the mailbox MUST be restored to its state before the APPEND attempt; no partial appending is permitted. If the destination mailbox does not exist, a server MUST return an error, and MUST NOT automatically create the mailbox. Unless it is certain that the destination mailbox can not be created, the server MUST send the response code "[TRYCREATE]" as the prefix of the text of the tagged NO response. This gives a hint to the client that it can attempt a CREATE command and retry the APPEND if the CREATE is successful. If the mailbox is currently selected, the normal new message actions SHOULD occur. Specifically, the server SHOULD notify the client immediately via an untagged EXISTS response. If the server does not do so, the client MAY issue a NOOP command (or failing that, a CHECK command) after one or more APPEND commands. Example: C: A003 APPEND saved-messages (\Seen) {310} S: + Ready for literal data C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST) C: From: Fred Foobar <*****@*****.**> C: Subject: afternoon meeting C: To: [email protected] C: Message-Id: <*****@*****.**> C: MIME-Version: 1.0 C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII C: C: Hello Joe, do you think we can meet at 3:30 tomorrow? C: S: A003 OK APPEND completed Note: The APPEND command is not used for message delivery, because it does not provide a mechanism to transfer [SMTP] envelope information. */ if(!this.IsAuthenticated){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required.")); return; } // Store start time long startTime = DateTime.Now.Ticks; #region Parse arguments StringReader r = new StringReader(cmdText); r.ReadToFirstChar(); string folder = null; if(r.StartsWith("\"")){ folder = IMAP_Utils.DecodeMailbox(r.ReadWord()); } else{ folder = IMAP_Utils.DecodeMailbox(r.QuotedReadToDelimiter(' ')); } r.ReadToFirstChar(); List<string> flags = new List<string>(); if(r.StartsWith("(")){ foreach(string f in r.ReadParenthesized().Split(' ')){ if(f.Length > 0 && !flags.Contains(f.Substring(1))){ flags.Add(f.Substring(1)); } } } r.ReadToFirstChar(); DateTime date = DateTime.MinValue; if(!r.StartsWith("{")){ date = IMAP_Utils.ParseDate(r.ReadWord()); } int size = Convert.ToInt32(r.ReadParenthesized()); if(r.Available > 0){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments.")); return; } #endregion IMAP_e_Append e = OnAppend(folder,flags.ToArray(),date,size,new IMAP_r_ServerStatus(cmdTag,"OK","APPEND command completed in %exectime seconds.")); if(e.Response.IsError){ m_pResponseSender.SendResponseAsync(e.Response); } else if(e.Stream == null){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Internal server error: No storage stream available.")); } else{ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+","Ready for literal data.")); // Create callback which is called when BeginReadFixedCount completes. AsyncCallback readLiteralCompletedCallback = delegate(IAsyncResult ar){ try{ this.TcpStream.EndReadFixedCount(ar); // Log. LogAddRead(size,"Readed " + size + " bytes."); // TODO: Async // Read command line terminating CRLF. SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException); this.TcpStream.ReadLine(readLineOP,false); // Read command line terminating CRLF failed. if(readLineOP.Error != null){ OnError(readLineOP.Error); } // Read command line terminating CRLF succeeded. else{ LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8); // Raise Completed event. e.OnCompleted(); m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(e.Response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2")))); BeginReadCmd(); } } catch(Exception x){ OnError(x); } }; this.TcpStream.BeginReadFixedCount(e.Stream,size,readLiteralCompletedCallback,null); } }
/// <summary> /// Reads IMAP string/astring/nstring/utf8-quoted from string reader. /// </summary> /// <param name="reader">String reader.</param> /// <returns>Returns IMAP string.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>reader</b> is null reference.</exception> internal static string ReadString(StringReader reader) { if(reader == null){ throw new ArgumentNullException("reader"); } reader.ReadToFirstChar(); // utf8-quoted if(reader.StartsWith("*\"")){ reader.ReadSpecifiedLength(1); return reader.ReadWord(); } // string/astring/nstring else{ string word = reader.ReadWord(); // nstring if(string.Equals(word,"NIL",StringComparison.InvariantCultureIgnoreCase)){ return null; } return word; } }
/// <summary> /// Reads IMAP string(string-literal,quoted-string,NIL) and remaining FETCH line if needed. /// </summary> /// <param name="imap">IMAP client.</param> /// <param name="r">Fetch line reader.</param> /// <param name="callback">Fetch completion callback.</param> /// <param name="stream">Stream where to store readed data.</param> /// <returns>Returns true if completed asynchronously or false if completed synchronously.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>r</b>,<b>callback</b> or <b>stream</b> is null reference.</exception> private bool ReadData(IMAP_Client imap,StringReader r,EventHandler<EventArgs<Exception>> callback,Stream stream) { if(imap == null){ throw new ArgumentNullException("imap"); } if(r == null){ throw new ArgumentNullException("r"); } if(callback == null){ throw new ArgumentNullException("callback"); } if(stream == null){ throw new ArgumentNullException("stream"); } r.ReadToFirstChar(); // We don't have data. if(r.StartsWith("NIL",false)){ // Eat NIL. r.ReadWord(); return false; } // Data value is returned as string-literal. else if(r.StartsWith("{",false)){ IMAP_Client.ReadStringLiteralAsyncOP op = new IMAP_Client.ReadStringLiteralAsyncOP(stream,Convert.ToInt32(r.ReadParenthesized())); op.CompletedAsync += delegate(object sender,EventArgs<IMAP_Client.ReadStringLiteralAsyncOP> e){ try{ // Read string literal failed. if(op.Error != null){ callback(this,new EventArgs<Exception>(op.Error)); } else{ // Read next fetch line completed synchronously. if(!ReadNextFetchLine(imap,r,callback)){ ParseDataItems(imap,r,callback); } } } catch(Exception x){ callback(this,new EventArgs<Exception>(x)); } finally{ op.Dispose(); } }; // Read string literal completed sync. if(!imap.ReadStringLiteralAsync(op)){ try{ // Read string literal failed. if(op.Error != null){ callback(this,new EventArgs<Exception>(op.Error)); return true; } else{ // Read next fetch line completed synchronously. if(!ReadNextFetchLine(imap,r,callback)){ return false; } else{ return true; } } } finally{ op.Dispose(); } } // Read string literal completed async. else{ return true; } } // Data is quoted-string. else{ byte[] data = Encoding.UTF8.GetBytes(r.ReadWord()); stream.Write(data,0,data.Length); return false; } }
/// <summary> /// Starts parsing fetch data-items, /// </summary> /// <param name="imap">IMAP client.</param> /// <param name="r">Fetch line reader.</param> /// <param name="callback">Callback to be called when parsing completes.</param> /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>r</b> or <b>callback</b> is null reference.</exception> private void ParseDataItems(IMAP_Client imap,StringReader r,EventHandler<EventArgs<Exception>> callback) { if(imap == null){ throw new ArgumentNullException("imap"); } if(r == null){ throw new ArgumentNullException("r"); } if(callback == null){ throw new ArgumentNullException("callback"); } /* RFC 3501 7.4.2. FETCH Response. Example: S: * 23 FETCH (FLAGS (\Seen) RFC822.SIZE 44827) */ while(true){ r.ReadToFirstChar(); #region BODY[] if(r.StartsWith("BODY[",false)){ /* RFC 3501 7.4.2. FETCH Response. BODY[<section>]<<origin octet>> A string expressing the body contents of the specified section. The string SHOULD be interpreted by the client according to the content transfer encoding, body type, and subtype. If the origin octet is specified, this string is a substring of the entire body contents, starting at that origin octet. This means that BODY[]<0> MAY be truncated, but BODY[] is NEVER truncated. Note: The origin octet facility MUST NOT be used by a server in a FETCH response unless the client specifically requested it by means of a FETCH of a BODY[<section>]<<partial>> data item. 8-bit textual data is permitted if a [CHARSET] identifier is part of the body parameter parenthesized list for this section. Note that headers (part specifiers HEADER or MIME, or the header portion of a MESSAGE/RFC822 part), MUST be 7-bit; 8-bit characters are not permitted in headers. Note also that the [RFC-2822] delimiting blank line between the header and the body is not affected by header line subsetting; the blank line is always included as part of header data, except in the case of a message which has no body and no blank line. Non-textual data such as binary data MUST be transfer encoded into a textual form, such as BASE64, prior to being sent to the client. To derive the original binary data, the client MUST decode the transfer encoded string. */ // Eat BODY word. r.ReadWord(); // Read body-section. string section = r.ReadParenthesized(); // Read origin if any. int offset = -1; if(r.StartsWith("<")){ offset = Convert.ToInt32(r.ReadParenthesized().Split(' ')[0]); } IMAP_t_Fetch_r_i_Body dataItem = new IMAP_t_Fetch_r_i_Body(section,offset,new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region BODY else if(r.StartsWith("BODY ",false)){ //IMAP_t_Fetch_r_i_BodyS } #endregion #region BODYSTRUCTURE else if(r.StartsWith("BODYSTRUCTURE",false)){ //IMAP_t_Fetch_r_i_BodyStructure } #endregion #region ENVELOPE else if(r.StartsWith("ENVELOPE",false)){ // Envelope can contain string literals, we just try to parse it. // If parse fails, just get string literal and try again as long as all ENVELOPE data has read. string envelope = null; while(true){ // Create temporary reader(we don't want to read partial ENVELOPE data from reader). StringReader tmpReader = new StringReader(r.SourceString); // Eat ENVELOPE word. tmpReader.ReadWord(); tmpReader.ReadToFirstChar(); try{ envelope = tmpReader.ReadParenthesized(); // We got full ENVELOPE, so use tmp reader as reader. r = tmpReader; break; } catch{ // Read completed async, it will continue parsing. if(ReadStringLiteral(imap,r,callback)){ return; } } } m_pDataItems.Add(IMAP_t_Fetch_r_i_Envelope.Parse(new StringReader(envelope))); } #endregion #region FLAGS else if(r.StartsWith("FLAGS",false)){ /* RFC 3501 7.4.2. FETCH Response. FLAGS A parenthesized list of flags that are set for this message. */ // Eat FLAGS word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Flags(IMAP_t_MsgFlags.Parse(r.ReadParenthesized()))); } #endregion #region INTERNALDATE else if(r.StartsWith("INTERNALDATE",false)){ /* RFC 3501 7.4.2. FETCH Response. INTERNALDATE A string representing the internal date of the message. */ // Eat INTERNALDATE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_InternalDate(IMAP_Utils.ParseDate(r.ReadWord()))); } #endregion #region RFC822 else if(r.StartsWith("RFC822 ",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822 Equivalent to BODY[]. */ // Eat RFC822 word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822 dataItem = new IMAP_t_Fetch_r_i_Rfc822(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region RFC822.HEADER else if(r.StartsWith("RFC822.HEADER",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.HEADER Equivalent to BODY[HEADER]. Note that this did not result in \Seen being set, because RFC822.HEADER response data occurs as a result of a FETCH of RFC822.HEADER. BODY[HEADER] response data occurs as a result of a FETCH of BODY[HEADER] (which sets \Seen) or BODY.PEEK[HEADER] (which does not set \Seen). */ // Eat RFC822.HEADER word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Header dataItem = new IMAP_t_Fetch_r_i_Rfc822Header(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region RFC822.SIZE else if(r.StartsWith("RFC822.SIZE",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.SIZE A number expressing the [RFC-2822] size of the message. */ // Eat RFC822.SIZE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Rfc822Size(Convert.ToInt32(r.ReadWord()))); } #endregion #region RFC822.TEXT else if(r.StartsWith("RFC822.TEXT",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.TEXT Equivalent to BODY[TEXT]. */ // Eat RFC822.TEXT word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Text dataItem = new IMAP_t_Fetch_r_i_Rfc822Text(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region UID else if(r.StartsWith("UID",false)){ /* RFC 3501 7.4.2. FETCH Response. UID A number expressing the unique identifier of the message. */ // Eat UID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Uid(Convert.ToInt64(r.ReadWord()))); } #endregion #region X-GM-MSGID else if(r.StartsWith("X-GM-MSGID",false)){ /* http://code.google.com/intl/et/apis/gmail/imap X-GM-MSGID. */ // Eat X-GM-MSGID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_MSGID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region X-GM-THRID else if(r.StartsWith("X-GM-THRID",false)){ /* http://code.google.com/intl/et/apis/gmail/imap X-GM-THRID. */ // Eat X-GM-THRID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_THRID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region ) - fetch closing. else if(r.StartsWith(")",false)){ break; } #endregion else{ throw new ParseException("Not supported FETCH data-item '" + r.ReadToEnd() + "'."); } } callback(this,new EventArgs<Exception>(null)); }
/// <summary> /// Gets files and directories in the current server directory. /// </summary> /// <param name="path">Directory or file name which listing to get. Value null means current directory will be listed.</param> /// <returns>Returns current working directory listing.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when FTP client is not connected or FTP data connection has active read/write operation.</exception> /// <exception cref="FTP_ClientException">Is raised when FTP server returns error.</exception> public FTP_ListItem[] GetList(string path) { if(this.IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(!this.IsConnected){ throw new InvalidOperationException("You must connect first."); } if(m_pDataConnection.IsActive){ throw new InvalidOperationException("There is already active read/write operation on data connection."); } List<FTP_ListItem> retVal = new List<FTP_ListItem>(); // Set transfer mode SetTransferType(TransferType.Binary); if(m_TransferMode == FTP_TransferMode.Passive){ Pasv(); } else{ Port(); } // If FTP server supports MLSD command, use it to get directory listing. // MLSD is standard way to get dir listing, while LIST command isn't any strict standard. bool mlsdSupported = false; foreach(string feature in m_pExtCapabilities){ if(feature.ToLower().StartsWith("mlsd")){ mlsdSupported = true; break; } } #region MLSD if(mlsdSupported){ if(string.IsNullOrEmpty(path)){ WriteLine("MLSD"); } else{ WriteLine("MLSD " + path); } string[] response = ReadResponse(); if(!response[0].StartsWith("1")){ throw new FTP_ClientException(response[0]); } MemoryStream ms = new MemoryStream(); m_pDataConnection.ReadAll(ms); response = ReadResponse(); if(!response[0].StartsWith("2")){ throw new FTP_ClientException(response[0]); } byte[] lineBuffer = new byte[8000]; ms.Position = 0; SmartStream mlsdStream = new SmartStream(ms,true); while(true){ SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(lineBuffer,SizeExceededAction.JunkAndThrowException); mlsdStream.ReadLine(args,false); if(args.Error != null){ throw args.Error; } string line = args.LineUtf8; // We reached end of stream, we readed whole list sucessfully. if(line == null){ break; } else{ string[] parameters = line.Substring(0,line.LastIndexOf(';')).Split(';'); string name = line.Substring(line.LastIndexOf(';') + 1).Trim(); string type = ""; long size = 0; DateTime modified = DateTime.MinValue; foreach(string parameter in parameters){ string[] name_value = parameter.Split('='); if(name_value[0].ToLower() == "type"){ type = name_value[1].ToLower(); } else if(name_value[0].ToLower() == "size"){ size = Convert.ToInt32(name_value[1]); } else if(name_value[0].ToLower() == "modify"){ modified = DateTime.ParseExact(name_value[1],"yyyyMMddHHmmss",System.Globalization.DateTimeFormatInfo.InvariantInfo); } else{ // Other options won't interest us, skip them. } } if(type == "dir"){ retVal.Add(new FTP_ListItem(name,0,modified,true)); } else if(type == "file"){ retVal.Add(new FTP_ListItem(name,size,modified,false)); } } } } #endregion #region LIST else{ if(string.IsNullOrEmpty(path)){ WriteLine("LIST"); } else{ WriteLine("LIST " + path); } string[] response = ReadResponse(); if(!response[0].StartsWith("1")){ throw new FTP_ClientException(response[0]); } MemoryStream ms = new MemoryStream(); m_pDataConnection.ReadAll(ms); response = ReadResponse(); if(!response[0].StartsWith("2")){ throw new FTP_ClientException(response[0]); } ms.Position = 0; SmartStream listStream = new SmartStream(ms,true); string[] winDateFormats = new string[]{"M-d-yy h:mmtt"}; string[] unixFormats = new string[]{"MMM d H:mm","MMM d yyyy"}; SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException); while(true){ listStream.ReadLine(args,false); if(args.Error != null){ throw args.Error; } else if(args.BytesInBuffer == 0){ break; } string line = args.LineUtf8; // Dedect listing. string listingType = "unix"; if(line != null){ StringReader r = new StringReader(line); DateTime modified; if(DateTime.TryParseExact(r.ReadWord() + " " + r.ReadWord(),new string[]{"MM-dd-yy hh:mmtt"},System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None,out modified)){ listingType = "win"; } } try{ // Windows listing. if(listingType == "win"){ // MM-dd-yy hh:mm <DIR> directoryName // MM-dd-yy hh:mm size fileName StringReader r = new StringReader(line); // Read date DateTime modified = DateTime.ParseExact(r.ReadWord() + " " + r.ReadWord(),winDateFormats,System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None); r.ReadToFirstChar(); // We have directory. if(r.StartsWith("<dir>",false)){ r.ReadSpecifiedLength(5); r.ReadToFirstChar(); retVal.Add(new FTP_ListItem(r.ReadToEnd(),0,modified,true)); } // We have file else{ // Read file size long size = Convert.ToInt64(r.ReadWord()); r.ReadToFirstChar(); retVal.Add(new FTP_ListItem(r.ReadToEnd(),size,modified,false)); } } // Unix listing else{ // "d"directoryAtttributes xx xx xx 0 MMM d HH:mm/yyyy directoryName // fileAtttributes xx xx xx fileSize MMM d HH:mm/yyyy fileName StringReader r = new StringReader(line); string attributes = r.ReadWord(); r.ReadWord(); r.ReadWord(); r.ReadWord(); long size = Convert.ToInt64(r.ReadWord()); DateTime modified = DateTime.ParseExact(r.ReadWord() + " " + r.ReadWord() + " " + r.ReadWord(),unixFormats,System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None); r.ReadToFirstChar(); string name = r.ReadToEnd(); if(name != "." && name != ".."){ if(attributes.StartsWith("d")){ retVal.Add(new FTP_ListItem(name,0,modified,true)); } else{ retVal.Add(new FTP_ListItem(name,size,modified,false)); } } } } catch{ // Skip unknown entries. } } } #endregion return retVal.ToArray(); }
/// <summary> /// Gets specified folder quota info. Throws Exception if server doesn't support QUOTA. /// </summary> /// <param name="folder">Folder name.</param> /// <returns></returns> public IMAP_Quota GetFolderQuota(string folder) { /* RFC 2087 4.3. GETQUOTAROOT Command Arguments: mailbox name Data: untagged responses: QUOTAROOT, QUOTA Result: OK - getquota completed NO - getquota error: no such mailbox, permission denied BAD - command unknown or arguments invalid The GETQUOTAROOT command takes the name of a mailbox and returns the list of quota roots for the mailbox in an untagged QUOTAROOT response. For each listed quota root, it also returns the quota root's resource usage and limits in an untagged QUOTA response. Example: C: A003 GETQUOTAROOT INBOX S: * QUOTAROOT INBOX "" S: * QUOTA "" (STORAGE 10 512) S: A003 OK Getquota completed */ if(!m_Connected){ throw new Exception("You must connect first !"); } if(!m_Authenticated){ throw new Exception("You must authenticate first !"); } IMAP_Quota retVal = null; m_pSocket.WriteLine("a1 GETQUOTAROOT \"" + Core.Encode_IMAP_UTF7_String(folder) + "\""); // Must get lines with * and cmdTag + OK or cmdTag BAD/NO string reply = m_pSocket.ReadLine(); if(reply.StartsWith("*")){ // Read multiline response while(reply.StartsWith("*")){ // Get rid of * reply = reply.Substring(1).Trim(); if(reply.ToUpper().StartsWith("QUOTAROOT")){ // Skip QUOTAROOT } else if(reply.ToUpper().StartsWith("QUOTA")){ StringReader r = new StringReader(reply); // Skip QUOTA word r.ReadWord(); string qoutaRootName = r.ReadWord(); long storage = -1; long maxStorage = -1; long messages = -1; long maxMessages = -1; string limits = r.ReadParenthesized(); r = new StringReader(limits); while(r.Available > 0){ string limitName = r.ReadWord(); // STORAGE usedBytes maximumAllowedBytes if(limitName.ToUpper() == "STORAGE"){ storage = Convert.ToInt64(r.ReadWord()); maxStorage = Convert.ToInt64(r.ReadWord()); } // STORAGE messagesCount maximumAllowedMessages else if(limitName.ToUpper() == "MESSAGE"){ messages = Convert.ToInt64(r.ReadWord()); maxMessages = Convert.ToInt64(r.ReadWord()); } } retVal = new IMAP_Quota(qoutaRootName,messages,maxMessages,storage,maxStorage); } reply = m_pSocket.ReadLine(); } } reply = reply.Substring(reply.IndexOf(" ")).Trim(); // Remove Cmd tag if(!reply.ToUpper().StartsWith("OK")){ throw new Exception("Server returned:" + reply); } return retVal; }
/// <summary> /// Decodes "encoded-word"'s from the specified text. For more information see RFC 2047. /// </summary> /// <param name="text">Text to decode.</param> /// <returns>Returns decoded text.</returns> public static string DecodeWords(string text) { if(text == null){ return null; } /* RFC 2047 2. Syntax of encoded-words. An 'encoded-word' is defined by the following ABNF grammar. The notation of RFC 822 is used, with the exception that white space characters MUST NOT appear between components of an 'encoded-word'. encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" charset = token ; see section 3 encoding = token ; see section 4 token = 1*<Any CHAR except SPACE, CTLs, and especials> especials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / " <"> / "/" / "[" / "]" / "?" / "." / "=" encoded-text = 1*<Any printable ASCII character other than "?" or SPACE> ; (but see "Use of encoded-words in message headers", section 5) Both 'encoding' and 'charset' names are case-independent. Thus the charset name "ISO-8859-1" is equivalent to "iso-8859-1", and the encoding named "Q" may be spelled either "Q" or "q". An 'encoded-word' may not be more than 75 characters long, including 'charset', 'encoding', 'encoded-text', and delimiters. If it is desirable to encode more text than will fit in an 'encoded-word' of 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may be used. IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's by an RFC 822 parser. As a consequence, unencoded white space characters (such as SPACE and HTAB) are FORBIDDEN within an 'encoded-word'. For example, the character sequence =?iso-8859-1?q?this is some text?= would be parsed as four 'atom's, rather than as a single 'atom' (by an RFC 822 parser) or 'encoded-word' (by a parser which understands 'encoded-words'). The correct way to encode the string "this is some text" is to encode the SPACE characters as well, e.g. =?iso-8859-1?q?this=20is=20some=20text?= */ StringReader r = new StringReader(text); StringBuilder retVal = new StringBuilder(); // We need to loop all words, if encoded word, decode it, othwerwise just append to return value. bool lastIsEncodedWord = false; while(r.Available > 0){ string whiteSpaces = r.ReadToFirstChar(); // Probably is encoded-word, we try to parse it. if(r.StartsWith("=?") && r.SourceString.IndexOf("?=") > -1){ StringBuilder encodedWord = new StringBuilder(); string decodedWord = null; try{ // NOTE: We can't read encoded word and then split !!!, we need to read each part. // Remove =? encodedWord.Append(r.ReadSpecifiedLength(2)); // Read charset string charset = r.QuotedReadToDelimiter('?'); encodedWord.Append(charset + "?"); // Read encoding string encoding = r.QuotedReadToDelimiter('?'); encodedWord.Append(encoding + "?"); // Read text string encodedText = r.QuotedReadToDelimiter('?'); encodedWord.Append(encodedText + "?"); // We must have remaining '=' here if(r.StartsWith("=")){ encodedWord.Append(r.ReadSpecifiedLength(1)); Encoding c = Encoding.GetEncoding(charset); if(encoding.ToLower() == "q"){ decodedWord = Core.QDecode(c,encodedText); } else if(encoding.ToLower() == "b"){ decodedWord = c.GetString(Core.Base64Decode(Encoding.Default.GetBytes(encodedText))); } } } catch{ // Not encoded-word or contains unknwon charset/encoding, so leave // encoded-word as is. } /* RFC 2047 6.2. When displaying a particular header field that contains multiple 'encoded-word's, any 'linear-white-space' that separates a pair of adjacent 'encoded-word's is ignored. (This is to allow the use of multiple 'encoded-word's to represent long strings of unencoded text, without having to separate 'encoded-word's where spaces occur in the unencoded text.) */ if(!lastIsEncodedWord){ retVal.Append(whiteSpaces); } // Decoding failed for that encoded-word, leave encoded-word as is. if(decodedWord == null){ retVal.Append(encodedWord.ToString()); } // We deocded encoded-word successfully. else{ retVal.Append(decodedWord); } lastIsEncodedWord = true; } // Normal word. else if(r.StartsWithWord()){ retVal.Append(whiteSpaces + r.ReadWord(false)); lastIsEncodedWord = false; } // We have some separator or parenthesize. else{ retVal.Append(whiteSpaces + r.ReadSpecifiedLength(1)); } } return retVal.ToString(); }
/// <summary> /// Fetches specifes messages specified fetch items. /// </summary> /// <param name="sequence_set">IMAP sequence-set.</param> /// <param name="fetchFlags">Specifies what data to fetch from IMAP server.</param> /// <param name="setSeenFlag">If true message seen flag is setted.</param> /// <param name="uidFetch">Specifies if sequence-set contains message UIDs or message numbers.</param> /// <returns>Returns requested fetch items.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when IMAP client is not connected,not authenticated and folder not selected.</exception> public IMAP_FetchItem[] FetchMessages(IMAP_SequenceSet sequence_set, IMAP_FetchItem_Flags fetchFlags, bool setSeenFlag, bool uidFetch) { if (this.IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (!this.IsConnected) { throw new InvalidOperationException("You must connect first."); } if (!this.IsAuthenticated) { throw new InvalidOperationException("The command is only valid in authenticated state."); } if (m_SelectedFolder.Length == 0) { throw new InvalidOperationException("The command is only valid in selected state."); } List<IMAP_FetchItem> fetchItems = new List<IMAP_FetchItem>(); //--- Construct FETCH command line -----------------------------------------------------------------------// string fetchCmdLine = GetNextCmdTag(); if (uidFetch) { fetchCmdLine += " UID"; } fetchCmdLine += " FETCH " + sequence_set.ToSequenceSetString() + " (UID"; // FLAGS if ((fetchFlags & IMAP_FetchItem_Flags.MessageFlags) != 0) { fetchCmdLine += " FLAGS"; } // RFC822.SIZE if ((fetchFlags & IMAP_FetchItem_Flags.Size) != 0) { fetchCmdLine += " RFC822.SIZE"; } // INTERNALDATE if ((fetchFlags & IMAP_FetchItem_Flags.InternalDate) != 0) { fetchCmdLine += " INTERNALDATE"; } // ENVELOPE if ((fetchFlags & IMAP_FetchItem_Flags.Envelope) != 0) { fetchCmdLine += " ENVELOPE"; } // BODYSTRUCTURE if ((fetchFlags & IMAP_FetchItem_Flags.BodyStructure) != 0) { fetchCmdLine += " BODYSTRUCTURE"; } // BODY[] or BODY.PEEK[] if ((fetchFlags & IMAP_FetchItem_Flags.Message) != 0) { if (setSeenFlag) { fetchCmdLine += " BODY[]"; } else { fetchCmdLine += " BODY.PEEK[]"; } } // BODY[HEADER] or BODY.PEEK[HEADER] ---> This needed only if full message isn't requested. if ((fetchFlags & IMAP_FetchItem_Flags.Message) == 0 && (fetchFlags & IMAP_FetchItem_Flags.Header) != 0) { if (setSeenFlag) { fetchCmdLine += " BODY[HEADER]"; } else { fetchCmdLine += " BODY.PEEK[HEADER]"; } } //--------------------------------------------------------------------------------------------------------// fetchCmdLine += ")"; // Send fetch command line to server. int countWritten = this.TcpStream.WriteLine(fetchCmdLine); LogAddWrite(countWritten, fetchCmdLine); // Read un-tagged response lines while we get final response line. byte[] lineBuffer = new byte[100000]; string line = ""; while (true) { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(lineBuffer, SizeExceededAction.JunkAndThrowException); this.TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); // We have un-tagged resposne. if (line.StartsWith("*")) { if (IsStatusResponse(line)) { ProcessStatusResponse(line); } else { int no = 0; int uid = 0; int size = 0; byte[] data = null; IMAP_MessageFlags flags = IMAP_MessageFlags.Recent; string envelope = ""; string bodystructure = ""; string internalDate = ""; // Remove * line = RemoveCmdTag(line); // Get message number no = Convert.ToInt32(line.Substring(0, line.IndexOf(" "))); // Get rid of FETCH and parse params. Reply:* 1 FETCH (UID 12 BODY[] ...) line = line.Substring(line.IndexOf("FETCH (") + 7); StringReader r = new StringReader(line); // Loop fetch result fields while (r.Available > 0) { r.ReadToFirstChar(); // Fetch command closing ) parenthesis if (r.SourceString == ")") { break; } #region UID <value> // UID <value> else if (r.StartsWith("UID", false)) { // Remove UID word from reply r.ReadSpecifiedLength("UID".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if (word == null) { throw new Exception("IMAP server didn't return UID <value> !"); } else { uid = Convert.ToInt32(word); } } #endregion #region RFC822.SIZE <value> // RFC822.SIZE <value> else if (r.StartsWith("RFC822.SIZE", false)) { // Remove RFC822.SIZE word from reply r.ReadSpecifiedLength("RFC822.SIZE".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if (word == null) { throw new Exception("IMAP server didn't return RFC822.SIZE <value> !"); } else { try { size = Convert.ToInt32(word); } catch { throw new Exception("IMAP server returned invalid RFC822.SIZE <value> '" + word + "' !"); } } } #endregion #region INTERNALDATE <value> // INTERNALDATE <value> else if (r.StartsWith("INTERNALDATE", false)) { // Remove INTERNALDATE word from reply r.ReadSpecifiedLength("INTERNALDATE".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if (word == null) { throw new Exception("IMAP server didn't return INTERNALDATE <value> !"); } else { internalDate = word; } } #endregion #region ENVELOPE (<envelope-string>) else if (r.StartsWith("ENVELOPE", false)) { // Remove ENVELOPE word from reply r.ReadSpecifiedLength("ENVELOPE".Length); r.ReadToFirstChar(); /* Handle string literals {count-to-read}<CRLF>data(length = count-to-read). (string can be quoted string or literal) Loop while get envelope,invalid response or timeout. */ while (true) { try { envelope = r.ReadParenthesized(); break; } catch (Exception x) { string s = r.ReadToEnd(); /* partial_envelope {count-to-read} Example: ENVELOPE ("Mon, 03 Apr 2006 10:10:10 GMT" {35} */ if (s.EndsWith("}")) { // Get partial envelope and append it back to reader r.AppenString(s.Substring(0, s.LastIndexOf('{'))); // Read remaining envelope and append it to reader. int countToRead = Convert.ToInt32(s.Substring(s.LastIndexOf('{') + 1, s.LastIndexOf('}') - s.LastIndexOf('{') - 1)); string reply = this.TcpStream.ReadFixedCountString(countToRead); LogAddRead(countToRead, reply); r.AppenString(TextUtils.QuoteString(reply)); // Read fetch continuing line. this.TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); r.AppenString(line); } // Unexpected response else { throw x; } } } } #endregion #region BODYSTRUCTURE (<bodystructure-string>) else if (r.StartsWith("BODYSTRUCTURE", false)) { // Remove BODYSTRUCTURE word from reply r.ReadSpecifiedLength("BODYSTRUCTURE".Length); r.ReadToFirstChar(); bodystructure = r.ReadParenthesized(); } #endregion #region BODY[] or BODY[HEADER] // BODY[] or BODY[HEADER] else if (r.StartsWith("BODY", false)) { if (r.StartsWith("BODY[]", false)) { // Remove BODY[] r.ReadSpecifiedLength("BODY[]".Length); } else if (r.StartsWith("BODY[HEADER]", false)) { // Remove BODY[HEADER] r.ReadSpecifiedLength("BODY[HEADER]".Length); } else { throw new Exception("Invalid FETCH response: " + r.SourceString); } r.ReadToFirstChar(); // We must now have {<size-to-read>}, or there is error if (!r.StartsWith("{")) { throw new Exception("Invalid FETCH BODY[] or BODY[HEADER] response: " + r.SourceString); } // Read <size-to-read> int dataLength = Convert.ToInt32(r.ReadParenthesized()); // Read data MemoryStream storeStrm = new MemoryStream(dataLength); this.TcpStream.ReadFixedCount(storeStrm, dataLength); LogAddRead(dataLength, "Readed " + dataLength.ToString() + " bytes."); data = storeStrm.ToArray(); // Read fetch continuing line. this.TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); r.AppenString(line); } #endregion #region FLAGS (<flags-list>) // FLAGS (<flags-list>) else if (r.StartsWith("FLAGS", false)) { // Remove FLAGS word from reply r.ReadSpecifiedLength("FLAGS".Length); r.ReadToFirstChar(); // Read (<flags-list>) string flagsList = r.ReadParenthesized(); if (flagsList == null) { throw new Exception("IMAP server didn't return FLAGS (<flags-list>) !"); } else { flags = IMAP_Utils.ParseMessageFlags(flagsList); } } #endregion else { throw new Exception("Not supported fetch reply: " + r.SourceString); } } fetchItems.Add(new IMAP_FetchItem(no, uid, size, data, flags, internalDate, envelope, bodystructure, fetchFlags)); } } else { break; } } if (!RemoveCmdTag(line).ToUpper().StartsWith("OK")) { throw new IMAP_ClientException(line); } return fetchItems.ToArray(); }
/// <summary> /// Gets specified folder quota info. Throws Exception if server doesn't support QUOTA. /// </summary> /// <param name="folder">Folder name.</param> /// <returns>Returns specified folder quota info.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when IMAP client is not connected and authenticated.</exception> public IMAP_Quota GetFolderQuota(string folder) { /* RFC 2087 4.3. GETQUOTAROOT Command Arguments: mailbox name Data: untagged responses: QUOTAROOT, QUOTA Result: OK - getquota completed NO - getquota error: no such mailbox, permission denied BAD - command unknown or arguments invalid The GETQUOTAROOT command takes the name of a mailbox and returns the list of quota roots for the mailbox in an untagged QUOTAROOT response. For each listed quota root, it also returns the quota root's resource usage and limits in an untagged QUOTA response. Example: C: A003 GETQUOTAROOT INBOX S: * QUOTAROOT INBOX "" S: * QUOTA "" (STORAGE 10 512) S: A003 OK Getquota completed */ if (this.IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (!this.IsConnected) { throw new InvalidOperationException("You must connect first."); } if (!this.IsAuthenticated) { throw new InvalidOperationException("The command is only valid in authenticated state."); } IMAP_Quota retVal = null; // Ensure that we send right separator to server, we accept both \ and /. folder = folder.Replace('\\', this.PathSeparator).Replace('/', this.PathSeparator); string line = GetNextCmdTag() + " GETQUOTAROOT " + TextUtils.QuoteString(Core.Encode_IMAP_UTF7_String(folder)); int countWritten = this.TcpStream.WriteLine(line); LogAddWrite(countWritten, line); // Must get lines with * and cmdTag + OK or cmdTag BAD/NO. while (true) { line = this.ReadLine(); if (line.StartsWith("*")) { // Get rid of * line = line.Substring(1).Trim(); if (line.ToUpper().StartsWith("QUOTAROOT")) { // Skip QUOTAROOT } else if (line.ToUpper().StartsWith("QUOTA")) { StringReader r = new StringReader(line); // Skip QUOTA word r.ReadWord(); string qoutaRootName = r.ReadWord(); long storage = -1; long maxStorage = -1; long messages = -1; long maxMessages = -1; string limits = r.ReadParenthesized(); r = new StringReader(limits); while (r.Available > 0) { string limitName = r.ReadWord(); // STORAGE usedBytes maximumAllowedBytes if (limitName.ToUpper() == "STORAGE") { storage = Convert.ToInt64(r.ReadWord()); maxStorage = Convert.ToInt64(r.ReadWord()); } // STORAGE messagesCount maximumAllowedMessages else if (limitName.ToUpper() == "MESSAGE") { messages = Convert.ToInt64(r.ReadWord()); maxMessages = Convert.ToInt64(r.ReadWord()); } } retVal = new IMAP_Quota(qoutaRootName, messages, maxMessages, storage, maxStorage); } } else { break; } } line = RemoveCmdTag(line); if (!line.ToUpper().StartsWith("OK")) { throw new IMAP_ClientException(line); } return retVal; }
/// <summary> /// Starts parsing FETCH response. /// </summary> /// <param name="imap">IMAP cleint.</param> /// <param name="line">Initial FETCH response line.</param> /// <param name="callback">Callback to be called when fetch completed.</param> /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>line</b> or <b>callback</b> is null reference.</exception> internal void ParseAsync(IMAP_Client imap,string line,EventHandler<EventArgs<Exception>> callback) { if(imap == null){ throw new ArgumentNullException("imap"); } if(line == null){ throw new ArgumentNullException("line"); } if(callback == null){ throw new ArgumentNullException("callback"); } /* RFC 3501 7.4.2. FETCH Response. Example: S: * 23 FETCH (FLAGS (\Seen) RFC822.SIZE 44827) */ StringReader r = new StringReader(line); // Eat '*' r.ReadWord(); // Parse seqNo m_MsgSeqNo = Convert.ToInt32(r.ReadWord()); // Eat 'FETCH' r.ReadWord(); // Eat '(', if list of fetch data-items. r.ReadToFirstChar(); if(r.StartsWith("(")){ r.ReadSpecifiedLength(1); } ParseDataItems(imap,r,callback); }
/// <summary> /// Reads IMAP string-literal/string/astring/nstring/utf8-quoted from string reader. /// </summary> /// <param name="reader">String reader.</param> /// <returns>Returns IMAP string.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>reader</b> is null reference.</exception> public static string ReadString(StringReader reader) { if(reader == null){ throw new ArgumentNullException("reader"); } reader.ReadToFirstChar(); // We have string-literal. if(reader.SourceString.StartsWith("{")){ int literalSize = Convert.ToInt32(reader.ReadParenthesized()); // Literal has CRLF ending, skip it. reader.ReadSpecifiedLength(2); return reader.ReadSpecifiedLength(literalSize); } // utf8-quoted old rfc 5738 else if(reader.StartsWith("*\"")){ reader.ReadSpecifiedLength(1); return reader.ReadWord(); } // string/astring/nstring else{ string word = reader.ReadWord(); // nstring if(string.Equals(word,"NIL",StringComparison.InvariantCultureIgnoreCase)){ return null; } return word; } }
/// <summary> /// Gets current working directory in the sFTP server. /// </summary> /// <returns>Returns current working directory.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when FTP client is not connected.</exception> /// <exception cref="FTP_ClientException">Is raised when FTP server returns error.</exception> public string GetCurrentDir() { if(this.IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(!this.IsConnected){ throw new InvalidOperationException("You must connect first."); } WriteLine("PWD"); string[] response = ReadResponse(); if(!response[0].StartsWith("2")){ throw new FTP_ClientException(response[0]); } StringReader r = new StringReader(response[0]); // Skip status code. r.ReadWord(); return r.ReadWord(); }
/// <summary> /// Parses rfc 2822 datetime. /// </summary> /// <param name="date">Date string.</param> /// <returns></returns> public static DateTime ParseDate(string date) { /* Rfc 2822 3.3. Date and Time Specification. date-time = [ day-of-week "," ] date FWS time [CFWS] date = day month year time = hour ":" minute [ ":" second ] FWS zone */ /* IMAP date format. date-time = date FWS time [CFWS] date = day-month-year time = hour ":" minute [ ":" second ] FWS zone */ // zone = (( "+" / "-" ) 4DIGIT) //--- Replace timezone constants -------// /* UT -0000 GMT -0000 EDT -0400 EST -0500 CDT -0500 CST -0600 MDT -0600 MST -0700 PDT -0700 PST -0800 BST +0100 British Summer Time */ date = date.ToLower(); date = date.Replace("ut","-0000"); date = date.Replace("gmt","-0000"); date = date.Replace("edt","-0400"); date = date.Replace("est","-0500"); date = date.Replace("cdt","-0500"); date = date.Replace("cst","-0600"); date = date.Replace("mdt","-0600"); date = date.Replace("mst","-0700"); date = date.Replace("pdt","-0700"); date = date.Replace("pst","-0800"); date = date.Replace("bst","+0100"); //----------------------------------------// //--- Replace month constants ---// date = date.Replace("jan","01"); date = date.Replace("feb","02"); date = date.Replace("mar","03"); date = date.Replace("apr","04"); date = date.Replace("may","05"); date = date.Replace("jun","06"); date = date.Replace("jul","07"); date = date.Replace("aug","08"); date = date.Replace("sep","09"); date = date.Replace("oct","10"); date = date.Replace("nov","11"); date = date.Replace("dec","12"); //-------------------------------// // If date contains optional "day-of-week,", remove it if(date.IndexOf(',') > -1){ date = date.Substring(date.IndexOf(',') + 1); } // Remove () from date. "Mon, 13 Oct 2003 20:50:57 +0300 (EEST)" if(date.IndexOf(" (") > -1){ date = date.Substring(0,date.IndexOf(" (")); } int year = 1900; int month = 1; int day = 1; int hour = -1; int minute = -1; int second = -1; int zoneMinutes = -1; StringReader s = new StringReader(date); //--- Pase date --------------------------------------------------------------------// try{ day = Convert.ToInt32(s.ReadWord(true,new char[]{'.','-',' '},true)); } catch{ throw new Exception("Invalid date value '" + date + "', invalid day value !"); } try{ month = Convert.ToInt32(s.ReadWord(true,new char[]{'.','-',' '},true)); } catch{ throw new Exception("Invalid date value '" + date + "', invalid month value !"); } try{ year = Convert.ToInt32(s.ReadWord(true,new char[]{'.','-',' '},true)); } catch{ throw new Exception("Invalid date value '" + date + "', invalid year value !"); } //----------------------------------------------------------------------------------// //--- Parse time -------------------------------------------------------------------// // Time is optional, so parse it if its included. if(s.Available > 0){ try{ hour = Convert.ToInt32(s.ReadWord(true,new char[]{':'},true)); } catch{ throw new Exception("Invalid date value '" + date + "', invalid hour value !"); } try{ minute = Convert.ToInt32(s.ReadWord(true,new char[]{':'},false)); } catch{ throw new Exception("Invalid date value '" + date + "', invalid minute value !"); } s.ReadToFirstChar(); if(s.StartsWith(":")){ s.ReadSpecifiedLength(1); try{ string secondString = s.ReadWord(true,new char[]{' '},true); // Milli seconds specified, remove them. if(secondString.IndexOf('.') > -1){ secondString = secondString.Substring(0,secondString.IndexOf('.')); } second = Convert.ToInt32(secondString); } catch{ throw new Exception("Invalid date value '" + date + "', invalid second value !"); } } s.ReadToFirstChar(); if(s.Available > 3){ string timezone = s.SourceString.Replace(":",""); if(timezone.StartsWith("+") || timezone.StartsWith("-")){ bool utc_add_time = timezone.StartsWith("+"); // Remove +/- sign timezone = timezone.Substring(1); // padd time zone to 4 symbol. For example 200, will be 0200. while(timezone.Length < 4){ timezone = "0" + timezone; } try{ // time zone format hours|minutes int h = Convert.ToInt32(timezone.Substring(0,2)); int m = Convert.ToInt32(timezone.Substring(2)); if(utc_add_time){ zoneMinutes = 0 - ((h * 60) + m); } else{ zoneMinutes = (h * 60) + m; } } catch{ // Just skip time zone, if can't parse } } } } //---------------------------------------------------------------------------------// // Convert time to UTC if(hour != -1 && minute != -1 && second != -1){ DateTime d = new DateTime(year,month,day,hour,minute,second).AddMinutes(zoneMinutes); return new DateTime(d.Year,d.Month,d.Day,d.Hour,d.Minute,d.Second,DateTimeKind.Utc).ToLocalTime(); } else{ return new DateTime(year,month,day); } }
/// <summary> /// Fetches specifes messages specified fetch items. /// </summary> /// <param name="sequence_set">IMAP sequence-set.</param> /// <param name="fetchFlags">Specifies what data to fetch from IMAP server.</param> /// <param name="setSeenFlag">If true message seen flag is setted.</param> /// <param name="uidFetch">Specifies if sequence-set contains message UIDs or message numbers.</param> /// <returns></returns> public IMAP_FetchItem[] FetchMessages(IMAP_SequenceSet sequence_set,IMAP_FetchItem_Flags fetchFlags,bool setSeenFlag,bool uidFetch) { if(!m_Connected){ throw new Exception("You must connect first !"); } if(!m_Authenticated){ throw new Exception("You must authenticate first !"); } if(m_SelectedFolder.Length == 0){ throw new Exception("You must select folder first !"); } List<IMAP_FetchItem> fetchItems = new List<IMAP_FetchItem>(); //--- Construct FETCH command line -----------------------------------------------------------------------// string fetchCmdLine = "a1"; if(uidFetch){ fetchCmdLine += " UID"; } fetchCmdLine += " FETCH " + sequence_set.ToSequenceSetString() + " (UID"; // FLAGS if((fetchFlags & IMAP_FetchItem_Flags.MessageFlags) != 0){ fetchCmdLine += " FLAGS"; } // RFC822.SIZE if((fetchFlags & IMAP_FetchItem_Flags.Size) != 0){ fetchCmdLine += " RFC822.SIZE"; } // INTERNALDATE if((fetchFlags & IMAP_FetchItem_Flags.InternalDate) != 0){ fetchCmdLine += " INTERNALDATE"; } // ENVELOPE if((fetchFlags & IMAP_FetchItem_Flags.Envelope) != 0){ fetchCmdLine += " ENVELOPE"; } // BODYSTRUCTURE if((fetchFlags & IMAP_FetchItem_Flags.BodyStructure) != 0){ fetchCmdLine += " BODYSTRUCTURE"; } // BODY[] or BODY.PEEK[] if((fetchFlags & IMAP_FetchItem_Flags.Message) != 0){ if(setSeenFlag){ fetchCmdLine += " BODY[]"; } else{ fetchCmdLine += " BODY.PEEK[]"; } } // BODY[HEADER] or BODY.PEEK[HEADER] ---> This needed only if full message isn't requested. if((fetchFlags & IMAP_FetchItem_Flags.Message) == 0 && (fetchFlags & IMAP_FetchItem_Flags.Header) != 0){ if(setSeenFlag){ fetchCmdLine += " BODY[HEADER]"; } else{ fetchCmdLine += " BODY.PEEK[HEADER]"; } } //--------------------------------------------------------------------------------------------------------// fetchCmdLine += ")"; // Send fetch command line to server m_pSocket.WriteLine(fetchCmdLine); // Must get lines with * and cmdTag + OK or cmdTag BAD/NO string reply = m_pSocket.ReadLine(50000); // Read multiline response while(reply.StartsWith("*")){ // Fetch may return status response there, skip them if(IsStatusResponse(reply)){ // Read next fetch item or server response reply = m_pSocket.ReadLine(50000); continue; } int no = 0; int uid = 0; int size = 0; byte[] data = null; IMAP_MessageFlags flags = IMAP_MessageFlags.Recent; string envelope = ""; string bodystructure = ""; string internalDate = ""; // Remove * reply = reply.Substring(1).TrimStart(); // Get message number no = Convert.ToInt32(reply.Substring(0,reply.IndexOf(" "))); // Get rid of FETCH and parse params. Reply:* 1 FETCH (UID 12 BODY[] ...) reply = reply.Substring(reply.IndexOf("FETCH (") + 7); StringReader r = new StringReader(reply); // Loop fetch result fields while(r.Available > 0){ r.ReadToFirstChar(); // Fetch command closing ) parenthesis if(r.SourceString == ")"){ break; } #region UID <value> // UID <value> else if(r.StartsWith("UID",false)){ // Remove UID word from reply r.ReadSpecifiedLength("UID".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if(word == null){ throw new Exception("IMAP server didn't return UID <value> !"); } else{ uid = Convert.ToInt32(word); } } #endregion #region RFC822.SIZE <value> // RFC822.SIZE <value> else if(r.StartsWith("RFC822.SIZE",false)){ // Remove RFC822.SIZE word from reply r.ReadSpecifiedLength("RFC822.SIZE".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if(word == null){ throw new Exception("IMAP server didn't return RFC822.SIZE <value> !"); } else{ try{ size = Convert.ToInt32(word); } catch{ throw new Exception("IMAP server returned invalid RFC822.SIZE <value> '" + word + "' !"); } } } #endregion #region INTERNALDATE <value> // INTERNALDATE <value> else if(r.StartsWith("INTERNALDATE",false)){ // Remove INTERNALDATE word from reply r.ReadSpecifiedLength("INTERNALDATE".Length); r.ReadToFirstChar(); // Read <value> string word = r.ReadWord(); if(word == null){ throw new Exception("IMAP server didn't return INTERNALDATE <value> !"); } else{ internalDate = word; } } #endregion #region ENVELOPE (<envelope-string>) else if(r.StartsWith("ENVELOPE",false)){ // Remove ENVELOPE word from reply r.ReadSpecifiedLength("ENVELOPE".Length); r.ReadToFirstChar(); /* Handle string literals {count-to-read}<CRLF>data(length = count-to-read). (string can be quoted string or literal) Loop while get envelope,invalid response or timeout. */ while(true){ try{ envelope = r.ReadParenthesized(); break; } catch(Exception x){ string s = r.ReadToEnd(); /* partial_envelope {count-to-read} Example: ENVELOPE ("Mon, 03 Apr 2006 10:10:10 GMT" {35} */ if(s.EndsWith("}")){ // Get partial envelope and append it back to reader r.AppenString(s.Substring(0,s.LastIndexOf('{'))); // Read remaining envelope and append it to reader int countToRead = Convert.ToInt32(s.Substring(s.LastIndexOf('{') + 1,s.LastIndexOf('}') - s.LastIndexOf('{') - 1)); MemoryStream strm = new MemoryStream(); m_pSocket.ReadSpecifiedLength(countToRead,strm); r.AppenString(TextUtils.QuoteString(System.Text.Encoding.Default.GetString(strm.ToArray()))); // Read fetch continuing line r.AppenString(m_pSocket.ReadLine(50000)); } // Unexpected response else{ throw x; } } } } #endregion #region BODYSTRUCTURE (<bodystructure-string>) else if(r.StartsWith("BODYSTRUCTURE",false)){ // Remove BODYSTRUCTURE word from reply r.ReadSpecifiedLength("BODYSTRUCTURE".Length); r.ReadToFirstChar(); bodystructure = r.ReadParenthesized(); } #endregion #region BODY[] or BODY[HEADER] // BODY[] or BODY[HEADER] else if(r.StartsWith("BODY",false)){ if(r.StartsWith("BODY[]",false)){ // Remove BODY[] r.ReadSpecifiedLength("BODY[]".Length); } else if(r.StartsWith("BODY[HEADER]",false)){ // Remove BODY[HEADER] r.ReadSpecifiedLength("BODY[HEADER]".Length); } else{ throw new Exception("Invalid FETCH response: " + r.SourceString); } r.ReadToFirstChar(); // We must now have {<size-to-read>}, or there is error if(!r.StartsWith("{")){ throw new Exception("Invalid FETCH BODY[] or BODY[HEADER] response: " + r.SourceString); } // Read <size-to-read> int dataLength = Convert.ToInt32(r.ReadParenthesized()); // Read data MemoryStream storeStrm = new MemoryStream(dataLength); m_pSocket.ReadSpecifiedLength(dataLength,storeStrm); data = storeStrm.ToArray(); // Read fetch continuing line r.AppenString(m_pSocket.ReadLine(50000).Trim()); } #endregion #region FLAGS (<flags-list>) // FLAGS (<flags-list>) else if(r.StartsWith("FLAGS",false)){ // Remove FLAGS word from reply r.ReadSpecifiedLength("FLAGS".Length); r.ReadToFirstChar(); // Read (<flags-list>) string flagsList = r.ReadParenthesized(); if(flagsList == null){ throw new Exception("IMAP server didn't return FLAGS (<flags-list>) !"); } else{ flags = IMAP_Utils.ParseMessageFlags(flagsList); } } #endregion else{ throw new Exception("Not supported fetch reply: " + r.SourceString); } } fetchItems.Add(new IMAP_FetchItem(no,uid,size,data,flags,internalDate,envelope,bodystructure,fetchFlags)); // Read next fetch item or server response reply = m_pSocket.ReadLine(50000); } // We must get OK or otherwise there is error if(!RemoveCmdTag(reply).ToUpper().StartsWith("OK")){ if(!reply.ToUpper().StartsWith("NO")){ throw new Exception("Server returned:" + reply); } } return fetchItems.ToArray(); }