/// <summary> /// Returns parsed IMAP SEARCH <b>sequence-set</b> key. /// </summary> /// <param name="r">String reader.</param> /// <returns>Returns parsed IMAP SEARCH <b>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_SeqSet Parse(StringReader r) { if(r == null){ throw new ArgumentNullException("r"); } r.ReadToFirstChar(); string value = r.QuotedReadToDelimiter(' '); if(value == null){ throw new ParseException("Parse error: Invalid 'sequence-set' value."); } try{ return new IMAP_Search_Key_SeqSet(IMAP_t_SeqSet.Parse(value)); } catch{ throw new ParseException("Parse error: Invalid 'sequence-set' 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 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> /// 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(); }