private void m_pTimerNoop_Elapsed(object sender, ElapsedEventArgs e) { try { lock (this.LockSynchronizer) { if (this.TCP_Client.IsConnected) { this.m_pClient.TcpStream.WriteLine("NOOP"); SmartStream.ReadLineAsyncOP op = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction.JunkAndThrowException); op.Completed += Op_Completed; if (!this.m_pClient.TcpStream.ReadLine(op, false)) { Op_Completed(op, new EventArgs <SmartStream.ReadLineAsyncOP>(op)); } } } } catch (Exception ex) { this.Disconnect(); throw new Exception(ex.Message); } }
/// <summary> /// Reads line from TCP client. /// </summary> /// <returns></returns> internal string ReadLine() { SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction.JunkAndThrowException); this.TcpClient.TcpStream.ReadLine(readLineOP, false); return(readLineOP.LineUtf8); }
/// <summary> /// Starts reading incoming command from the connected client. /// </summary> private void BeginReadCmd() { if (this.IsDisposed) { return; } try { SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException); // This event is raised only if read period-terminated opeartion completes asynchronously. readLineOP.Completed += new EventHandler <EventArgs <SmartStream.ReadLineAsyncOP> >(delegate(object sender, EventArgs <SmartStream.ReadLineAsyncOP> e) { if (ProcessCmd(readLineOP)) { BeginReadCmd(); } }); // Process incoming commands while, command reading completes synchronously. while (this.TcpStream.ReadLine(readLineOP, true)) { if (!ProcessCmd(readLineOP)) { break; } } } catch (Exception x) { OnError(x); } }
internal string ReadLine() { if (!this.TCP_Client.IsConnected) { throw new Exception("You must connect first"); } SmartStream.ReadLineAsyncOP readLineAsyncTask = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction.JunkAndThrowException); this.TCP_Client.TcpStream.ReadLine(readLineAsyncTask, false); return(readLineAsyncTask.LineUtf8); }
/// <summary> /// Assigns data line info from rea line operation. /// </summary> /// <param name="op">Read line operation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception> public void AssignFrom(SmartStream.ReadLineAsyncOP op) { if (op == null) { throw new ArgumentNullException(); } m_BytesInBuffer = op.BytesInBuffer; Array.Copy(op.Buffer, m_pLineBuffer, op.BytesInBuffer); }
/// <summary> /// Default constructor. /// </summary> /// <param name="stream">Stream from where to read body part.</param> /// <param name="boundary">Boundry ID what separates body parts.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>boundary</b> is null reference.</exception> public _MultipartReader(SmartStream stream, string boundary) { if (stream == null) { throw new ArgumentNullException("stream"); } if (boundary == null) { throw new ArgumentNullException("boundary"); } m_pStream = stream; m_Boundary = boundary; m_pReadLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.ThrowException); m_pTextPreamble = new StringBuilder(); m_pTextEpilogue = new StringBuilder(); }
private void m_pTimerNoop_Elapsed(object sender, ElapsedEventArgs e) { try{ /* Noop * Responses: +OK */ lock (this.LockSynchronizer){ m_pClient.TcpStream.WriteLine("NOOP"); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction.JunkAndThrowException); m_pClient.TcpStream.ReadLine(readLineOP, false); } } catch { } }
/// <summary> /// Completes command reading operation. /// </summary> /// <param name="op">Operation.</param> /// <returns>Returns true if server should start reading next command.</returns> private bool ProcessCmd(SmartStream.ReadLineAsyncOP op) { bool readNextCommand = true; try { // We are already disposed. if (this.IsDisposed) { return(false); } // Check errors. if (op.Error != null) { OnError(op.Error); } // Remote host shut-down(Socket.ShutDown) socket. if (op.BytesInBuffer == 0) { //LogAddText("The remote host '" + this.RemoteEndPoint.ToString() + "' shut down socket."); Dispose(); return(false); } // Log. //if (this.Server.Logger != null) //{ // this.Server.Logger.AddRead(this.ID, this.AuthenticatedUserIdentity, op.BytesInBuffer, op.LineUtf8, this.LocalEndPoint, this.RemoteEndPoint); //} //if (op.LineBytesInBuffer > 23000) return readNextCommand; //if (op.LineBytesInBuffer > 5) OnPacketReceived(Encoding.UTF8.GetString(op.Buffer, 0, op.LineBytesInBuffer)); } catch (Exception x) { OnError(x); } return(readNextCommand); }
/// <summary> /// Reads and logs specified line from connected host. /// </summary> /// <returns>Returns readed line.</returns> protected string ReadLine() { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException); this.TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; if (args.BytesInBuffer > 0) { LogAddRead(args.BytesInBuffer, line); } else { LogAddText("Remote host closed connection."); } return(line); }
/// <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 (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!IsConnected) { throw new InvalidOperationException("You must connect first."); } if (!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 = TcpStream.WriteLine(fetchCmdLine); LogAddWrite(countWritten, fetchCmdLine); // Read un-tagged response lines while we get final response line. byte[] lineBuffer = new byte[Workaround.Definitions.MaxStreamLineLength]; string line = ""; while (true) { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(lineBuffer, SizeExceededAction. JunkAndThrowException); 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 = TcpStream.ReadFixedCountString(countToRead); LogAddRead(countToRead, reply); r.AppenString(TextUtils.QuoteString(reply)); // Read fetch continuing line. 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); TcpStream.ReadFixedCount(storeStrm, dataLength); LogAddRead(dataLength, "Readed " + dataLength + " bytes."); data = storeStrm.ToArray(); // Read fetch continuing line. 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> /// Parses MIME header from the specified stream. /// </summary> /// <param name="stream">MIME header stream.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception> public void Parse(SmartStream stream) { //TODO: ���� ��������� �������! �������� ����! �� ��� ���� �������� � utf8 ����� if (stream == null) { throw new ArgumentNullException("stream"); } var headers = new List <KeyValuePair <string, byte[]> >(); var currentMemStream = new MemoryStream(); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. ThrowException); while (true) { stream.ReadLine(readLineOP, false); if (readLineOP.Error != null) { throw readLineOP.Error; } // We reached end of stream. if (readLineOP.BytesInBuffer == 0) { if (currentMemStream.Length > 0) { AddToBinaryDict(headers, currentMemStream); } m_IsModified = false; break; } // We got blank header terminator line. if (readLineOP.LineBytesInBuffer == 0) { if (currentMemStream.Length > 0) { AddToBinaryDict(headers, currentMemStream); } m_IsModified = false; break; } string line = Encoding.UTF8.GetString(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); var realBuffer = new List <byte>(); if ((line.StartsWith("From: \"") || line.StartsWith("To: \"")) && !line.EndsWith(">\r\n")) { var tmpArr = new byte[readLineOP.BytesInBuffer]; Array.Copy(readLineOP.Buffer, 0, tmpArr, 0, readLineOP.BytesInBuffer); realBuffer.AddRange(tmpArr); do { stream.ReadLine(readLineOP, false); if (readLineOP.LineBytesInBuffer == 0) { break; } line = Encoding.UTF8.GetString(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); tmpArr = new byte[readLineOP.BytesInBuffer]; Array.Copy(readLineOP.Buffer, 0, tmpArr, 0, readLineOP.BytesInBuffer); realBuffer.AddRange(tmpArr); } while (!line.EndsWith(">\r\n")); if (realBuffer.Count > 0) { line = Encoding.UTF8.GetString(realBuffer.ToArray()); } } // New header field starts. if (currentMemStream.Length == 0) { currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } // Header field continues. else if (char.IsWhiteSpace(line[0])) { currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } // Current header field closed, new starts. else { AddToBinaryDict(headers, currentMemStream); currentMemStream = new MemoryStream(); if (realBuffer.Count > 0) { currentMemStream.Write(realBuffer.ToArray(), 0, realBuffer.Count); } else { currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } } } //Process dictionary //Find content type var contentTypeHeader = headers .Where(x => x.Value != null) .Where(x => "content-type".Equals(x.Key, StringComparison.OrdinalIgnoreCase)) .Select(x => Encoding.UTF8.GetString(x.Value)) .SingleOrDefault(); var encoding = Encoding.UTF8; if (contentTypeHeader != null) { var mime = MIME_h_ContentType.Parse(contentTypeHeader); if (!string.IsNullOrEmpty(mime.Param_Charset)) { encoding = EncodingTools.GetEncodingByCodepageName(mime.Param_Charset) ?? Encoding.UTF8; } else { //Join headers var subjectRaw = headers .Where(x => x.Value != null) .Where(x => "subject".Equals(x.Key, StringComparison.OrdinalIgnoreCase)) .Select(x => x.Value) .SingleOrDefault(); //Try to detect hueristic encoding = subjectRaw != null?EncodingTools.DetectInputCodepage(subjectRaw) : Encoding.UTF8; } } foreach (var keyValuePair in headers) { Add(encoding.GetString(keyValuePair.Value)); } }
/// <summary> /// Parses header fields from stream. Stream position stays where header reading ends. /// </summary> /// <param name="stream">Stream from where to parse.</param> public void Parse(SmartStream stream) { /* Rfc 2822 2.2 Header Fields * Header fields are lines composed of a field name, followed by a colon * (":"), followed by a field body, and terminated by CRLF. A field * name MUST be composed of printable US-ASCII characters (i.e., * characters that have values between 33 and 126, inclusive), except * colon. A field body may be composed of any US-ASCII characters, * except for CR and LF. However, a field body may contain CRLF when * used in header "folding" and "unfolding" as described in section * 2.2.3. All field bodies MUST conform to the syntax described in * sections 3 and 4 of this standard. * * Rfc 2822 2.2.3 Long Header Fields * The process of moving from this folded multiple-line representation * of a header field to its single line representation is called * "unfolding". Unfolding is accomplished by simply removing any CRLF * that is immediately followed by WSP. Each header field should be * treated in its unfolded form for further syntactic and semantic * evaluation. * * Example: * Subject: aaaaa<CRLF> * <TAB or SP>aaaaa<CRLF> */ m_pHeaderFields.Clear(); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; while (line != null) { // End of header reached if (line == "") { break; } // Store current header line and read next. We need to read 1 header line to ahead, // because of multiline header fields. string headerField = line; stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; // See if header field is multiline. See comment above. while (line != null && (line.StartsWith("\t") || line.StartsWith(" "))) { headerField += line; stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; } string[] name_value = headerField.Split(new[] { ':' }, 2); // There must be header field name and value, otherwise invalid header field if (name_value.Length == 2) { Add(name_value[0] + ":", name_value[1].Trim()); } } }
/// <summary> /// This method is called after TCP client has sucessfully connected. /// </summary> protected override void OnConnected() { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); line = RemoveCmdTag(line); if (line.ToUpper().StartsWith("OK")) { // Clear path separator, so next access will get it. m_PathSeparator = '\0'; } else { throw new Exception("Server returned: " + line); } GetCapabilities(); if (Capabilities.Contains("STARTTLS") && Capabilities.Contains("LOGINDISABLED")) { //Switch to ssl StartTLS(); //get caps again GetCapabilities(); } }
/// <summary> /// Parses MIME header from the specified stream. /// </summary> /// <param name="stream">MIME header stream.</param> /// <param name="encoding">Headers fields reading encoding. If not sure, UTF-8 is recommended.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>encoding</b> is null.</exception> public void Parse(SmartStream stream, Encoding encoding) { if (stream == null) { throw new ArgumentNullException("stream"); } if (encoding == null) { throw new ArgumentNullException("encoding"); } StringBuilder currentHeader = new StringBuilder(); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[84000], SizeExceededAction.ThrowException); while (true) { stream.ReadLine(readLineOP, false); if (readLineOP.Error != null) { throw readLineOP.Error; } // We reached end of stream. else if (readLineOP.BytesInBuffer == 0) { if (currentHeader.Length > 0) { Add(currentHeader.ToString()); } m_IsModified = false; return; } // We got blank header terminator line. else if (readLineOP.LineBytesInBuffer == 0) { if (currentHeader.Length > 0) { Add(currentHeader.ToString()); } m_IsModified = false; return; } else { string line = encoding.GetString(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); // New header field starts. if (currentHeader.Length == 0) { currentHeader.Append(line); } // Header field continues. else if (char.IsWhiteSpace(line[0])) { currentHeader.Append(line); } // Current header field closed, new starts. else { Add(currentHeader.ToString()); currentHeader = new StringBuilder(); currentHeader.Append(line); } } } }
/// <summary> /// Renames specified folder. /// </summary> /// <param name="sourceFolderName">Source folder name.</param> /// <param name="destinationFolderName">Destination folder name.</param> /// <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 void RenameFolder(string sourceFolderName, string destinationFolderName) { if (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!IsConnected) { throw new InvalidOperationException("You must connect first."); } if (!IsAuthenticated) { throw new InvalidOperationException("The RENAME command is only valid in authenticated state."); } string line = GetNextCmdTag() + " RENAME " + TextUtils.QuoteString(Core.Encode_IMAP_UTF7_String(sourceFolderName)) + " " + TextUtils.QuoteString(Core.Encode_IMAP_UTF7_String(destinationFolderName)); int countWritten = TcpStream.WriteLine(line); LogAddWrite(countWritten, line); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); line = RemoveCmdTag(line); if (!line.ToUpper().StartsWith("OK")) { throw new IMAP_ClientException(line); } }
/// <summary> /// Switches IMAP connection to SSL. /// </summary> /// <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 or is authenticated or is already secure connection.</exception> public void StartTLS() { /* RFC 2595 3. IMAP STARTTLS extension. Example: C: a001 CAPABILITY S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED S: a001 OK CAPABILITY completed C: a002 STARTTLS S: a002 OK Begin TLS negotiation now <TLS negotiation, further commands are under TLS layer> */ if (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!IsConnected) { throw new InvalidOperationException("You must connect first."); } if (IsAuthenticated) { throw new InvalidOperationException( "The STARTTLS command is only valid in non-authenticated state !"); } if (IsSecureConnection) { throw new InvalidOperationException("Connection is already secure."); } string line = GetNextCmdTag() + " STARTTLS"; int countWritten = TcpStream.WriteLine(line); LogAddWrite(countWritten, line); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); line = RemoveCmdTag(line); if (!line.ToUpper().StartsWith("OK")) { throw new IMAP_ClientException(line); } SwitchToSecure(); }
/// <summary> /// Starts reading incoming command from the connected client. /// </summary> private void BeginReadCmd() { if (IsDisposed) { return; } try { SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); // This event is raised only if read period-terminated opeartion completes asynchronously. readLineOP.Completed += delegate { if (ProcessCmd(readLineOP)) { BeginReadCmd(); } }; // Process incoming commands while, command reading completes synchronously. while (TcpStream.ReadLine(readLineOP, true)) { if (!ProcessCmd(readLineOP)) { break; } } } catch (Exception x) { OnError(x); } }
private void AUTH(string cmdText) { // RFC 5321 3.1. if (m_SessionRejected) { WriteLine("503 Bad sequence of commands: Session rejected."); return; } /* RFC 4954 AUTH mechanism [initial-response] Arguments: mechanism: A string identifying a [SASL] authentication mechanism. initial-response: An optional initial client response. If present, this response MUST be encoded as described in Section 4 of [BASE64] or contain a single character "=". Restrictions: After an AUTH command has been successfully completed, no more AUTH commands may be issued in the same session. After a successful AUTH command completes, a server MUST reject any further AUTH commands with a 503 reply. The AUTH command is not permitted during a mail transaction. An AUTH command issued during a mail transaction MUST be rejected with a 503 reply. A server challenge is sent as a 334 reply with the text part containing the [BASE64] encoded string supplied by the SASL mechanism. This challenge MUST NOT contain any text other than the BASE64 encoded challenge. In SMTP, a server challenge that contains no data is defined as a 334 reply with no text part. Note that there is still a space following the reply code, so the complete response line is "334 ". If the client wishes to cancel the authentication exchange, it issues a line with a single "*". If the server receives such a response, it MUST reject the AUTH command by sending a 501 reply. */ if (IsAuthenticated) { WriteLine("503 Bad sequence of commands: you are already authenticated."); return; } if (m_pFrom != null) { WriteLine( "503 Bad sequence of commands: The AUTH command is not permitted during a mail transaction."); return; } #region Parse parameters string[] arguments = cmdText.Split(' '); if (arguments.Length > 2) { WriteLine("501 Syntax error, syntax: AUTH SP mechanism [SP initial-response] CRLF"); return; } string initialClientResponse = ""; if (arguments.Length == 2) { if (arguments[1] == "=") { // Skip. } else { try { initialClientResponse = Encoding.UTF8.GetString(Convert.FromBase64String(arguments[1])); } catch { WriteLine( "501 Syntax error: Parameter 'initial-response' value must be BASE64 or contain a single character '='."); return; } } } string mechanism = arguments[0]; #endregion if (!Authentications.ContainsKey(mechanism)) { WriteLine("501 Not supported authentication mechanism."); return; } string clientResponse = initialClientResponse; AUTH_SASL_ServerMechanism auth = Authentications[mechanism]; while (true) { string serverResponse = auth.Continue(clientResponse); // Authentication completed. if (auth.IsCompleted) { if (auth.IsAuthenticated) { m_pUser = new GenericIdentity(auth.UserName, "SASL-" + auth.Name); WriteLine("235 2.7.0 Authentication succeeded."); } else { WriteLine("535 5.7.8 Authentication credentials invalid."); } break; } // Authentication continues. else { // Send server challange. if (string.IsNullOrEmpty(serverResponse)) { WriteLine("334 "); } else { WriteLine("334 " + Convert.ToBase64String(Encoding.UTF8.GetBytes(serverResponse))); } // Read client response. SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction . JunkAndThrowException); TcpStream.ReadLine(readLineOP, false); if (readLineOP.Error != null) { throw readLineOP.Error; } clientResponse = readLineOP.LineUtf8; // Log if (Server.Logger != null) { Server.Logger.AddRead(ID, AuthenticatedUserIdentity, readLineOP.BytesInBuffer, "base64 auth-data", LocalEndPoint, RemoteEndPoint); } // Client canceled authentication. if (clientResponse == "*") { WriteLine("501 Authentication canceled."); return; } // We have base64 client response, decode it. else { try { clientResponse = Encoding.UTF8.GetString(Convert.FromBase64String(clientResponse)); } catch { WriteLine("501 Invalid client response '" + clientResponse + "'."); return; } } } } }
/// <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 (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!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", 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); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction. JunkAndThrowException); listStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; string listingType = "unix"; // Dedect listing. if (line != null) { try { StringReader r = new StringReader(line); DateTime modified = DateTime.ParseExact(r.ReadWord() + " " + r.ReadWord(), new[] {"MM-dd-yy hh:mmtt"}, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None); listingType = "win"; } catch {} } string[] winDateFormats = new[] {"M-d-yy h:mmtt"}; string[] unixFormats = new[] {"MMM d H:mm", "MMM d yyyy"}; byte[] lineBuffer = new byte[8000]; while (line != null) { // 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, DateTimeFormatInfo.InvariantInfo, 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, DateTimeFormatInfo.InvariantInfo, 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)); } } } listStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; } } #endregion return retVal.ToArray(); }
/// <summary> /// Parses header fields from stream. Stream position stays where header reading ends. /// </summary> /// <param name="stream">Stream from where to parse.</param> public void Parse(SmartStream stream) { /* Rfc 2822 2.2 Header Fields Header fields are lines composed of a field name, followed by a colon (":"), followed by a field body, and terminated by CRLF. A field name MUST be composed of printable US-ASCII characters (i.e., characters that have values between 33 and 126, inclusive), except colon. A field body may be composed of any US-ASCII characters, except for CR and LF. However, a field body may contain CRLF when used in header "folding" and "unfolding" as described in section 2.2.3. All field bodies MUST conform to the syntax described in sections 3 and 4 of this standard. Rfc 2822 2.2.3 Long Header Fields The process of moving from this folded multiple-line representation of a header field to its single line representation is called "unfolding". Unfolding is accomplished by simply removing any CRLF that is immediately followed by WSP. Each header field should be treated in its unfolded form for further syntactic and semantic evaluation. Example: Subject: aaaaa<CRLF> <TAB or SP>aaaaa<CRLF> */ m_pHeaderFields.Clear(); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; while (line != null) { // End of header reached if (line == "") { break; } // Store current header line and read next. We need to read 1 header line to ahead, // because of multiline header fields. string headerField = line; stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; // See if header field is multiline. See comment above. while (line != null && (line.StartsWith("\t") || line.StartsWith(" "))) { headerField += line; stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; } string[] name_value = headerField.Split(new[] {':'}, 2); // There must be header field name and value, otherwise invalid header field if (name_value.Length == 2) { Add(name_value[0] + ":", name_value[1].Trim()); } } }
/// <summary> /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>buffer</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> /// <exception cref="NotSupportedException">Is raised when reading not supported.</exception> public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0 || offset > buffer.Length) { throw new ArgumentException("Invalid argument 'offset' value."); } if (offset + count > buffer.Length) { throw new ArgumentException("Invalid argument 'count' value."); } if ((m_AccessMode & FileAccess.Read) == 0) { throw new NotSupportedException(); } while (true) { // Read next quoted-printable line and decode it. if (m_DecodedOffset >= m_DecodedCount) { m_DecodedOffset = 0; m_DecodedCount = 0; SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(line_buf, SizeExceededAction . ThrowException); m_pStream.ReadLine(readLineOP, false); // IO error reading line. if (readLineOP.Error != null) { throw readLineOP.Error; } // We reached end of stream. else if (readLineOP.BytesInBuffer == 0) { return(0); } // Decode quoted-printable line. else { // Process bytes. bool softLineBreak = false; int lineLength = readLineOP.LineBytesInBuffer; for (int i = 0; i < readLineOP.LineBytesInBuffer; i++) { byte b = readLineOP.Buffer[i]; // We have soft line-break. if (b == '=' && i == (lineLength - 1)) { softLineBreak = true; } // We should have =XX char. else if (b == '=') { byte b1 = readLineOP.Buffer[++i]; byte b2 = readLineOP.Buffer[++i]; string b1b2 = ((char)b1).ToString() + (char)b2; byte b1b2_num; if (byte.TryParse(b1b2, NumberStyles.HexNumber, null, out b1b2_num)) { m_pDecodedBuffer[m_DecodedCount++] = b1b2_num; } else { m_pDecodedBuffer[m_DecodedCount++] = b; m_pDecodedBuffer[m_DecodedCount++] = b1; m_pDecodedBuffer[m_DecodedCount++] = b2; } } // Normal char. else { m_pDecodedBuffer[m_DecodedCount++] = b; } } if (!softLineBreak) { m_pDecodedBuffer[m_DecodedCount++] = (byte)'\r'; m_pDecodedBuffer[m_DecodedCount++] = (byte)'\n'; } } } // We some decoded data, return it. if (m_DecodedOffset < m_DecodedCount) { int countToCopy = Math.Min(count, m_DecodedCount - m_DecodedOffset); Array.Copy(m_pDecodedBuffer, m_DecodedOffset, buffer, offset, countToCopy); m_DecodedOffset += countToCopy; return(countToCopy); } } }
/// <summary> /// Authenticates user. /// </summary> /// <param name="userName">User name.</param> /// <param name="password">Password.</param> /// <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 or is already authenticated.</exception> public void Authenticate(string userName, string password) { if (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!IsConnected) { throw new InvalidOperationException("You must connect first."); } if (IsAuthenticated) { throw new InvalidOperationException("Session is already authenticated."); } string line = GetNextCmdTag() + " LOGIN " + TextUtils.QuoteString(userName) + " " + TextUtils.QuoteString(password); int countWritten = TcpStream.WriteLine(line); LogAddWrite(countWritten, string.IsNullOrEmpty(password) ? line : line.Replace(password, "<***REMOVED***>")); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); line = RemoveCmdTag(line); if (line.ToUpper().StartsWith("OK")) { m_pAuthdUserIdentity = new GenericIdentity(userName, "login"); } else { throw new IMAP_ClientException(line); } }
/// <summary> /// Creates specified folder. /// </summary> /// <param name="folderName">Folder name. Eg. test, Inbox/SomeSubFolder. NOTE: use GetFolderSeparator() to get right folder separator.</param> /// <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 void CreateFolder(string folderName) { if (IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (!IsConnected) { throw new InvalidOperationException("You must connect first."); } if (!IsAuthenticated) { throw new InvalidOperationException("The CREATE command is only valid in authenticated state."); } // Ensure that we send right separator to server, we accept both \ and /. folderName = folderName.Replace('\\', PathSeparator).Replace('/', PathSeparator); string line = GetNextCmdTag() + " CREATE " + TextUtils.QuoteString(Core.Encode_IMAP_UTF7_String(folderName)); int countWritten = TcpStream.WriteLine(line); LogAddWrite(countWritten, line); SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } line = args.LineUtf8; LogAddRead(args.BytesInBuffer, line); line = RemoveCmdTag(line); if (!line.ToUpper().StartsWith("OK")) { throw new IMAP_ClientException(line); } }
/// <summary> /// Start operation processing. /// </summary> public void Start() { /* RFC 3501. literal = "{" number "}" CRLF *CHAR8 ; Number represents the number of CHAR8s */ // TODO: Async // TODO: Limit total command size. 64k ? // If initial command line ends with literal string, read literal string and remaining command text. if(EndsWithLiteralString(m_InitialCmdLine)){ StringBuilder cmdText = new StringBuilder(); int literalSize = GetLiteralSize(m_InitialCmdLine); // Add initial command line part to command text. cmdText.Append(RemoveLiteralSpecifier(m_InitialCmdLine)); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException); while(true){ #region Read literal string // Send "+ Continue". m_pSession.WriteLine("+ Continue."); // Read literal string. MemoryStream msLiteral = new MemoryStream(); m_pSession.TcpStream.ReadFixedCount(msLiteral,literalSize); // Log m_pSession.LogAddRead(literalSize,m_pCharset.GetString(msLiteral.ToArray())); // Add to command text as quoted string. cmdText.Append(TextUtils.QuoteString(m_pCharset.GetString(msLiteral.ToArray()))); #endregion #region Read continuing command text // Read continuing command text. m_pSession.TcpStream.ReadLine(readLineOP,false); // We have error. if(readLineOP.Error != null){ throw readLineOP.Error; } else{ string line = readLineOP.LineUtf8; // Log m_pSession.LogAddRead(readLineOP.BytesInBuffer,line); // Add command line part to command text. if(EndsWithLiteralString(line)){ cmdText.Append(RemoveLiteralSpecifier(line)); } else{ cmdText.Append(line); } // No more literal string, we are done. if(!EndsWithLiteralString(line)){ break; } else{ literalSize = GetLiteralSize(line); } } #endregion } m_CmdLine = cmdText.ToString(); } // We have no literal string, so initial cmd line is final. else{ m_CmdLine = m_InitialCmdLine; } }
/// <summary> /// Reads and logs specified line from connected host. /// </summary> /// <returns>Returns readed line.</returns> protected string ReadLine() { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. JunkAndThrowException); TcpStream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string line = args.LineUtf8; if (args.BytesInBuffer > 0) { LogAddRead(args.BytesInBuffer, line); } else { LogAddText("Remote host closed connection."); } return line; }
/// <summary> /// Default constructor. /// </summary> /// <param name="stream">Stream from where to read body part.</param> /// <param name="boundary">Boundry ID what separates body parts.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>boundary</b> is null reference.</exception> public _MultipartReader(SmartStream stream, string boundary) { if (stream == null) { throw new ArgumentNullException("stream"); } if (boundary == null) { throw new ArgumentNullException("boundary"); } m_pStream = stream; m_Boundary = boundary; m_pReadLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction.ThrowException); m_pTextPreamble = new StringBuilder(); m_pTextEpilogue = new StringBuilder(); }
/// <summary> /// Reads next continuing FETCH line and stores to fetch reader 'r'. /// </summary> /// <param name="imap">IMAP client.</param> /// <param name="r">String reader.</param> /// <param name="callback">Fetch completion callback.</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> or <b>callback</b> is null reference.</exception> private bool ReadNextFetchLine(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"); } SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[64000], SizeExceededAction.JunkAndThrowException); readLineOP.Completed += delegate(object sender, EventArgs <SmartStream.ReadLineAsyncOP> e){ try{ // Read line failed. if (readLineOP.Error != null) { callback(this, new EventArgs <Exception>(readLineOP.Error)); } else { // Log. imap.LogAddRead(readLineOP.BytesInBuffer, readLineOP.LineUtf8); // Append fetch line to fetch reader. r.AppendString(readLineOP.LineUtf8); ParseDataItems(imap, r, callback); } } catch (Exception x) { callback(this, new EventArgs <Exception>(x)); } finally{ readLineOP.Dispose(); } }; // Read line completed synchronously. if (imap.TcpStream.ReadLine(readLineOP, true)) { try{ // Read line failed. if (readLineOP.Error != null) { callback(this, new EventArgs <Exception>(readLineOP.Error)); return(true); } else { // Log. imap.LogAddRead(readLineOP.BytesInBuffer, readLineOP.LineUtf8); // Append fetch line to fetch reader. r.AppendString(readLineOP.LineUtf8); return(false); } } finally{ readLineOP.Dispose(); } } return(true); }
/// <summary> /// Parses mime entity from stream. /// </summary> /// <param name="stream">Data stream from where to read data.</param> /// <param name="toBoundary">Entity data is readed to specified boundary.</param> /// <returns>Returns false if last entity. Returns true for mulipart entity, if there are more entities.</returns> internal bool Parse(SmartStream stream, string toBoundary) { // Clear header fields m_pHeader.Clear(); m_pHeaderFieldCache.Clear(); // Parse header m_pHeader.Parse(stream); // Parse entity and child entities if any (Conent-Type: multipart/xxx...) // Multipart entity if ((ContentType & MediaType_enum.Multipart) != 0) { // There must be be boundary ID (rfc 1341 7.2.1 The Content-Type field for multipart entities requires one parameter, // "boundary", which is used to specify the encapsulation boundary.) string boundaryID = ContentType_Boundary; if (boundaryID == null) { // This is invalid message, just skip this mime entity } else { // There is one or more mime entities // Find first boundary start position SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[8000], SizeExceededAction. JunkAndThrowException); stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } string lineString = args.LineUtf8; while (lineString != null) { if (lineString.StartsWith("--" + boundaryID)) { break; } stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } lineString = args.LineUtf8; } // This is invalid entity, boundary start not found. Skip that entity. if (string.IsNullOrEmpty(lineString)) { return false; } // Start parsing child entities of this entity while (true) { // Parse and add child entity MimeEntity childEntity = new MimeEntity(); ChildEntities.Add(childEntity); // This is last entity, stop parsing if (childEntity.Parse(stream, boundaryID) == false) { break; } // else{ // There are more entities, parse them } // This entity is child of mulipart entity. // All this entity child entities are parsed, // we need to move stream position to next entity start. if (!string.IsNullOrEmpty(toBoundary)) { stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } lineString = args.LineUtf8; while (lineString != null) { if (lineString.StartsWith("--" + toBoundary)) { break; } stream.ReadLine(args, false); if (args.Error != null) { throw args.Error; } lineString = args.LineUtf8; } // Invalid boundary end, there can't be more entities if (string.IsNullOrEmpty(lineString)) { return false; } // See if last boundary or there is more. Last boundary ends with -- if (lineString.EndsWith(toBoundary + "--")) { return false; } // else{ // There are more entities return true; } } } // Singlepart entity. else { // Boundary is specified, read data to specified boundary. if (!string.IsNullOrEmpty(toBoundary)) { MemoryStream entityData = new MemoryStream(); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction . JunkAndThrowException); // Read entity data while get boundary end tag --boundaryID-- or EOS. while (true) { stream.ReadLine(readLineOP, false); if (readLineOP.Error != null) { throw readLineOP.Error; } // End of stream reached. Normally we should get boundary end tag --boundaryID--, but some x mailers won't add it, so // if we reach EOS, consider boundary closed. if (readLineOP.BytesInBuffer == 0) { // Just return data what was readed. m_EncodedData = entityData.ToArray(); return false; } // We readed a line. else { // We have boundary start/end tag or just "--" at the beginning of line. if (readLineOP.LineBytesInBuffer >= 2 && readLineOP.Buffer[0] == '-' && readLineOP.Buffer[1] == '-') { string lineString = readLineOP.LineUtf8; // We have boundary end tag, no more boundaries. if (lineString == "--" + toBoundary + "--") { m_EncodedData = entityData.ToArray(); return false; } // We have new boundary start. else if (lineString == "--" + toBoundary) { m_EncodedData = entityData.ToArray(); return true; } else { // Just skip } } // Write readed line. entityData.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } } } // Boundary isn't specified, read data to the stream end. else { MemoryStream ms = new MemoryStream(); stream.ReadAll(ms); m_EncodedData = ms.ToArray(); } } return false; }
/// <summary> /// Parses MIME header from the specified stream. /// </summary> /// <param name="stream">MIME header stream.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception> public void Parse(SmartStream stream) { //TODO: ���� ��������� �������! �������� ����! �� ��� ���� �������� � utf8 ����� if (stream == null) { throw new ArgumentNullException("stream"); } var headers = new List<KeyValuePair<string, byte[]>>(); var currentMemStream = new MemoryStream(); SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength], SizeExceededAction. ThrowException); while (true) { stream.ReadLine(readLineOP, false); if (readLineOP.Error != null) { throw readLineOP.Error; } // We reached end of stream. if (readLineOP.BytesInBuffer == 0) { if (currentMemStream.Length > 0) { AddToBinaryDict(headers, currentMemStream); } m_IsModified = false; break; } // We got blank header terminator line. if (readLineOP.LineBytesInBuffer == 0) { if (currentMemStream.Length > 0) { AddToBinaryDict(headers, currentMemStream); } m_IsModified = false; break; } string line = Encoding.UTF8.GetString(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); var realBuffer = new List<byte>(); if ((line.StartsWith("From: \"") || line.StartsWith("To: \"")) && !line.EndsWith(">\r\n")) { var tmpArr = new byte[readLineOP.BytesInBuffer]; Array.Copy(readLineOP.Buffer, 0, tmpArr, 0, readLineOP.BytesInBuffer); realBuffer.AddRange(tmpArr); do { stream.ReadLine(readLineOP, false); if (readLineOP.LineBytesInBuffer == 0) break; line = Encoding.UTF8.GetString(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); tmpArr = new byte[readLineOP.BytesInBuffer]; Array.Copy(readLineOP.Buffer, 0, tmpArr, 0, readLineOP.BytesInBuffer); realBuffer.AddRange(tmpArr); } while (!line.EndsWith(">\r\n")); if (realBuffer.Count > 0) { line = Encoding.UTF8.GetString(realBuffer.ToArray()); } } // New header field starts. if (currentMemStream.Length == 0) { currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } // Header field continues. else if (char.IsWhiteSpace(line[0])) { currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } // Current header field closed, new starts. else { AddToBinaryDict(headers, currentMemStream); currentMemStream = new MemoryStream(); if (realBuffer.Count > 0) currentMemStream.Write(realBuffer.ToArray(), 0, realBuffer.Count); else currentMemStream.Write(readLineOP.Buffer, 0, readLineOP.BytesInBuffer); } } //Process dictionary //Find content type var contentTypeHeader = headers .Where(x => x.Value != null) .Where(x => "content-type".Equals(x.Key, StringComparison.OrdinalIgnoreCase)) .Select(x => Encoding.UTF8.GetString(x.Value)) .SingleOrDefault(); var encoding = Encoding.UTF8; if (contentTypeHeader != null) { var mime = MIME_h_ContentType.Parse(contentTypeHeader); if (!string.IsNullOrEmpty(mime.Param_Charset)) { encoding = EncodingTools.GetEncodingByCodepageName(mime.Param_Charset) ?? Encoding.UTF8; } else { //Join headers var subjectRaw = headers .Where(x => x.Value != null) .Where(x => "subject".Equals(x.Key, StringComparison.OrdinalIgnoreCase)) .Select(x => x.Value) .SingleOrDefault(); //Try to detect hueristic encoding = subjectRaw != null ? EncodingTools.DetectInputCodepage(subjectRaw) : Encoding.UTF8; } } foreach (var keyValuePair in headers) { Add(encoding.GetString(keyValuePair.Value)); } }