/// <summary> /// Convert one MIME header field and update message accordingly /// </summary> /// <param name="message">Parsed message object</param> /// <param name="headerField">header field</param> private void ProcessHeaderField(MailMessageParser message, string headerField) { string headerLineType; string headerLineContent; int separatorPosition = headerField.IndexOf(ServiceConstants.COLON, StringComparison.CurrentCulture); if (0 < separatorPosition) { ////process header field type headerLineType = headerField.Substring(0, separatorPosition).ToUpperInvariant(); headerLineContent = headerField.Substring(separatorPosition + 1).Trim(whiteSpaceChars); if (string.IsNullOrEmpty(headerLineType) || string.IsNullOrEmpty(headerLineContent)) { ////mail header parts missing, exist function return; } //// add header line to headers message.Headers.Add(headerLineType, headerLineContent); switch (headerLineType) { case ServiceConstants.MailAttributes.BCC: AddMailAddresses(headerLineContent, message.Bcc); break; case ServiceConstants.MailAttributes.CC: AddMailAddresses(headerLineContent, message.CC); break; case ServiceConstants.MailAttributes.CONTENT_DESCRIPTION: message.ContentDescription = headerLineContent; break; case ServiceConstants.MailAttributes.CONTENT_DISPOSITION: message.SetContentDisposition(headerLineContent); break; case ServiceConstants.MailAttributes.CONTENT_ID: message.ContentId = headerLineContent; break; case ServiceConstants.MailAttributes.CONTENT_TRANSFER_ENCODING: message.ContentTransferEncoding = ConvertToTransferEncoding(headerLineContent); break; case ServiceConstants.MailAttributes.CONTENT_TYPE: message.SetContentTypeFields(headerLineContent); break; case ServiceConstants.MailAttributes.DATE: message.DeliveryDate = this.ConvertToDateTime(headerLineContent); break; case ServiceConstants.MailAttributes.FROM: MailAddress address = ConvertToMailAddress(headerLineContent); if (null != address) { message.From = address; } break; case ServiceConstants.MailAttributes.SENDER: message.Sender = ConvertToMailAddress(headerLineContent); break; case ServiceConstants.MailAttributes.SUBJECT: message.Subject = headerLineContent; break; case ServiceConstants.MailAttributes.TO: AddMailAddresses(headerLineContent, message.To); break; case ServiceConstants.MailAttributes.IMPORTANCE: message.MailImportance = headerLineContent; break; case ServiceConstants.MailAttributes.CATEGORIES: message.MailCategories = headerLineContent; break; case ServiceConstants.MailAttributes.RECEIVED: if (message.ReceivedDate.Year.Equals(1)) { message.ReceivedDate = this.ProcessReceivedDate(headerLineContent); } break; default: message.UnknowHeaderlines.Add(headerField); if (IsCollectHiddenHeaderLines) { AllHiddenHeaderLines.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> /// <param name="message">Mail Message.</param> /// <param name="parentBoundaryStart">The parent boundary start value</param> /// <returns>Mime entity return code for parsed mail message</returns> private MimeEntityReturnCode ProcessMimeEntity(MailMessageParser message, string parentBoundaryStart) { bool hasParentBoundary = parentBoundaryStart.Length > 0; string parentBoundaryEnd = parentBoundaryStart + ServiceConstants.HYPHEN + ServiceConstants.HYPHEN; 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 as well message.SetContentTypeFields(ServiceConstants.MAIL_CONTENT_TYPE); 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 (!this.ReadMultipleLine(out response)) { while (this.ReadMultipleLine(out response)) { continue; } return MimeEntityReturnCode.problem; } if (1 > response.Length) { ////empty line found => end of header if (completeHeaderField != null) { this.ProcessHeaderField(message, completeHeaderField); } break; } if (hasParentBoundary && ParentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, out boundaryMimeReturnCode)) { while (this.ReadMultipleLine(out response)) { continue; } 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 (ServiceConstants.SPACE == Convert.ToString(response[0], CultureInfo.InvariantCulture) || ServiceConstants.HORIZONTAL_TAB == Convert.ToString(response[0], CultureInfo.InvariantCulture)) { if (completeHeaderField == null) { while (this.ReadMultipleLine(out response)) { continue; } return MimeEntityReturnCode.problem; } else { if (ServiceConstants.SPACE != Convert.ToString(completeHeaderField[completeHeaderField.Length - 1], CultureInfo.InvariantCulture)) { completeHeaderField += ServiceConstants.SPACE + response.TrimStart(whiteSpaceChars); } else { completeHeaderField += response.TrimStart(whiteSpaceChars); } } } else { if (null == completeHeaderField) { completeHeaderField = response; } else { this.ProcessHeaderField(message, completeHeaderField); completeHeaderField = response; } } } this.mimeEntitySB.Length = 0; string boundaryDelimiterLineStart = null; bool isBoundaryDefined = false; if (null != message.ContentType.Boundary) { 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 (this.ReadMultipleLine(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 this.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 this.mimeEntitySB.Append(response + this.CRLF); } ////a complete MIME body read ////convert received US ASCII characters to .NET string (Unicode) string transferEncodedMessage = Convert.ToString(this.mimeEntitySB, CultureInfo.InvariantCulture); bool isAttachmentSaved = false; switch (message.ContentTransferEncoding) { case TransferEncoding.SevenBit: SaveMessageBody(message, transferEncodedMessage); break; case TransferEncoding.Base64: byte[] bodyBytes = System.Convert.FromBase64String(transferEncodedMessage); message.ContentStream = new MemoryStream(bodyBytes, false); if (message.MediaMainType == ServiceConstants.TEXT_MEDIA_MAIN_TYPE) { message.Body = DecodeByteArrayToString(bodyBytes, message.BodyEncoding); } else if (message.MediaMainType == ServiceConstants.IMAGE_MEDIA_MAIN_TYPE || message.MediaMainType == ServiceConstants.APPLICATION_MEDIA_MAIN_TYPE || message.MediaMainType == ServiceConstants.MESSAGE_MEDIA_MAIN_TYPE) { SaveAttachment(message); isAttachmentSaved = true; } break; case TransferEncoding.QuotedPrintable: SaveMessageBody(message, QuotedPrintable.Decode(transferEncodedMessage)); break; default: SaveMessageBody(message, transferEncodedMessage); break; } if (null != message.ContentDisposition && message.ContentDisposition.DispositionType.ToUpperInvariant() == ServiceConstants.MailAttributes.ATTACHMENT && !isAttachmentSaved) { SaveAttachment(message); isAttachmentSaved = true; } return boundaryMimeReturnCode; }
/// <summary> /// Process a MIME entity /// A MIME entity consists of header and body. /// Separator lines in the body might mark children MIME entities /// </summary> /// <param name="message">Mail Message.</param> /// <param name="parentBoundaryStart">The parent boundary start value</param> /// <returns>Mime entity return code for parsed mail message</returns> private MimeEntityReturnCode ProcessMimeEntity(MailMessageParser message, string parentBoundaryStart) { bool hasParentBoundary = parentBoundaryStart.Length > 0; string parentBoundaryEnd = parentBoundaryStart + ServiceConstants.HYPHEN + ServiceConstants.HYPHEN; 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 as well message.SetContentTypeFields(ServiceConstants.MAIL_CONTENT_TYPE); 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 (!this.ReadMultipleLine(out response)) { while (this.ReadMultipleLine(out response)) { continue; } return(MimeEntityReturnCode.problem); } if (1 > response.Length) { ////empty line found => end of header if (completeHeaderField != null) { this.ProcessHeaderField(message, completeHeaderField); } break; } if (hasParentBoundary && ParentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, out boundaryMimeReturnCode)) { while (this.ReadMultipleLine(out response)) { continue; } 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 (ServiceConstants.SPACE == Convert.ToString(response[0], CultureInfo.InvariantCulture) || ServiceConstants.HORIZONTAL_TAB == Convert.ToString(response[0], CultureInfo.InvariantCulture)) { if (completeHeaderField == null) { while (this.ReadMultipleLine(out response)) { continue; } return(MimeEntityReturnCode.problem); } else { if (ServiceConstants.SPACE != Convert.ToString(completeHeaderField[completeHeaderField.Length - 1], CultureInfo.InvariantCulture)) { completeHeaderField += ServiceConstants.SPACE + response.TrimStart(whiteSpaceChars); } else { completeHeaderField += response.TrimStart(whiteSpaceChars); } } } else { if (null == completeHeaderField) { completeHeaderField = response; } else { this.ProcessHeaderField(message, completeHeaderField); completeHeaderField = response; } } } this.mimeEntitySB.Length = 0; string boundaryDelimiterLineStart = null; bool isBoundaryDefined = false; if (null != message.ContentType.Boundary) { 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 (this.ReadMultipleLine(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(this.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 this.mimeEntitySB.Append(response + this.CRLF); } ////a complete MIME body read ////convert received US ASCII characters to .NET string (Unicode) string transferEncodedMessage = Convert.ToString(this.mimeEntitySB, CultureInfo.InvariantCulture); bool isAttachmentSaved = false; switch (message.ContentTransferEncoding) { case TransferEncoding.SevenBit: SaveMessageBody(message, transferEncodedMessage); break; case TransferEncoding.Base64: byte[] bodyBytes = System.Convert.FromBase64String(transferEncodedMessage); message.ContentStream = new MemoryStream(bodyBytes, false); if (message.MediaMainType == ServiceConstants.TEXT_MEDIA_MAIN_TYPE) { message.Body = DecodeByteArrayToString(bodyBytes, message.BodyEncoding); } else if (message.MediaMainType == ServiceConstants.IMAGE_MEDIA_MAIN_TYPE || message.MediaMainType == ServiceConstants.APPLICATION_MEDIA_MAIN_TYPE || message.MediaMainType == ServiceConstants.MESSAGE_MEDIA_MAIN_TYPE) { SaveAttachment(message); isAttachmentSaved = true; } break; case TransferEncoding.QuotedPrintable: SaveMessageBody(message, QuotedPrintable.Decode(transferEncodedMessage)); break; default: SaveMessageBody(message, transferEncodedMessage); break; } if (null != message.ContentDisposition && message.ContentDisposition.DispositionType.ToUpperInvariant() == ServiceConstants.MailAttributes.ATTACHMENT && !isAttachmentSaved) { SaveAttachment(message); isAttachmentSaved = true; } return(boundaryMimeReturnCode); }