public bool ProcessEmbedded(ref RxMailMessage Message) { //raw email tracing if (isGetRawEmail) { isTraceRawEmail = true; if (RawEmailSB == null) { RawEmailSB = new StringBuilder(100000); } else { RawEmailSB.Length = 0; } } //convert received email into RxMailMessage MimeEntityReturnCode MessageMimeReturnCode = ProcessMimeEntity(Message, ""); if (isGetRawEmail) { //add raw email version to message Message.RawContent = RawEmailSB.ToString(); isTraceRawEmail = false; } if (MessageMimeReturnCode == MimeEntityReturnCode.bodyComplete || MessageMimeReturnCode == MimeEntityReturnCode.parentBoundaryEndFound) { TraceFrom("email with {0} body chars received", Message.Body.Length); return(true); } return(false); }
/// <summary> /// Gets 1 email from POP3 server and processes it. /// </summary> /// <param name="MessageNo">Email Id to be fetched from POP3 server</param> /// <param name="Message">decoded email</param> /// <returns>false: no email received or email not properly formatted</returns> public bool GetEmail(int MessageNo, out RxMailMessage Message) { Message = null; //request email, send RETRieve command to POP3 server if (!SendRetrCommand(MessageNo)) { return(false); } //prepare message, set defaults as specified in RFC 2046 //although they get normally overwritten, we have to make sure there are at least defaults Message = new RxMailMessage(); Message.ContentTransferEncoding = TransferEncoding.SevenBit; Message.TransferType = "7bit"; this.messageNo = MessageNo; //raw email tracing if (isGetRawEmail) { isTraceRawEmail = true; if (RawEmailSB == null) { RawEmailSB = new StringBuilder(100000); } else { RawEmailSB.Length = 0; } } //convert received email into RxMailMessage MimeEntityReturnCode MessageMimeReturnCode = ProcessMimeEntity(Message, ""); if (isGetRawEmail) { //add raw email version to message Message.RawContent = RawEmailSB.ToString(); isTraceRawEmail = false; } if (MessageMimeReturnCode == MimeEntityReturnCode.bodyComplete || MessageMimeReturnCode == MimeEntityReturnCode.parentBoundaryEndFound) { TraceFrom("email with {0} body chars received", Message.Body.Length); return(true); } return(false); }
/// <summary> /// Gets an email from the supplied Email Stream and processes it. /// </summary> /// <param name="sEmlPath">Stream that designates the an Email stream</param> /// <returns>RxMailMessage or null if email not properly formatted</returns> public RxMailMessage GetEmail(Stream EmailStream) { DATA_TEXT = null; EmailStreamReader = new StreamReader(EmailStream, Encoding.ASCII); RxMailMessage Message = new RxMailMessage(); Message.ContentTransferEncoding = TransferEncoding.SevenBit; Message.TransferType = "7bit"; RxMailMessage Root = new RxMailMessage(); Root.ContentTransferEncoding = TransferEncoding.SevenBit; Root.TransferType = "7bit"; MimeEntityReturnCode MessageMimeReturnCode = ProcessMimeEntity(Root, Message, ""); if (MessageMimeReturnCode == MimeEntityReturnCode.bodyComplete || MessageMimeReturnCode == MimeEntityReturnCode.parentBoundaryEndFound) { if (Root.To.Count == 0) { string sTo = Root.Headers["x-receiver"]; if (!string.IsNullOrEmpty(sTo)) { Root.To.Add(sTo); } } if (Root.From == null) { string sFrom = Root.Headers["x-sender"]; if (!string.IsNullOrEmpty(sFrom)) { Root.From = new MailAddress(sFrom); } } foreach (string key in Message.Headers) { Root.Headers[key] = Message.Headers[key]; } return(Root); } return(null); }
/// <summary> /// Gets an email from the supplied Email Stream and processes it. /// </summary> /// <param name="sEmlPath">Stream that designates the an Email stream</param> /// <returns>RxMailMessage or null if email not properly formatted</returns> public RxMailMessage GetEmail(Stream EmailStream) { EmailStreamReader = new StreamReader(EmailStream, Encoding.ASCII); //prepare message, set defaults as specified in RFC 2046 //although they get normally overwritten, we have to make sure there are at least defaults RxMailMessage Message = new RxMailMessage(); Message.ContentTransferEncoding = TransferEncoding.SevenBit; Message.TransferType = "7bit"; //convert received email into RxMailMessage MimeEntityReturnCode MessageMimeReturnCode = ProcessMimeEntity(Message, ""); if (MessageMimeReturnCode == MimeEntityReturnCode.bodyComplete || MessageMimeReturnCode == MimeEntityReturnCode.parentBoundaryEndFound) { // I've seen EML files that don't have a "To: entity but have "x-receiver:" entity set to the recipient. check and use that if need be if (Message.To.Count == 0) { // do something with string sTo = Message.Headers["x-receiver"]; if (!string.IsNullOrEmpty(sTo)) { Message.To.Add(sTo); } } // From: maybe also but never have seen it missing if (Message.From == null) { // do something with string sFrom = Message.Headers["x-sender"]; if (!string.IsNullOrEmpty(sFrom)) { Message.From = new MailAddress(sFrom); } } //TraceFrom("email with {0} body chars received", Message.Body.Length); return(Message); } return(null); }
/// <summary> /// Gets an email from the supplied Email Stream and processes it. /// </summary> /// <param name="emailStream">The email stream object</param> /// <returns> /// MailMessageParser or null if email not properly formatted /// </returns> public MailMessageParser GetEmail(Stream emailStream) { this.EmailStreamReader = new StreamReader(emailStream, Encoding.ASCII); ////prepare message, set defaults as specified in RFC 2046 MailMessageParser message = new MailMessageParser(); message.ContentTransferEncoding = TransferEncoding.SevenBit; MailMessageParser result = null; MimeEntityReturnCode messageMimeReturnCode = this.ProcessMimeEntity(message, string.Empty); if (messageMimeReturnCode == MimeEntityReturnCode.bodyComplete || messageMimeReturnCode == MimeEntityReturnCode.parentBoundaryEndFound) { if (0 == message.To.Count) { string toField = message.Headers[ServiceConstants.Mail_Message_Receiver_Header]; if (!string.IsNullOrEmpty(toField)) { message.To.Add(toField); } } if (null == message.From) { string mailFrom = message.Headers[ServiceConstants.Mail_Message_Sender_Header]; if (!string.IsNullOrEmpty(mailFrom)) { message.From = new MailAddress(mailFrom); } } result = message; } return(result); }
/// <summary> /// Check if the response line received is a parent boundary /// </summary> private bool parentBoundaryFound(string response, string parentBoundaryStart, string parentBoundaryEnd, out MimeEntityReturnCode boundaryMimeReturnCode) { boundaryMimeReturnCode = MimeEntityReturnCode.undefined; if (response==null || response.Length<2 || response[0]!='-' || response[1]!='-') { //quick test: reponse doesn't start with "--", so cannot be a separator line return false; } if (response==parentBoundaryStart) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryStartFound; return true; } else if (response==parentBoundaryEnd) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryEndFound; return true; } return false; }
/// <summary> /// Check if the response line received is a parent boundary /// </summary> private bool parentBoundaryFound(string response, string parentBoundaryStart, string parentBoundaryEnd, out MimeEntityReturnCode boundaryMimeReturnCode) { boundaryMimeReturnCode = MimeEntityReturnCode.undefined; if (response == null || response.Length < 2 || response[0] != '-' || response[1] != '-') { //quick test: reponse doesn't start with "--", so cannot be a separator line return(false); } if (response == parentBoundaryStart) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryStartFound; return(true); } else if (response == parentBoundaryEnd) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryEndFound; return(true); } return(false); }
/// <summary> /// Check if the response line received is a parent boundary /// </summary> /// <param name="response">mail response line</param> /// <param name="parentBoundaryStart"> parent boundary start identifier</param> /// <param name="parentBoundaryEnd">parent boundary end identifier</param> /// <param name="boundaryMimeReturnCode">boundary MIME return code.</param> /// <returns> /// If parent boundary found then true or false /// </returns> private static bool ParentBoundaryFound(string response, string parentBoundaryStart, string parentBoundaryEnd, out MimeEntityReturnCode boundaryMimeReturnCode) { bool result = false; boundaryMimeReturnCode = MimeEntityReturnCode.undefined; if (null == response || 2 > response.Length || '-' != response[0] || '-' != response[1]) { result = false; } else if (response == parentBoundaryStart) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryStartFound; result = true; } else if (response == parentBoundaryEnd) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryEndFound; result = true; } return result; }
private MimeEntityReturnCode ProcessDelimitedBody(RxMailMessage message, string BoundaryStart, string parentBoundaryStart, string parentBoundaryEnd) { string response = string.Empty; if (BoundaryStart.Trim() == parentBoundaryStart.Trim()) { //Mime entity boundaries have to be unique callGetEmailWarning("new boundary same as parent boundary: \'{0}\'", parentBoundaryStart); //empty this message while (readMultiLine(ref response)) { } return MimeEntityReturnCode.problem; } // MimeEntityReturnCode ReturnCode = new MimeEntityReturnCode(); do { //empty StringBuilder MimeEntitySB.Length = 0; RxMailMessage ChildPart = message.CreateChildEntity(); //recursively call MIME part processing ReturnCode = ProcessMimeEntity(ChildPart, BoundaryStart); if (ReturnCode == MimeEntityReturnCode.problem) { //it seems the received email doesn't follow the MIME specification. Stop here return MimeEntityReturnCode.problem; } //add the newly found child MIME part to the parent AddChildPartsToParent(ChildPart, message); } while (ReturnCode != MimeEntityReturnCode.parentBoundaryEndFound); //disregard all future lines until parent boundary is found or end of complete message MimeEntityReturnCode boundaryMimeReturnCode = new MimeEntityReturnCode(); bool hasParentBoundary = parentBoundaryStart.Length > 0; while (readMultiLine(ref response)) { if (hasParentBoundary && parentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, ref boundaryMimeReturnCode)) { return boundaryMimeReturnCode; } } return MimeEntityReturnCode.bodyComplete; }
/// <summary> /// Process a MIME entity /// /// A MIME entity consists of header and body. /// Separator lines in the body might mark children MIME entities /// </summary> private MimeEntityReturnCode ProcessMimeEntity(RxMailMessage message, string parentBoundaryStart) { bool hasParentBoundary = parentBoundaryStart.Length > 0; string parentBoundaryEnd = parentBoundaryStart + "--"; MimeEntityReturnCode boundaryMimeReturnCode = new MimeEntityReturnCode(); //some format fields are inherited from parent, only the default for //ContentType needs to be set here, otherwise the boundary parameter would be //inherited too ! message.SetContentTypeFields("text/plain; charset=us-ascii"); //get header //---------- string completeHeaderField = null; //consists of one start line and possibly several continuation lines string response = string.Empty; // read header lines until empty line is found (end of header) while (true) { if (! readMultiLine(ref response)) { //POP3 server has not send any more lines callGetEmailWarning("incomplete MIME entity header received", null); //empty this message while (readMultiLine(ref response)) { } System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this return MimeEntityReturnCode.problem; } if (response.Length < 1) { //empty line found => end of header if (completeHeaderField != null) { ProcessHeaderField(message, completeHeaderField); } else { //there was only an empty header. } break; } //check if there is a parent boundary in the header (wrong format!) if (hasParentBoundary && parentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, ref boundaryMimeReturnCode)) { callGetEmailWarning("MIME entity header prematurely ended by parent boundary", null); //empty this message while (readMultiLine(ref response)) { } System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this return boundaryMimeReturnCode; } //read header field //one header field can extend over one start line and multiple continuation lines //a continuation line starts with at least 1 blank (' ') or tab if (response[0] == ' ' || response[0] == '\t') { //continuation line found. if (completeHeaderField == null) { callGetEmailWarning("Email header starts with continuation line", null); //empty this message while (readMultiLine(ref response)) { } System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this return MimeEntityReturnCode.problem; } else { // append space, if needed, and continuation line if (completeHeaderField[completeHeaderField.Length - 1] != ' ') { //previous line did not end with a whitespace //need to replace CRLF with a ' ' completeHeaderField += ' ' + response.TrimStart(WhiteSpaceChars); } else { //previous line did end with a whitespace completeHeaderField += response.TrimStart(WhiteSpaceChars); } } } else { //a new header field line found if (completeHeaderField == null) { //very first field, just copy it and then check for continuation lines completeHeaderField = response; } else { //new header line found ProcessHeaderField(message, completeHeaderField); //save the beginning of the next line completeHeaderField = response; } } } //end while read header lines //process body //------------ MimeEntitySB.Length = 0; //empty StringBuilder. For speed reasons, reuse StringBuilder defined as member of class string BoundaryDelimiterLineStart = null; bool isBoundaryDefined = false; if (message.ContentType.Boundary != null) { isBoundaryDefined = true; BoundaryDelimiterLineStart = "--" + message.ContentType.Boundary; } //prepare return code for the case there is no boundary in the body boundaryMimeReturnCode = MimeEntityReturnCode.bodyComplete; //read body lines while (readMultiLine(ref response)) { //check if there is a boundary line from this entity itself in the body if (isBoundaryDefined && response.TrimEnd(null) == BoundaryDelimiterLineStart) { //boundary line found. //stop the processing here and start a delimited body processing return ProcessDelimitedBody(message, BoundaryDelimiterLineStart, parentBoundaryStart, parentBoundaryEnd); } //check if there is a parent boundary in the body if (hasParentBoundary && parentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, ref boundaryMimeReturnCode)) { //a parent boundary is found. Decode the content of the body received so far, then end this MIME entity //note that boundaryMimeReturnCode is set here, but used in the return statement break; } //process next line MimeEntitySB.Append(response + CRLF); } //a complete MIME body read //convert received US ASCII characters to .NET string (Unicode) string TransferEncodedMessage = MimeEntitySB.ToString(); bool isAttachmentSaved = false; switch (message.ContentTransferEncoding) { case TransferEncoding.SevenBit: //nothing to do saveMessageBody(message, TransferEncodedMessage); break; case TransferEncoding.Base64: //convert base 64 -> byte[] byte[] bodyBytes = System.Convert.FromBase64String(TransferEncodedMessage); message.ContentStream = new MemoryStream(bodyBytes, false); if (message.MediaMainType == "text") { //convert byte[] -> string message.Body = DecodeByteArryToString(bodyBytes, message.BodyEncoding); } else if (message.MediaMainType == "image" || message.MediaMainType == "application") { SaveAttachment(message); isAttachmentSaved = true; } break; case TransferEncoding.QuotedPrintable: saveMessageBody(message, QuotedPrintable.Decode(TransferEncodedMessage)); break; default: saveMessageBody(message, TransferEncodedMessage); break; //no need to raise a warning here, the warning was done when analising the header } if (message.ContentDisposition != null&& message.ContentDisposition.DispositionType.ToLowerInvariant() == "attachment" && (! isAttachmentSaved)) { SaveAttachment(message); isAttachmentSaved = true; } return boundaryMimeReturnCode; }
/// <summary> /// Check if the response line received is a parent boundary /// </summary> /// <param name="response">mail response line</param> /// <param name="parentBoundaryStart"> parent boundary start identifier</param> /// <param name="parentBoundaryEnd">parent boundary end identifier</param> /// <param name="boundaryMimeReturnCode">boundary MIME return code.</param> /// <returns> /// If parent boundary found then true or false /// </returns> private static bool ParentBoundaryFound(string response, string parentBoundaryStart, string parentBoundaryEnd, out MimeEntityReturnCode boundaryMimeReturnCode) { bool result = false; boundaryMimeReturnCode = MimeEntityReturnCode.undefined; if (null == response || 2 > response.Length || '-' != response[0] || '-' != response[1]) { result = false; } else if (response == parentBoundaryStart) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryStartFound; result = true; } else if (response == parentBoundaryEnd) { boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryEndFound; result = true; } return(result); }