/// <summary> /// Convert one MIME header field and update message accordingly /// </summary> private void ProcessHeaderField(RxMailMessage message, string headerField) { string headerLineType; string headerLineContent; int separatorPosition = headerField.IndexOf(':'); if (separatorPosition < 1) { // header field type not found, skip this line callGetEmailWarning("character ':' missing in header format field: '{0}'", headerField); } else { //process header field type headerLineType = headerField.Substring(0, separatorPosition).ToLowerInvariant(); headerLineContent = headerField.Substring(separatorPosition + 1).Trim(WhiteSpaceChars); if (headerLineType == "" || headerLineContent == "") { //1 of the 2 parts missing, drop the line return; } // add header line to headers message.Headers.Add(headerLineType, headerLineContent); //interpret if possible switch (headerLineType) { case "bcc": AddMailAddresses(headerLineContent, message.Bcc); break; case "cc": AddMailAddresses(headerLineContent, message.CC); break; case "content-description": message.ContentDescription = headerLineContent; break; case "content-disposition": message.ContentDisposition = new ContentDisposition(headerLineContent); break; case "content-id": message.ContentId = headerLineContent; break; case "content-transfer-encoding": message.TransferType = headerLineContent; message.ContentTransferEncoding = ConvertToTransferEncoding(headerLineContent); break; case "content-type": message.SetContentTypeFields(headerLineContent); break; case "date": message.DeliveryDate = ConvertToDateTime(headerLineContent); break; case "delivered-to": message.DeliveredTo = ConvertToMailAddress(headerLineContent); break; case "from": MailAddress address = ConvertToMailAddress(headerLineContent); if (address != null) { message.From = address; } break; case "message-id": message.MessageId = headerLineContent; break; case "mime-version": message.MimeVersion = headerLineContent; //message.BodyEncoding = new Encoding(); break; case "sender": message.Sender = ConvertToMailAddress(headerLineContent); break; case "subject": message.Subject = headerLineContent; break; case "received": //throw mail routing information away break; case "reply-to": message.ReplyTo = ConvertToMailAddress(headerLineContent); break; case "return-path": message.ReturnPath = ConvertToMailAddress(headerLineContent); break; case "to": AddMailAddresses(headerLineContent, message.To); break; default: message.UnknowHeaderlines.Add(headerField); if (isCollectUnknowHeaderLines) { AllUnknowHeaderLines.Add(headerField); } break; } } }
/// <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; //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; // read header lines until empty line is found (end of header) while (true) { if (!readMultiLine(out response)) { //POP3 server has not send any more lines callGetEmailWarning("incomplete MIME entity header received"); //empty this message while (readMultiLine(out 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, out boundaryMimeReturnCode)) { callGetEmailWarning("MIME entity header prematurely ended by parent boundary"); //empty this message while (readMultiLine(out 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"); //empty this message while (readMultiLine(out 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(out response)) { //check if there is a boundary line from this entity itself in the body if (isBoundaryDefined && response.TrimEnd() == 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, out 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); //no need to raise a warning here, the warning was done when analising the header break; } if (message.ContentDisposition != null && message.ContentDisposition.DispositionType.ToLowerInvariant() == "attachment" && !isAttachmentSaved) { SaveAttachment(message); isAttachmentSaved = true; } return(boundaryMimeReturnCode); }