Example #1
0
        /// <summary>
        /// Initializes a populated instance of the OpaqueMail.ReadOnlyMailMessage class representing the message text passed in with attachments procesed according to the attachment filter flags.
        /// </summary>
        /// <param name="messageText">The raw contents of the e-mail message.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        /// <param name="parseExtendedHeaders">Whether to populate the ExtendedHeaders object.</param>
        public ReadOnlyMailMessage(string messageText, ReadOnlyMailMessageProcessingFlags processingFlags, bool parseExtendedHeaders)
        {
            if (((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawHeaders) > 0) &&
                (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawBody) > 0)
            {
                RawMessage = messageText;
            }

            // Remember which specialized attachments to include.
            ProcessingFlags = processingFlags;

            // Fix messages whose carriage returns have been stripped.
            if (messageText.IndexOf("\r") < 0)
            {
                messageText = messageText.Replace("\n", "\r\n");
            }

            // Separate the headers for processing.
            string headers;
            int    cutoff = messageText.IndexOf("\r\n\r\n");

            if (cutoff > -1)
            {
                headers = messageText.Substring(0, cutoff);
            }
            else
            {
                headers = messageText;
            }

            // Set the raw headers property if requested.
            if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawHeaders) > 0)
            {
                RawHeaders = headers;
            }

            // Calculate the size of the message.
            Size = messageText.Length;

            // Temporary header variables to be processed by Functions.FromMailAddressString() later.
            string fromText    = "";
            string toText      = "";
            string ccText      = "";
            string bccText     = "";
            string replyToText = "";
            string subjectText = "";

            // Temporary header variables to be processed later.
            List <string> receivedChain = new List <string>();
            string        receivedText  = "";

            // Unfold any unneeded whitespace, then loop through each line of the headers.
            string[] headersList = Functions.UnfoldWhitespace(headers).Replace("\r", "").Split('\n');
            foreach (string header in headersList)
            {
                // Split header {name:value} pairs by the first colon found.
                int colonPos = header.IndexOf(":");
                if (colonPos > -1 && colonPos < header.Length - 1)
                {
                    string[] headerParts = new string[] { header.Substring(0, colonPos), header.Substring(colonPos + 1).TrimStart(new char[] { ' ' }) };
                    string   headerType  = headerParts[0].ToLower();
                    string   headerValue = headerParts[1];

                    // Set header variables for common headers.
                    if (!string.IsNullOrEmpty(headerType) && !string.IsNullOrEmpty(headerValue))
                    {
                        Headers[headerParts[0]] = headerValue;
                    }

                    switch (headerType)
                    {
                    case "cc":
                        if (ccText.Length > 0)
                        {
                            ccText += ", ";
                        }
                        ccText = headerValue;
                        break;

                    case "content-transfer-encoding":
                        ContentTransferEncoding = headerValue;
                        switch (headerValue.ToLower())
                        {
                        case "base64":
                            BodyTransferEncoding = TransferEncoding.Base64;
                            break;

                        case "quoted-printable":
                            BodyTransferEncoding = TransferEncoding.QuotedPrintable;
                            break;

                        case "7bit":
                            BodyTransferEncoding = TransferEncoding.SevenBit;
                            break;

                        case "8bit":
                            BodyTransferEncoding = TransferEncoding.EightBit;
                            break;

                        default:
                            BodyTransferEncoding = TransferEncoding.Unknown;
                            break;
                        }
                        break;

                    case "content-language":
                        ContentLanguage = headerValue;
                        break;

                    case "content-type":
                        // If multiple content-types are passed, only process the first.
                        if (string.IsNullOrEmpty(ContentType))
                        {
                            ContentType = headerValue.Trim();
                            CharSet     = Functions.ExtractMimeParameter(ContentType, "charset");
                        }
                        break;

                    case "date":
                        string dateString = headerValue;

                        // Ignore extraneous datetime information.
                        int dateStringParenthesis = dateString.IndexOf("(");
                        if (dateStringParenthesis > -1)
                        {
                            dateString = dateString.Substring(0, dateStringParenthesis - 1);
                        }

                        // Remove timezone suffix.
                        if (dateString.Substring(dateString.Length - 4, 1) == " ")
                        {
                            dateString = dateString.Substring(0, dateString.Length - 4);
                        }

                        DateTime.TryParse(dateString, out Date);
                        break;

                    case "delivered-to":
                        DeliveredTo = headerValue;
                        break;

                    case "from":
                        fromText = headerValue;
                        break;

                    case "importance":
                        Importance = headerValue;
                        break;

                    case "in-reply-to":
                        // Ignore opening and closing <> characters.
                        InReplyTo = headerValue;
                        if (InReplyTo.StartsWith("<"))
                        {
                            InReplyTo = InReplyTo.Substring(1);
                        }
                        if (InReplyTo.EndsWith(">"))
                        {
                            InReplyTo = InReplyTo.Substring(0, InReplyTo.Length - 1);
                        }
                        break;

                    case "message-id":
                        // Ignore opening and closing <> characters.
                        MessageId = headerValue;
                        if (MessageId.StartsWith("<"))
                        {
                            MessageId = MessageId.Substring(1);
                        }
                        if (MessageId.EndsWith(">"))
                        {
                            MessageId = MessageId.Substring(0, MessageId.Length - 1);
                        }
                        break;

                    case "received":
                    case "x-received":
                        if (!string.IsNullOrEmpty(receivedText))
                        {
                            receivedChain.Add(receivedText);
                        }

                        receivedText = headerValue;
                        break;

                    case "replyto":
                    case "reply-to":
                        replyToText = headerValue;
                        break;

                    case "return-path":
                        // Ignore opening and closing <> characters.
                        ReturnPath = headerValue;
                        if (ReturnPath.StartsWith("<"))
                        {
                            ReturnPath = ReturnPath.Substring(1);
                        }
                        if (ReturnPath.EndsWith(">"))
                        {
                            ReturnPath = ReturnPath.Substring(0, ReturnPath.Length - 1);
                        }
                        break;

                    case "sender":
                    case "x-sender":
                        if (headerValue.Length > 0)
                        {
                            MailAddressCollection senderCollection = Functions.FromMailAddressString(headerValue);
                            if (senderCollection.Count > 0)
                            {
                                this.Sender = senderCollection[0];
                            }
                        }
                        break;

                    case "subject":
                        subjectText = headerValue;
                        break;

                    case "to":
                        if (toText.Length > 0)
                        {
                            toText += ", ";
                        }
                        toText += headerValue;
                        break;

                    case "x-priority":
                        switch (headerValue.ToUpper())
                        {
                        case "LOW":
                            Priority = MailPriority.Low;
                            break;

                        case "NORMAL":
                            Priority = MailPriority.Normal;
                            break;

                        case "HIGH":
                            Priority = MailPriority.High;
                            break;
                        }
                        break;

                    case "x-subject-encryption":
                        bool.TryParse(headerValue, out SubjectEncryption);
                        break;

                    default:
                        break;
                    }

                    // Set header variables for advanced headers.
                    if (parseExtendedHeaders)
                    {
                        ExtendedProperties = new ExtendedProperties();

                        switch (headerType)
                        {
                        case "acceptlanguage":
                        case "accept-language":
                            ExtendedProperties.AcceptLanguage = headerValue;
                            break;

                        case "authentication-results":
                            ExtendedProperties.AuthenticationResults = headerValue;
                            break;

                        case "bounces-to":
                        case "bounces_to":
                            ExtendedProperties.BouncesTo = headerValue;
                            break;

                        case "content-description":
                            ExtendedProperties.ContentDescription = headerValue;
                            break;

                        case "dispositionnotificationto":
                        case "disposition-notification-to":
                            ExtendedProperties.DispositionNotificationTo = headerValue;
                            break;

                        case "dkim-signature":
                        case "domainkey-signature":
                        case "x-google-dkim-signature":
                            ExtendedProperties.DomainKeySignature = headerValue;
                            break;

                        case "domainkey-status":
                            ExtendedProperties.DomainKeyStatus = headerValue;
                            break;

                        case "errors-to":
                            ExtendedProperties.ErrorsTo = headerValue;
                            break;

                        case "list-unsubscribe":
                        case "x-list-unsubscribe":
                            ExtendedProperties.ListUnsubscribe = headerValue;
                            break;

                        case "mailer":
                        case "x-mailer":
                            ExtendedProperties.Mailer = headerValue;
                            break;

                        case "organization":
                        case "x-originator-org":
                        case "x-originatororg":
                        case "x-organization":
                            ExtendedProperties.OriginatorOrg = headerValue;
                            break;

                        case "original-messageid":
                        case "x-original-messageid":
                            ExtendedProperties.OriginalMessageId = headerValue;
                            break;

                        case "originating-email":
                        case "x-originating-email":
                            ExtendedProperties.OriginatingEmail = headerValue;
                            break;

                        case "precedence":
                            ExtendedProperties.Precedence = headerValue;
                            break;

                        case "received-spf":
                            ExtendedProperties.ReceivedSPF = headerValue;
                            break;

                        case "references":
                            ExtendedProperties.References = headerValue;
                            break;

                        case "resent-date":
                            string dateString = headerValue;

                            // Ignore extraneous datetime information.
                            int dateStringParenthesis = dateString.IndexOf("(");
                            if (dateStringParenthesis > -1)
                            {
                                dateString = dateString.Substring(0, dateStringParenthesis - 1);
                            }

                            // Remove timezone suffix.
                            if (dateString.Substring(dateString.Length - 4) == " ")
                            {
                                dateString = dateString.Substring(0, dateString.Length - 4);
                            }

                            DateTime.TryParse(dateString, out ExtendedProperties.ResentDate);
                            break;

                        case "resent-from":
                            ExtendedProperties.ResentFrom = headerValue;
                            break;

                        case "resent-message-id":
                            ExtendedProperties.ResentMessageID = headerValue;
                            break;

                        case "thread-index":
                            ExtendedProperties.ThreadIndex = headerValue;
                            break;

                        case "thread-topic":
                            ExtendedProperties.ThreadTopic = headerValue;
                            break;

                        case "user-agent":
                        case "useragent":
                            ExtendedProperties.UserAgent = headerValue;
                            break;

                        case "x-auto-response-suppress":
                            ExtendedProperties.AutoResponseSuppress = headerValue;
                            break;

                        case "x-campaign":
                        case "x-campaign-id":
                        case "x-campaignid":
                        case "x-mllistcampaign":
                        case "x-rpcampaign":
                            ExtendedProperties.CampaignID = headerValue;
                            break;

                        case "x-delivery-context":
                            ExtendedProperties.DeliveryContext = headerValue;
                            break;

                        case "x-maillist-id":
                            ExtendedProperties.MailListId = headerValue;
                            break;

                        case "x-msmail-priority":
                            ExtendedProperties.MSMailPriority = headerValue;
                            break;

                        case "x-originalarrivaltime":
                        case "x-original-arrival-time":
                            dateString = headerValue;

                            // Ignore extraneous datetime information.
                            dateStringParenthesis = dateString.IndexOf("(");
                            if (dateStringParenthesis > -1)
                            {
                                dateString = dateString.Substring(0, dateStringParenthesis - 1);
                            }

                            // Remove timezone suffix.
                            if (dateString.Substring(dateString.Length - 4) == " ")
                            {
                                dateString = dateString.Substring(0, dateString.Length - 4);
                            }

                            DateTime.TryParse(dateString, out ExtendedProperties.OriginalArrivalTime);
                            break;

                        case "x-originating-ip":
                            ExtendedProperties.OriginatingIP = headerValue;
                            break;

                        case "x-rcpt-to":
                            if (headerValue.Length > 1)
                            {
                                ExtendedProperties.RcptTo = headerValue.Substring(1, headerValue.Length - 2);
                            }
                            break;

                        case "x-csa-complaints":
                        case "x-complaints-to":
                        case "x-reportabuse":
                        case "x-report-abuse":
                        case "x-mail_abuse_inquiries":
                            ExtendedProperties.ReportAbuse = headerValue;
                            break;

                        case "x-spam-score":
                            ExtendedProperties.SpamScore = headerValue;
                            break;

                        default:
                            break;
                        }
                    }
                }
            }

            // Track all Received and X-Received headers.
            if (!string.IsNullOrEmpty(receivedText))
            {
                receivedChain.Add(receivedText);
            }
            ReceivedChain = receivedChain.ToArray();

            // Process the body if it's passed in.
            string body = "";

            if (cutoff > -1)
            {
                body = messageText.Substring(cutoff + 2);
            }
            if (!string.IsNullOrEmpty(body))
            {
                // Set the raw body property if requested.
                if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawBody) > 0)
                {
                    RawBody = body;
                }

                // Parse body into MIME parts.
                List <MimePart> mimeParts = MimePart.ExtractMIMEParts(ContentType, CharSet, ContentTransferEncoding, body, ProcessingFlags);

                // Process each MIME part.
                if (mimeParts.Count > 0)
                {
                    // Keep track of S/MIME signing and envelope encryption.
                    bool allMimePartsSigned = true, allMimePartsEncrypted = true, allMimePartsTripleWrapped = true;

                    // Process each MIME part.
                    for (int j = 0; j < mimeParts.Count; j++)
                    {
                        MimePart mimePart = mimeParts[j];

                        int semicolon = mimePart.ContentType.IndexOf(";");
                        if (semicolon > -1)
                        {
                            string originalContentType = mimePart.ContentType;
                            mimePart.ContentType = mimePart.ContentType.Substring(0, semicolon);

                            if (mimePart.ContentType.ToUpper() == "MESSAGE/PARTIAL")
                            {
                                PartialMessageId = Functions.ExtractMimeParameter(originalContentType, "id");
                                int partialMessageNumber = 0;
                                if (int.TryParse(Functions.ExtractMimeParameter(originalContentType, "number"), out partialMessageNumber))
                                {
                                    PartialMessageNumber = partialMessageNumber;
                                }
                            }
                        }

                        // Extract any signing certificates.  If this MIME part isn't signed, the overall message isn't signed.
                        if (mimePart.SmimeSigned)
                        {
                            if (mimePart.SmimeSigningCertificates.Count > 0 && SmimeSigningCertificate == null)
                            {
                                foreach (X509Certificate2 signingCert in mimePart.SmimeSigningCertificates)
                                {
                                    if (!SmimeSigningCertificateChain.Contains(signingCert))
                                    {
                                        SmimeSigningCertificateChain.Add(signingCert);
                                        SmimeSigningCertificate = signingCert;
                                    }
                                }
                            }
                        }
                        else
                        {
                            allMimePartsSigned = false;
                        }

                        // If this MIME part isn't marked as being in an encrypted envelope, the overall message isn't encrypted.
                        if (!mimePart.SmimeEncryptedEnvelope)
                        {
                            // Ignore signatures and encryption blocks when determining if everything is encrypted.
                            if (!mimePart.ContentType.StartsWith("application/pkcs7-signature") && !mimePart.ContentType.StartsWith("application/x-pkcs7-signature") && !mimePart.ContentType.StartsWith("application/pkcs7-mime"))
                            {
                                allMimePartsEncrypted = false;
                            }
                        }

                        // If this MIME part isn't marked as being triple wrapped, the overall message isn't triple wrapped.
                        if (!mimePart.SmimeTripleWrapped)
                        {
                            // Ignore signatures and encryption blocks when determining if everything is triple wrapped.
                            if (!mimePart.ContentType.StartsWith("application/pkcs7-signature") && !mimePart.ContentType.StartsWith("application/x-pkcs7-signature") && !mimePart.ContentType.StartsWith("application/pkcs7-mime"))
                            {
                                allMimePartsTripleWrapped = false;
                            }
                        }

                        // Set the default primary body, defaulting to text/html and falling back to any text/*.
                        string contentTypeToUpper = mimePart.ContentType.ToUpper();
                        if (Body.Length < 1)
                        {
                            // If the MIME part is of type text/*, set it as the intial message body.
                            if (string.IsNullOrEmpty(mimePart.ContentType) || contentTypeToUpper.StartsWith("TEXT/"))
                            {
                                IsBodyHtml  = contentTypeToUpper.StartsWith("TEXT/HTML");
                                Body        = mimePart.Body;
                                CharSet     = mimePart.CharSet;
                                ContentType = mimePart.ContentType;
                                if (mimePart.ContentTransferEncoding != TransferEncoding.Unknown)
                                {
                                    BodyTransferEncoding = mimePart.ContentTransferEncoding;
                                }
                            }
                            else
                            {
                                // If the MIME part isn't of type text/*, treat is as an attachment.
                                MemoryStream attachmentStream = new MemoryStream(mimePart.BodyBytes);
                                Attachment   attachment;
                                if (mimePart.ContentType.IndexOf("/") > -1)
                                {
                                    attachment = new Attachment(attachmentStream, mimePart.Name, mimePart.ContentType);
                                }
                                else
                                {
                                    attachment = new Attachment(attachmentStream, mimePart.Name);
                                }

                                attachment.ContentId = mimePart.ContentID;
                                Attachments.Add(attachment);
                            }
                        }
                        else
                        {
                            // If the current body isn't text/html and this is, replace the default body with the current MIME part.
                            if (!ContentType.ToUpper().StartsWith("TEXT/HTML") && contentTypeToUpper.StartsWith("TEXT/HTML"))
                            {
                                // Add the previous default body as an alternate view.
                                MemoryStream  alternateViewStream = new MemoryStream(Encoding.UTF8.GetBytes(Body));
                                AlternateView alternateView       = new AlternateView(alternateViewStream, ContentType);
                                if (BodyTransferEncoding != TransferEncoding.Unknown)
                                {
                                    alternateView.TransferEncoding = BodyTransferEncoding;
                                }
                                AlternateViews.Add(alternateView);

                                IsBodyHtml  = true;
                                Body        = mimePart.Body;
                                CharSet     = mimePart.CharSet;
                                ContentType = mimePart.ContentType;
                                if (mimePart.ContentTransferEncoding != TransferEncoding.Unknown)
                                {
                                    BodyTransferEncoding = mimePart.ContentTransferEncoding;
                                }
                            }
                            else
                            {
                                // If the MIME part isn't of type text/*, treat is as an attachment.
                                MemoryStream attachmentStream = new MemoryStream(mimePart.BodyBytes);
                                Attachment   attachment;
                                if (mimePart.ContentType.IndexOf("/") > -1)
                                {
                                    attachment = new Attachment(attachmentStream, mimePart.Name, mimePart.ContentType);
                                }
                                else
                                {
                                    attachment = new Attachment(attachmentStream, mimePart.Name);
                                }
                                attachment.ContentId = mimePart.ContentID;
                                Attachments.Add(attachment);
                            }
                        }
                    }

                    // OpaqueMail optional setting for protecting the subject.
                    if (SubjectEncryption && Body.StartsWith("Subject: "))
                    {
                        int linebreakPosition = Body.IndexOf("\r\n");
                        if (linebreakPosition > -1)
                        {
                            subjectText = Body.Substring(9, linebreakPosition - 9);
                            Body        = Body.Substring(linebreakPosition + 2);
                        }
                    }

                    // Set the message's S/MIME attributes.
                    SmimeSigned            = allMimePartsSigned;
                    SmimeEncryptedEnvelope = allMimePartsEncrypted;
                    SmimeTripleWrapped     = allMimePartsTripleWrapped;
                }
                else
                {
                    // Process non-MIME messages.
                    Body = body;
                }
            }

            // Parse String representations of addresses into MailAddress objects.
            if (fromText.Length > 0)
            {
                MailAddressCollection fromAddresses = Functions.FromMailAddressString(fromText);
                if (fromAddresses.Count > 0)
                {
                    From = fromAddresses[0];
                }
            }

            if (toText.Length > 0)
            {
                To.Clear();
                MailAddressCollection toAddresses = Functions.FromMailAddressString(toText);
                foreach (MailAddress toAddress in toAddresses)
                {
                    To.Add(toAddress);
                }

                // Add the address to the AllRecipients collection.
                foreach (MailAddress toAddress in toAddresses)
                {
                    if (!AllRecipients.Contains(toAddress.Address))
                    {
                        AllRecipients.Add(toAddress.Address);
                    }
                }
            }

            if (ccText.Length > 0)
            {
                CC.Clear();
                MailAddressCollection ccAddresses = Functions.FromMailAddressString(ccText);
                foreach (MailAddress ccAddress in ccAddresses)
                {
                    CC.Add(ccAddress);
                }

                // Add the address to the AllRecipients collection.
                foreach (MailAddress ccAddress in ccAddresses)
                {
                    if (!AllRecipients.Contains(ccAddress.Address))
                    {
                        AllRecipients.Add(ccAddress.Address);
                    }
                }
            }

            if (bccText.Length > 0)
            {
                Bcc.Clear();
                MailAddressCollection bccAddresses = Functions.FromMailAddressString(bccText);
                foreach (MailAddress bccAddress in bccAddresses)
                {
                    Bcc.Add(bccAddress);
                }

                // Add the address to the AllRecipients collection.
                foreach (MailAddress bccAddress in bccAddresses)
                {
                    if (!AllRecipients.Contains(bccAddress.Address))
                    {
                        AllRecipients.Add(bccAddress.Address);
                    }
                }
            }

            if (replyToText.Length > 0)
            {
                ReplyToList.Clear();
                MailAddressCollection replyToAddresses = Functions.FromMailAddressString(replyToText);
                foreach (MailAddress replyToAddress in replyToAddresses)
                {
                    ReplyToList.Add(replyToAddress);
                }
            }

            // Decode international strings and remove escaped linebreaks.
            Subject = Functions.DecodeMailHeader(subjectText).Replace("\r", "").Replace("\n", "");
        }
Example #2
0
        /// <summary>
        /// Decrypt the encrypted S/MIME envelope.
        /// </summary>
        /// <param name="contentType">Content Type of the outermost MIME part.</param>
        /// <param name="contentTransferEncoding">Encoding of the outermost MIME part.</param>
        /// <param name="envelopeText">The MIME envelope.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        /// <param name="depth">The nesting layer of this MIME part.</param>
        public static List <MimePart> ReturnDecryptedMimeParts(string contentType, string contentTransferEncoding, string envelopeText, ReadOnlyMailMessageProcessingFlags processingFlags, int depth)
        {
            try
            {
                // Hydrate the envelope CMS object.
                EnvelopedCms envelope = new EnvelopedCms();

                // Attempt to decrypt the envelope.
                envelope.Decode(Convert.FromBase64String(envelopeText));
                envelope.Decrypt();

                string body        = Encoding.UTF8.GetString(envelope.ContentInfo.Content);
                int    divider     = body.IndexOf("\r\n\r\n");
                string mimeHeaders = body.Substring(0, divider);
                body = body.Substring(divider + 4);

                // Divide the MIME part's headers into its components.
                string mimeContentType = "", mimeCharSet = "", mimeContentTransferEncoding = "", mimeFileName = "", mimeContentDisposition = "", mimeContentID = "";
                ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                // Recurse through embedded MIME parts.
                List <MimePart> mimeParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, body, processingFlags, depth + 1);
                foreach (MimePart mimePart in mimeParts)
                {
                    mimePart.SmimeEncryptedEnvelope = true;
                }

                return(mimeParts);
            }
            catch (Exception)
            {
                // If unable to decrypt the body, return null.
                return(null);
            }
        }
Example #3
0
 /// <summary>
 /// Initializes a populated instance of the OpaqueMail.ReadOnlyMailMessage class representing the message text passed in with attachments procesed according to the attachment filter flags.
 /// </summary>
 /// <param name="messageText">The raw contents of the e-mail message.</param>
 /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
 public ReadOnlyMailMessage(string messageText, ReadOnlyMailMessageProcessingFlags processingFlags) : this(messageText, processingFlags, false)
 {
 }
Example #4
0
        /// <summary>
        /// Extract a list of MIME parts from a multipart/* MIME encoded message.
        /// </summary>
        /// <param name="contentType">Content Type of the outermost MIME part.</param>
        /// <param name="charSet">Character set of the outermost MIME part.</param>
        /// <param name="contentTransferEncoding">Encoding of the outermost MIME part.</param>
        /// <param name="body">The outermost MIME part's contents.</param>
        /// <param name="depth">The nesting layer of this MIME part.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        public static List <MimePart> ExtractMIMEParts(string contentType, string charSet, string contentTransferEncoding, string body, ReadOnlyMailMessageProcessingFlags processingFlags, int depth)
        {
            List <MimePart> mimeParts = new List <MimePart>();

            string contentTypeToUpper = contentType.ToUpper();

            if (contentTypeToUpper.StartsWith("MULTIPART/"))
            {
                // Prepare to process each part of the multipart/* message.
                int cursor = 0;

                // Prepend and append to the body with a carriage return and linefeed for consistent boundary matching.
                body = "\r\n" + body + "\r\n";

                // Determine the outermost boundary name.
                string boundaryName       = Functions.ExtractMimeParameter(contentType, "boundary");
                int    boundaryNameLength = boundaryName.Length;

                // Variables used for record keeping with signed S/MIME parts.
                int           signatureBlock = -1;
                List <string> mimeBlocks     = new List <string>();

                cursor = body.IndexOf("\r\n--" + boundaryName, 0, StringComparison.Ordinal);
                while (cursor > -1)
                {
                    // Calculate the end boundary of the current MIME part.
                    int mimeStartPosition = cursor + boundaryNameLength + 4;
                    int mimeEndPosition   = body.IndexOf("\r\n--" + boundaryName, mimeStartPosition, StringComparison.Ordinal);
                    if (mimeEndPosition > -1 && (mimeEndPosition + boundaryNameLength + 6 <= body.Length))
                    {
                        string afterBoundaryEnd = body.Substring(mimeEndPosition + 4 + boundaryNameLength, 2);
                        if (afterBoundaryEnd == "\r\n" || afterBoundaryEnd == "--")
                        {
                            string mimeContents = body.Substring(mimeStartPosition, mimeEndPosition - mimeStartPosition);

                            // Extract the header portion of the current MIME part.
                            int    mimeDivider = mimeContents.IndexOf("\r\n\r\n");
                            string mimeHeaders, mimeBody;
                            if (mimeDivider > -1)
                            {
                                mimeHeaders = mimeContents.Substring(0, mimeDivider);
                                mimeBody    = mimeContents.Substring(mimeDivider + 4);
                            }
                            else
                            {
                                // The following is a workaround to handle malformed MIME bodies.
                                mimeHeaders = mimeContents;
                                mimeBody    = "";

                                int linePos = 0, lastLinePos = 0;
                                while (linePos > -1)
                                {
                                    lastLinePos = linePos;
                                    linePos     = mimeHeaders.IndexOf("\r\n", lastLinePos);
                                    if (linePos > -1)
                                    {
                                        string currentLine = mimeContents.Substring(lastLinePos, linePos - lastLinePos);
                                        if (currentLine.Length > 0 && currentLine.IndexOf(":") < 0)
                                        {
                                            mimeBody = mimeContents.Substring(lastLinePos + 2, mimeContents.Length - lastLinePos - 4);
                                            linePos  = -1;
                                        }
                                        else
                                        {
                                            linePos += 2;
                                        }
                                    }
                                }
                            }

                            mimeBlocks.Add(mimeContents);

                            // Divide the MIME part's headers into its components.
                            string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                            ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                            string mimeContentTypeToUpper = mimeContentType.ToUpper();
                            if (mimeContentTypeToUpper.StartsWith("MULTIPART/"))
                            {
                                // Recurse through embedded MIME parts.
                                List <MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags, depth + 1);
                                foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                {
                                    mimeParts.Add(returnedMIMEPart);
                                }
                            }
                            else
                            {
                                // Keep track of whether this MIME part's body has already been processed.
                                bool processed = false;

                                if (mimeContentTypeToUpper.StartsWith("APPLICATION/PKCS7-SIGNATURE") || mimeContentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-SIGNATURE"))
                                {
                                    // Unless a flag has been set to include this *.p7s block, exclude it from attachments.
                                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeSignedData) == 0)
                                    {
                                        processed = true;
                                    }

                                    // Remember the signature block to use for later verification.
                                    signatureBlock = mimeBlocks.Count() - 1;
                                }
                                else if (mimeContentTypeToUpper.StartsWith("APPLICATION/PKCS7-MIME") || mimeContentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-MIME"))
                                {
                                    // Unless a flag has been set to include this *.p7m block, exclude it from attachments.
                                    processed = (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) == 0;

                                    // Decrypt the MIME part and recurse through embedded MIME parts.
                                    List <MimePart> returnedMIMEParts = ReturnDecryptedMimeParts(mimeContentType, mimeContentTransferEncoding, mimeBody, processingFlags, depth + 1);
                                    if (returnedMIMEParts != null)
                                    {
                                        foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                        {
                                            mimeParts.Add(returnedMIMEPart);
                                        }
                                    }
                                    else
                                    {
                                        // If we were unable to decrypt, return this MIME part as-is.
                                        processed = false;
                                    }
                                }
                                else if (mimeContentTypeToUpper.StartsWith("APPLICATION/MS-TNEF") || mimeFileName.ToUpper() == "WINMAIL.DAT")
                                {
                                    // Process the TNEF encoded message.
                                    processed = true;
                                    TnefEncoding tnef = new TnefEncoding(Convert.FromBase64String(mimeBody));

                                    // If we were unable to extract content from this MIME, include it as an attachment.
                                    if ((tnef.Body.Length < 1 && tnef.MimeAttachments.Count < 1) || (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                                    {
                                        processed = false;
                                    }
                                    else
                                    {
                                        // Unless a flag has been set to include this winmail.dat block, exclude it from attachments.
                                        if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                                        {
                                            if (!string.IsNullOrEmpty(tnef.Body))
                                            {
                                                mimeParts.Add(new MimePart("winmail.dat", tnef.ContentType, "", "", mimeContentTransferEncoding, Encoding.UTF8.GetBytes(tnef.Body)));
                                            }
                                        }

                                        foreach (MimePart mimePart in tnef.MimeAttachments)
                                        {
                                            mimeParts.Add(mimePart);
                                        }
                                    }
                                }
                                else if (mimeContentTypeToUpper == "MESSAGE/RFC822")
                                {
                                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeNestedRFC822Messages) > 0)
                                    {
                                        // Recurse through the RFC822 container.
                                        processed = true;

                                        mimeDivider = mimeBody.IndexOf("\r\n\r\n");
                                        if (mimeDivider > -1)
                                        {
                                            mimeHeaders = Functions.UnfoldWhitespace(mimeBody.Substring(0, mimeDivider));
                                            mimeBody    = mimeBody.Substring(mimeDivider + 4);

                                            mimeContentType             = Functions.ReturnBetween(mimeHeaders, "Content-Type:", "\r\n").Trim();
                                            mimeContentTransferEncoding = Functions.ReturnBetween(mimeHeaders, "Content-Transfer-Encoding:", "\r\n").Trim();
                                            mimeCharSet = Functions.ExtractMimeParameter(mimeContentType, "charset").Replace("\"", "");

                                            List <MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags, depth + 1);
                                            foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                            {
                                                mimeParts.Add(returnedMIMEPart);
                                            }
                                        }
                                    }
                                }

                                if (!processed)
                                {
                                    // Decode and add the message to the MIME parts collection.
                                    switch (mimeContentTransferEncoding.ToLower())
                                    {
                                    case "base64":
                                        mimeBody = mimeBody.Replace("\r\n", "");
                                        if (mimeBody.Length % 4 != 0)
                                        {
                                            mimeBody += new String('=', 4 - (mimeBody.Length % 4));
                                        }

                                        mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, Convert.FromBase64String(mimeBody)));
                                        break;

                                    case "quoted-printable":
                                        mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, Functions.FromQuotedPrintable(mimeBody, mimeCharSet, null)));
                                        break;

                                    case "binary":
                                    case "7bit":
                                    case "8bit":
                                    default:
                                        mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, mimeBody));
                                        break;
                                    }
                                }
                            }
                        }
                        cursor = mimeEndPosition;
                    }
                    else
                    {
                        cursor = -1;
                    }
                }

                // If a PKCS signature was found and there's one other MIME part, verify the signature.
                if (signatureBlock > -1 && mimeBlocks.Count == 2)
                {
                    // Verify the signature and track the signing certificates.
                    X509Certificate2Collection signingCertificates;
                    if (VerifySignature(mimeBlocks[signatureBlock], mimeBlocks[1 - signatureBlock], out signingCertificates))
                    {
                        // Stamp each MIME part found so far as signed, and if relevant, triple wrapped.
                        foreach (MimePart mimePart in mimeParts)
                        {
                            mimePart.SmimeSigningCertificates = signingCertificates;

                            if (mimePart.SmimeSigned && mimePart.SmimeEncryptedEnvelope)
                            {
                                mimePart.SmimeTripleWrapped = true;
                            }

                            mimePart.SmimeSigned = true;
                        }
                    }
                }
            }
            else if (contentTypeToUpper.StartsWith("APPLICATION/MS-TNEF"))
            {
                // Process the TNEF encoded message.
                TnefEncoding tnef = new TnefEncoding(Convert.FromBase64String(body));

                // Unless a flag has been set to include this winmail.dat block, exclude it from attachments.
                if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                {
                    if (!string.IsNullOrEmpty(tnef.Body))
                    {
                        mimeParts.Add(new MimePart("winmail.dat", tnef.ContentType, "", "", "", Encoding.UTF8.GetBytes(tnef.Body)));
                    }
                }

                foreach (MimePart mimePart in tnef.MimeAttachments)
                {
                    mimeParts.Add(mimePart);
                }
            }
            else if (contentTypeToUpper.StartsWith("APPLICATION/PKCS7-MIME") || contentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-MIME"))
            {
                // Don't attempt to decrypt if this is a signed message only.
                if (contentType.IndexOf("smime-type=signed-data") < 0)
                {
                    // Unless a flag has been set to include this *.p7m block, exclude it from attachments.
                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) > 0)
                    {
                        mimeParts.Add(new MimePart("smime.p7m", contentType, "", "", "", body));
                    }

                    // Decrypt the MIME part and recurse through embedded MIME parts.
                    List <MimePart> returnedMIMEParts = ReturnDecryptedMimeParts(contentType, contentTransferEncoding, body, processingFlags, depth + 1);
                    if (returnedMIMEParts != null)
                    {
                        foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                        {
                            mimeParts.Add(returnedMIMEPart);
                        }
                    }
                    else
                    {
                        // If we were unable to decrypt the message, pass it along as-is.
                        mimeParts.Add(new MimePart(Functions.ReturnBetween(contentType + ";", "name=", ";").Replace("\"", ""), contentType, "", "", contentTransferEncoding, body));
                    }
                }
                else
                {
                    // Hydrate the signature CMS object.
                    SignedCms signedCms = new SignedCms();

                    try
                    {
                        // Attempt to decode the signature block and verify the passed in signature.
                        signedCms.Decode(Convert.FromBase64String(body));
                        signedCms.CheckSignature(true);

                        string mimeContents = Encoding.UTF8.GetString(signedCms.ContentInfo.Content);

                        int    mimeDivider = mimeContents.IndexOf("\r\n\r\n");
                        string mimeHeaders;
                        if (mimeDivider > -1)
                        {
                            mimeHeaders = mimeContents.Substring(0, mimeDivider);
                        }
                        else
                        {
                            mimeHeaders = mimeContents;
                        }

                        if (mimeHeaders.Length > 0)
                        {
                            // Extract the body portion of the current MIME part.
                            string mimeBody = mimeContents.Substring(mimeDivider + 4);

                            string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                            ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                            List <MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags, depth + 1);
                            foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                            {
                                mimeParts.Add(returnedMIMEPart);
                            }
                        }
                    }
                    catch
                    {
                        // If an exception occured, the signature could not be verified.
                    }
                }
            }
            else if (contentTypeToUpper == "MESSAGE/RFC822")
            {
                int mimeDivider = body.IndexOf("\r\n\r\n");
                if (mimeDivider > -1)
                {
                    string mimeHeaders = Functions.UnfoldWhitespace(body.Substring(0, mimeDivider));
                    string mimeBody    = body.Substring(mimeDivider + 4);

                    string mimeContentType             = Functions.ReturnBetween(mimeHeaders, "Content-Type:", "\r\n").Trim();
                    string mimeContentTransferEncoding = Functions.ReturnBetween(mimeHeaders, "Content-Transfer-Encoding:", "\r\n").Trim();
                    string mimeCharSet = Functions.ExtractMimeParameter(mimeContentType, "charset");

                    List <MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags, depth + 1);
                    foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                    {
                        mimeParts.Add(returnedMIMEPart);
                    }
                }
            }
            else
            {
                // Decode the message.
                switch (contentTransferEncoding.ToLower())
                {
                case "base64":
                    body = Functions.FromBase64(body);
                    break;

                case "quoted-printable":
                    body = Functions.FromQuotedPrintable(body, charSet, null);
                    break;

                case "binary":
                case "7bit":
                case "8bit":
                    break;
                }

                // If we're beyond the first layer, process the MIME part.  Otherwise, the message isn't MIME encoded.
                if (depth > 0)
                {
                    // Extract the headers from this MIME part.
                    string mimeHeaders;
                    int    mimeDivider = body.IndexOf("\r\n\r\n");
                    if (mimeDivider > -1)
                    {
                        mimeHeaders = body.Substring(0, mimeDivider);
                    }
                    else
                    {
                        mimeHeaders = body;
                    }

                    // Divide the MIME part's headers into its components.
                    string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                    ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                    // If this MIME part's content type is null, fall back to the overall content type.
                    if ((string.IsNullOrEmpty(mimeContentType) && !string.IsNullOrEmpty(contentType)) || (contentTypeToUpper.StartsWith("MESSAGE/PARTIAL")))
                    {
                        mimeCharSet     = charSet;
                        mimeContentType = contentType;
                    }
                    else
                    {
                        if (body.Length > (mimeDivider + 4))
                        {
                            body = body.Substring(mimeDivider + 4);
                        }
                        else
                        {
                            body = "";
                        }
                    }

                    // Add the message to the MIME parts collection.
                    mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, body));
                }
                else
                {
                    // If the content type contains a character set, extract it.
                    charSet = Functions.NormalizeCharSet(Functions.ExtractMimeParameter(contentType, "charset"));

                    int semicolonPos = contentType.IndexOf(";");
                    if (semicolonPos > -1)
                    {
                        contentType = contentType.Substring(0, semicolonPos);
                    }

                    // Add the message as-is.
                    mimeParts.Add(new MimePart("", contentType, charSet, "", contentTransferEncoding, body));
                }
            }

            return(mimeParts);
        }
Example #5
0
        /// <summary>
        /// Decrypt the encrypted S/MIME envelope.
        /// </summary>
        /// <param name="contentType">Content Type of the outermost MIME part.</param>
        /// <param name="contentTransferEncoding">Encoding of the outermost MIME part.</param>
        /// <param name="envelopeText">The MIME envelope.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        public static List<MimePart> ReturnDecryptedMimeParts(string contentType, string contentTransferEncoding, string envelopeText, ReadOnlyMailMessageProcessingFlags processingFlags)
        {
            try
            {
                // Hydrate the envelope CMS object.
                EnvelopedCms envelope = new EnvelopedCms();

                // Attempt to decrypt the envelope.
                envelope.Decode(Convert.FromBase64String(envelopeText));
                envelope.Decrypt();

                string body = Encoding.UTF8.GetString(envelope.ContentInfo.Content);
                int divider = body.IndexOf("\r\n\r\n");
                string mimeHeaders = body.Substring(0, divider);
                body = body.Substring(divider + 4);

                // Divide the MIME part's headers into its components.
                string mimeContentType = "", mimeCharSet = "", mimeContentTransferEncoding = "", mimeFileName = "", mimeContentDisposition = "", mimeContentID = "";
                ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                // Recurse through embedded MIME parts.
                List<MimePart> mimeParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, body, processingFlags);
                foreach (MimePart mimePart in mimeParts)
                    mimePart.SmimeEncryptedEnvelope = true;

                return mimeParts;
            }
            catch (Exception)
            {
                // If unable to decrypt the body, return null.
                return null;
            }
        }
Example #6
0
        /// <summary>
        /// Extract a list of MIME parts from a multipart/* MIME encoded message.
        /// </summary>
        /// <param name="contentType">Content Type of the outermost MIME part.</param>
        /// <param name="charSet">Character set of the outermost MIME part.</param>
        /// <param name="contentTransferEncoding">Encoding of the outermost MIME part.</param>
        /// <param name="body">The outermost MIME part's contents.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        public static List<MimePart> ExtractMIMEParts(string contentType, string charSet, string contentTransferEncoding, string body, ReadOnlyMailMessageProcessingFlags processingFlags)
        {
            List<MimePart> mimeParts = new List<MimePart>();

            string contentTypeToUpper = contentType.ToUpper();
            if (contentTypeToUpper.StartsWith("MULTIPART/"))
            {
                // Prepare to process each part of the multipart/* message.
                int cursor = 0;

                // Prepend the body with a carriage return and linefeed for consistent boundary matching.
                body = "\r\n" + body;

                // Determine the outermost boundary name.
                string boundaryName = Functions.ReturnBetween(contentType, "boundary=\"", "\"");
                if (boundaryName.Length < 1)
                {
                    cursor = contentType.IndexOf("boundary=", StringComparison.OrdinalIgnoreCase);
                    if (cursor > -1)
                        boundaryName = contentType.Substring(cursor + 9);
                    cursor = boundaryName.IndexOf(";");
                    if (cursor > -1)
                        boundaryName = boundaryName.Substring(0, cursor);

                    if (boundaryName.StartsWith("\"") && boundaryName.EndsWith("\""))
                        boundaryName = boundaryName.Substring(1, boundaryName.Length - 2);
                    else
                    {
                        // Remove linear whitespace from boundary names.
                        boundaryName = boundaryName.Trim();
                    }
                }
                int boundaryNameLength = boundaryName.Length;

                // Variables used for record keeping with signed S/MIME parts.
                int signatureBlock = -1;
                List<string> mimeBlocks = new List<string>();

                cursor = body.IndexOf("\r\n--" + boundaryName, 0, StringComparison.Ordinal);
                while (cursor > -1)
                {
                    // Calculate the end boundary of the current MIME part.
                    int mimeStartPosition = cursor + boundaryNameLength + 4;
                    int mimeEndPosition = body.IndexOf("\r\n--" + boundaryName, mimeStartPosition, StringComparison.Ordinal);
                    if (mimeEndPosition > -1)
                    {
                        string afterBoundaryEnd = body.Substring(mimeEndPosition + 4 + boundaryNameLength, 2);
                        if (afterBoundaryEnd == "\r\n" || afterBoundaryEnd == "--")
                        {
                            string mimeContents = body.Substring(mimeStartPosition, mimeEndPosition - mimeStartPosition);

                            // Extract the header portion of the current MIME part.
                            int mimeDivider = mimeContents.IndexOf("\r\n\r\n");
                            string mimeHeaders, mimeBody;
                            if (mimeDivider > -1)
                            {
                                mimeHeaders = mimeContents.Substring(0, mimeDivider);
                                mimeBody = mimeContents.Substring(mimeDivider + 4);
                            }
                            else
                            {
                                // The following is a workaround to handle malformed MIME bodies.
                                mimeHeaders = mimeContents;
                                mimeBody = "";

                                int linePos = 0, lastLinePos = 0;
                                while (linePos > -1)
                                {
                                    lastLinePos = linePos;
                                    linePos = mimeHeaders.IndexOf("\r\n", lastLinePos);
                                    if (linePos > -1)
                                    {
                                        string currentLine = mimeContents.Substring(lastLinePos, linePos - lastLinePos);
                                        if (currentLine.Length > 0 && currentLine.IndexOf(":") < 0)
                                        {
                                            mimeBody = mimeContents.Substring(lastLinePos + 2, mimeContents.Length - lastLinePos - 4);
                                            linePos = -1;
                                        }
                                        else
                                            linePos += 2;
                                    }
                                }
                            }

                            mimeBlocks.Add(mimeContents);

                            // Divide the MIME part's headers into its components.
                            string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                            ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                            string mimeContentTypeToUpper = mimeContentType.ToUpper();
                            if (mimeContentTypeToUpper.StartsWith("MULTIPART/"))
                            {
                                // Recurse through embedded MIME parts.
                                List<MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags);
                                foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                    mimeParts.Add(returnedMIMEPart);
                            }
                            else
                            {
                                // Keep track of whether this MIME part's body has already been processed.
                                bool processed = false;

                                if (mimeContentTypeToUpper.StartsWith("APPLICATION/PKCS7-SIGNATURE") || mimeContentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-SIGNATURE"))
                                {
                                    // Unless a flag has been set to include this *.p7s block, exclude it from attachments.
                                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeSignedData) == 0)
                                        processed = true;

                                    // Remember the signature block to use for later verification.
                                    signatureBlock = mimeBlocks.Count() - 1;
                                }
                                else if (mimeContentTypeToUpper.StartsWith("APPLICATION/PKCS7-MIME") || mimeContentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-MIME"))
                                {
                                    // Unless a flag has been set to include this *.p7m block, exclude it from attachments.
                                    processed = (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) == 0;

                                    // Decrypt the MIME part and recurse through embedded MIME parts.
                                    List<MimePart> returnedMIMEParts = ReturnDecryptedMimeParts(mimeContentType, mimeContentTransferEncoding, mimeBody, processingFlags);
                                    if (returnedMIMEParts != null)
                                    {
                                        foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                            mimeParts.Add(returnedMIMEPart);
                                    }
                                    else
                                    {
                                        // If we were unable to decrypt, return this MIME part as-is.
                                        processed = false;
                                    }
                                }
                                else if (mimeContentTypeToUpper.StartsWith("APPLICATION/MS-TNEF") || mimeFileName.ToUpper() == "WINMAIL.DAT")
                                {
                                    // Process the TNEF encoded message.
                                    processed = true;
                                    TnefEncoding tnef = new TnefEncoding(Convert.FromBase64String(mimeBody));

                                    // If we were unable to extract content from this MIME, include it as an attachment.
                                    if ((tnef.Body.Length < 1 && tnef.MimeAttachments.Count < 1) || (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                                        processed = false;
                                    else
                                    {
                                        // Unless a flag has been set to include this winmail.dat block, exclude it from attachments.
                                        if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                                        {
                                            if (!string.IsNullOrEmpty(tnef.Body))
                                                mimeParts.Add(new MimePart("winmail.dat", tnef.ContentType, "", "", mimeContentTransferEncoding, Encoding.UTF8.GetBytes(tnef.Body)));
                                        }

                                        foreach (MimePart mimePart in tnef.MimeAttachments)
                                            mimeParts.Add(mimePart);
                                    }
                                }
                                else if (mimeContentTypeToUpper == "MESSAGE/RFC822")
                                {
                                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeNestedRFC822Messages) > 0)
                                    {
                                        // Recurse through the RFC822 container.
                                        processed = true;

                                        mimeDivider = mimeBody.IndexOf("\r\n\r\n");
                                        if (mimeDivider > -1)
                                        {
                                            mimeHeaders = Functions.UnfoldWhitespace(mimeBody.Substring(0, mimeDivider));
                                            mimeBody = mimeBody.Substring(mimeDivider + 4);

                                            mimeContentType = Functions.ReturnBetween(mimeHeaders, "Content-Type:", "\r\n").Trim();
                                            mimeContentTransferEncoding = Functions.ReturnBetween(mimeHeaders, "Content-Transfer-Encoding:", "\r\n").Trim();
                                            mimeCharSet = Functions.ExtractMimeParameter(mimeContentType, "charset").Replace("\"", "");

                                            List<MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags);
                                            foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                                mimeParts.Add(returnedMIMEPart);
                                        }
                                    }
                                }

                                if (!processed)
                                {
                                    // Decode and add the message to the MIME parts collection.
                                    switch (mimeContentTransferEncoding.ToLower())
                                    {
                                        case "base64":
                                            mimeBody = mimeBody.Replace("\r\n", "");
                                            if (mimeBody.Length % 4 != 0)
                                                mimeBody += new String('=', 4 - (mimeBody.Length % 4));

                                            mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, Convert.FromBase64String(mimeBody)));
                                            break;
                                        case "quoted-printable":
                                            mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, Functions.FromQuotedPrintable(mimeBody, mimeCharSet, null)));
                                            break;
                                        case "binary":
                                        case "7bit":
                                        case "8bit":
                                        default:
                                            mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, mimeBody));
                                            break;
                                    }
                                }
                            }
                        }
                        cursor = mimeEndPosition;
                    }
                    else
                        cursor = -1;
                }

                // If a PKCS signature was found and there's one other MIME part, verify the signature.
                if (signatureBlock > -1 && mimeBlocks.Count == 2)
                {
                    // Verify the signature and track the signing certificates.
                    X509Certificate2Collection signingCertificates;
                    if (VerifySignature(mimeBlocks[signatureBlock], mimeBlocks[1 - signatureBlock], out signingCertificates))
                    {
                        // Stamp each MIME part found so far as signed, and if relevant, triple wrapped.
                        foreach (MimePart mimePart in mimeParts)
                        {
                            mimePart.SmimeSigningCertificates = signingCertificates;

                            if (mimePart.SmimeSigned && mimePart.SmimeEncryptedEnvelope)
                                mimePart.SmimeTripleWrapped = true;

                            mimePart.SmimeSigned = true;
                        }
                    }
                }
            }
            else if (contentTypeToUpper.StartsWith("APPLICATION/MS-TNEF"))
            {
                // Process the TNEF encoded message.
                TnefEncoding tnef = new TnefEncoding(Convert.FromBase64String(body));

                // Unless a flag has been set to include this winmail.dat block, exclude it from attachments.
                if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeWinMailData) > 0)
                {
                    if (!string.IsNullOrEmpty(tnef.Body))
                        mimeParts.Add(new MimePart("winmail.dat", tnef.ContentType, "", "", "", Encoding.UTF8.GetBytes(tnef.Body)));
                }

                foreach (MimePart mimePart in tnef.MimeAttachments)
                    mimeParts.Add(mimePart);
            }
            else if (contentTypeToUpper.StartsWith("APPLICATION/PKCS7-MIME") || contentTypeToUpper.StartsWith("APPLICATION/X-PKCS7-MIME"))
            {
                // Don't attempt to decrypt if this is a signed message only.
                if (contentType.IndexOf("smime-type=signed-data") < 0)
                {
                    // Unless a flag has been set to include this *.p7m block, exclude it from attachments.
                    if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) > 0)
                        mimeParts.Add(new MimePart("smime.p7m", contentType, "", "", "", body));

                    // Decrypt the MIME part and recurse through embedded MIME parts.
                    List<MimePart> returnedMIMEParts = ReturnDecryptedMimeParts(contentType, contentTransferEncoding, body, processingFlags);
                    if (returnedMIMEParts != null)
                    {
                        foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                            mimeParts.Add(returnedMIMEPart);
                    }
                    else
                    {
                        // If we were unable to decrypt the message, pass it along as-is.
                        mimeParts.Add(new MimePart(Functions.ReturnBetween(contentType + ";", "name=", ";").Replace("\"", ""), contentType, "", "", contentTransferEncoding, body));
                    }
                }
                else
                {
                    // Hydrate the signature CMS object.
                    SignedCms signedCms = new SignedCms();

                    try
                    {
                        // Attempt to decode the signature block and verify the passed in signature.
                        signedCms.Decode(Convert.FromBase64String(body));
                        signedCms.CheckSignature(true);

                        string mimeContents = Encoding.UTF8.GetString(signedCms.ContentInfo.Content);

                        int mimeDivider = mimeContents.IndexOf("\r\n\r\n");
                        string mimeHeaders;
                        if (mimeDivider > -1)
                            mimeHeaders = mimeContents.Substring(0, mimeDivider);
                        else
                            mimeHeaders = mimeContents;

                        if (mimeHeaders.Length > 0)
                        {
                            // Extract the body portion of the current MIME part.
                            string mimeBody = mimeContents.Substring(mimeDivider + 4);

                            string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                            ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                            List<MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags);
                            foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                                mimeParts.Add(returnedMIMEPart);
                        }
                    }
                    catch
                    {
                        // If an exception occured, the signature could not be verified.
                    }
                }
            }
            else if (contentTypeToUpper == "MESSAGE/RFC822")
            {
                int mimeDivider = body.IndexOf("\r\n\r\n");
                if (mimeDivider > -1)
                {
                    string mimeHeaders = Functions.UnfoldWhitespace(body.Substring(0, mimeDivider));
                    string mimeBody = body.Substring(mimeDivider + 4);

                    string mimeContentType = Functions.ReturnBetween(mimeHeaders, "Content-Type:", "\r\n").Trim();
                    string mimeContentTransferEncoding = Functions.ReturnBetween(mimeHeaders, "Content-Transfer-Encoding:", "\r\n").Trim();
                    string mimeCharSet = Functions.ExtractMimeParameter(mimeContentType, "charset");

                    List<MimePart> returnedMIMEParts = ExtractMIMEParts(mimeContentType, mimeCharSet, mimeContentTransferEncoding, mimeBody, processingFlags);
                    foreach (MimePart returnedMIMEPart in returnedMIMEParts)
                        mimeParts.Add(returnedMIMEPart);
                }
            }
            else
            {
                // Decode the message.
                switch (contentTransferEncoding.ToLower())
                {
                    case "base64":
                        body = Functions.FromBase64(body);
                        break;
                    case "quoted-printable":
                        body = Functions.FromQuotedPrintable(body, charSet, null);
                        break;
                    case "binary":
                    case "7bit":
                    case "8bit":
                        break;
                }

                // Extract the headers from this MIME part.
                string mimeHeaders;
                int mimeDivider = body.IndexOf("\r\n\r\n");
                if (mimeDivider > -1 )
                    mimeHeaders = body.Substring(0, mimeDivider);
                else
                    mimeHeaders = body;

                // Divide the MIME part's headers into its components.
                string mimeCharSet = "", mimeContentDisposition = "", mimeContentID = "", mimeContentType = "", mimeContentTransferEncoding = "", mimeFileName = "";
                ExtractMimeHeaders(mimeHeaders, out mimeContentType, out mimeCharSet, out mimeContentTransferEncoding, out mimeContentDisposition, out mimeFileName, out mimeContentID);

                // If this MIME part's content type is null, fall back to the overall content type.
                if ((string.IsNullOrEmpty(mimeContentType) && !string.IsNullOrEmpty(contentType)) || (contentTypeToUpper.StartsWith("MESSAGE/PARTIAL")))
                {
                    mimeCharSet = charSet;
                    mimeContentType = contentType;
                }
                else
                {
                    if (body.Length > (mimeDivider + 4))
                        body = body.Substring(mimeDivider + 4);
                    else
                        body = "";
                }

                // Add the message to the MIME parts collection.
                mimeParts.Add(new MimePart(mimeFileName, mimeContentType, mimeCharSet, mimeContentID, mimeContentTransferEncoding, body));
            }
            
            return mimeParts;
        }
        /// <summary>
        /// Initializes a populated instance of the OpaqueMail.ReadOnlyMailMessage class representing the message text passed in with attachments procesed according to the attachment filter flags.
        /// </summary>
        /// <param name="messageText">The raw contents of the e-mail message.</param>
        /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
        /// <param name="parseExtendedHeaders">Whether to populate the ExtendedHeaders object.</param>
        public ReadOnlyMailMessage(string messageText, ReadOnlyMailMessageProcessingFlags processingFlags, bool parseExtendedHeaders)
        {
            if (((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawHeaders) > 0)
                && (processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawBody) > 0)
                RawMessage = messageText;

            // Remember which specialized attachments to include.
            ProcessingFlags = processingFlags;

            // Fix messages whose carriage returns have been stripped.
            if (messageText.IndexOf("\r") < 0)
                messageText = messageText.Replace("\n", "\r\n");

            // Separate the headers for processing.
            string headers;
            int cutoff = messageText.IndexOf("\r\n\r\n");
            if (cutoff > -1)
                headers = messageText.Substring(0, cutoff);
            else
                headers = messageText;

            // Set the raw headers property if requested.
            if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawHeaders) > 0)
                RawHeaders = headers;

            // Calculate the size of the message.
            Size = messageText.Length;

            // Temporary header variables to be processed by Functions.FromMailAddressString() later.
            string fromText = "";
            string toText = "";
            string ccText = "";
            string bccText = "";
            string replyToText = "";
            string subjectText = "";

            // Temporary header variables to be processed later.
            List<string> receivedChain = new List<string>();
            string receivedText = "";

            // Unfold any unneeded whitespace, then loop through each line of the headers.
            string[] headersList = Functions.UnfoldWhitespace(headers).Replace("\r", "").Split('\n');
            foreach (string header in headersList)
            {
                // Split header {name:value} pairs by the first colon found.
                int colonPos = header.IndexOf(":");
                if (colonPos > -1 && colonPos < header.Length - 1)
                {
                    string[] headerParts = new string[] { header.Substring(0, colonPos), header.Substring(colonPos + 1).TrimStart(new char[] { ' ' }) };
                    string headerType = headerParts[0].ToLower();
                    string headerValue = headerParts[1];

                    // Set header variables for common headers.
                    if (!string.IsNullOrEmpty(headerType) && !string.IsNullOrEmpty(headerValue))
                        Headers[headerParts[0]] = headerValue;

                    switch (headerType)
                    {
                        case "cc":
                            if (ccText.Length > 0)
                                ccText += ", ";
                            ccText = headerValue;
                            break;
                        case "content-transfer-encoding":
                            ContentTransferEncoding = headerValue;
                            switch (headerValue.ToLower())
                            {
                                case "base64":
                                    BodyTransferEncoding = TransferEncoding.Base64;
                                    break;
                                case "quoted-printable":
                                    BodyTransferEncoding = TransferEncoding.QuotedPrintable;
                                    break;
                                case "7bit":
                                    BodyTransferEncoding = TransferEncoding.SevenBit;
                                    break;
                                case "8bit":
                                    BodyTransferEncoding = TransferEncoding.EightBit;
                                    break;
                                default:
                                    BodyTransferEncoding = TransferEncoding.Unknown;
                                    break;
                            }
                            break;
                        case "content-language":
                            ContentLanguage = headerValue;
                            break;
                        case "content-type":
                            // If multiple content-types are passed, only process the first.
                            if (string.IsNullOrEmpty(ContentType))
                            {
                                ContentType = headerValue.Trim();
                                CharSet = Functions.ExtractMimeParameter(ContentType, "charset");
                            }
                            break;
                        case "date":
                            string dateString = headerValue;

                            // Ignore extraneous datetime information.
                            int dateStringParenthesis = dateString.IndexOf("(");
                            if (dateStringParenthesis > -1)
                                dateString = dateString.Substring(0, dateStringParenthesis - 1);

                            // Remove timezone suffix.
                            if (dateString.Substring(dateString.Length - 4, 1) == " ")
                                dateString = dateString.Substring(0, dateString.Length - 4);

                            DateTime.TryParse(dateString, out Date);
                            break;
                        case "delivered-to":
                            DeliveredTo = headerValue;
                            break;
                        case "from":
                            fromText = headerValue;
                            break;
                        case "importance":
                            Importance = headerValue;
                            break;
                        case "in-reply-to":
                            // Ignore opening and closing <> characters.
                            InReplyTo = headerValue;
                            if (InReplyTo.StartsWith("<"))
                                InReplyTo = InReplyTo.Substring(1);
                            if (InReplyTo.EndsWith(">"))
                                InReplyTo = InReplyTo.Substring(0, InReplyTo.Length - 1);
                            break;
                        case "message-id":
                            // Ignore opening and closing <> characters.
                            MessageId = headerValue;
                            if (MessageId.StartsWith("<"))
                                MessageId = MessageId.Substring(1);
                            if (MessageId.EndsWith(">"))
                                MessageId = MessageId.Substring(0, MessageId.Length - 1);
                            break;
                        case "received":
                        case "x-received":
                            if (!string.IsNullOrEmpty(receivedText))
                                receivedChain.Add(receivedText);

                            receivedText = headerValue;
                            break;
                        case "replyto":
                        case "reply-to":
                            replyToText = headerValue;
                            break;
                        case "return-path":
                            // Ignore opening and closing <> characters.
                            ReturnPath = headerValue;
                            if (ReturnPath.StartsWith("<"))
                                ReturnPath = ReturnPath.Substring(1);
                            if (ReturnPath.EndsWith(">"))
                                ReturnPath = ReturnPath.Substring(0, ReturnPath.Length - 1);
                            break;
                        case "sender":
                        case "x-sender":
                            if (headerValue.Length > 0)
                            {
                                MailAddressCollection senderCollection = Functions.FromMailAddressString(headerValue);
                                if (senderCollection.Count > 0)
                                    this.Sender = senderCollection[0];
                            }
                            break;
                        case "subject":
                            subjectText = headerValue;
                            break;
                        case "to":
                            if (toText.Length > 0)
                                toText += ", ";
                            toText += headerValue;
                            break;
                        case "x-priority":
                            switch (headerValue.ToUpper())
                            {
                                case "LOW":
                                    Priority = MailPriority.Low;
                                    break;
                                case "NORMAL":
                                    Priority = MailPriority.Normal;
                                    break;
                                case "HIGH":
                                    Priority = MailPriority.High;
                                    break;
                            }
                            break;
                        case "x-subject-encryption":
                            bool.TryParse(headerValue, out SubjectEncryption);
                            break;
                        default:
                            break;
                    }

                    // Set header variables for advanced headers.
                    if (parseExtendedHeaders)
                    {
                        ExtendedProperties = new ExtendedProperties();

                        switch (headerType)
                        {
                            case "acceptlanguage":
                            case "accept-language":
                                ExtendedProperties.AcceptLanguage = headerValue;
                                break;
                            case "authentication-results":
                                ExtendedProperties.AuthenticationResults = headerValue;
                                break;
                            case "bounces-to":
                            case "bounces_to":
                                ExtendedProperties.BouncesTo = headerValue;
                                break;
                            case "content-description":
                                ExtendedProperties.ContentDescription = headerValue;
                                break;
                            case "dispositionnotificationto":
                            case "disposition-notification-to":
                                ExtendedProperties.DispositionNotificationTo = headerValue;
                                break;
                            case "dkim-signature":
                            case "domainkey-signature":
                            case "x-google-dkim-signature":
                                ExtendedProperties.DomainKeySignature = headerValue;
                                break;
                            case "domainkey-status":
                                ExtendedProperties.DomainKeyStatus = headerValue;
                                break;
                            case "errors-to":
                                ExtendedProperties.ErrorsTo = headerValue;
                                break;
                            case "list-unsubscribe":
                            case "x-list-unsubscribe":
                                ExtendedProperties.ListUnsubscribe = headerValue;
                                break;
                            case "mailer":
                            case "x-mailer":
                                ExtendedProperties.Mailer = headerValue;
                                break;
                            case "organization":
                            case "x-originator-org":
                            case "x-originatororg":
                            case "x-organization":
                                ExtendedProperties.OriginatorOrg = headerValue;
                                break;
                            case "original-messageid":
                            case "x-original-messageid":
                                ExtendedProperties.OriginalMessageId = headerValue;
                                break;
                            case "originating-email":
                            case "x-originating-email":
                                ExtendedProperties.OriginatingEmail = headerValue;
                                break;
                            case "precedence":
                                ExtendedProperties.Precedence = headerValue;
                                break;
                            case "received-spf":
                                ExtendedProperties.ReceivedSPF = headerValue;
                                break;
                            case "references":
                                ExtendedProperties.References = headerValue;
                                break;
                            case "resent-date":
                                string dateString = headerValue;

                                // Ignore extraneous datetime information.
                                int dateStringParenthesis = dateString.IndexOf("(");
                                if (dateStringParenthesis > -1)
                                    dateString = dateString.Substring(0, dateStringParenthesis - 1);

                                // Remove timezone suffix.
                                if (dateString.Substring(dateString.Length - 4) == " ")
                                    dateString = dateString.Substring(0, dateString.Length - 4);

                                DateTime.TryParse(dateString, out ExtendedProperties.ResentDate);
                                break;
                            case "resent-from":
                                ExtendedProperties.ResentFrom = headerValue;
                                break;
                            case "resent-message-id":
                                ExtendedProperties.ResentMessageID = headerValue;
                                break;
                            case "thread-index":
                                ExtendedProperties.ThreadIndex = headerValue;
                                break;
                            case "thread-topic":
                                ExtendedProperties.ThreadTopic = headerValue;
                                break;
                            case "user-agent":
                            case "useragent":
                                ExtendedProperties.UserAgent = headerValue;
                                break;
                            case "x-auto-response-suppress":
                                ExtendedProperties.AutoResponseSuppress = headerValue;
                                break;
                            case "x-campaign":
                            case "x-campaign-id":
                            case "x-campaignid":
                            case "x-mllistcampaign":
                            case "x-rpcampaign":
                                ExtendedProperties.CampaignID = headerValue;
                                break;
                            case "x-delivery-context":
                                ExtendedProperties.DeliveryContext = headerValue;
                                break;
                            case "x-maillist-id":
                                ExtendedProperties.MailListId = headerValue;
                                break;
                            case "x-msmail-priority":
                                ExtendedProperties.MSMailPriority = headerValue;
                                break;
                            case "x-originalarrivaltime":
                            case "x-original-arrival-time":
                                dateString = headerValue;

                                // Ignore extraneous datetime information.
                                dateStringParenthesis = dateString.IndexOf("(");
                                if (dateStringParenthesis > -1)
                                    dateString = dateString.Substring(0, dateStringParenthesis - 1);

                                // Remove timezone suffix.
                                if (dateString.Substring(dateString.Length - 4) == " ")
                                    dateString = dateString.Substring(0, dateString.Length - 4);

                                DateTime.TryParse(dateString, out ExtendedProperties.OriginalArrivalTime);
                                break;
                            case "x-originating-ip":
                                ExtendedProperties.OriginatingIP = headerValue;
                                break;
                            case "x-rcpt-to":
                                if (headerValue.Length > 1)
                                    ExtendedProperties.RcptTo = headerValue.Substring(1, headerValue.Length - 2);
                                break;
                            case "x-csa-complaints":
                            case "x-complaints-to":
                            case "x-reportabuse":
                            case "x-report-abuse":
                            case "x-mail_abuse_inquiries":
                                ExtendedProperties.ReportAbuse = headerValue;
                                break;
                            case "x-spam-score":
                                ExtendedProperties.SpamScore = headerValue;
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

            // Track all Received and X-Received headers.
            if (!string.IsNullOrEmpty(receivedText))
                receivedChain.Add(receivedText);
            ReceivedChain = receivedChain.ToArray();

            // Process the body if it's passed in.
            string body = "";
            if (cutoff > -1)
                 body = messageText.Substring(cutoff + 2);
            if (!string.IsNullOrEmpty(body))
            {
                // Set the raw body property if requested.
                if ((processingFlags & ReadOnlyMailMessageProcessingFlags.IncludeRawBody) > 0)
                    RawBody = body;

                // Parse body into MIME parts.
                List<MimePart> mimeParts = MimePart.ExtractMIMEParts(ContentType, CharSet, ContentTransferEncoding, body, ProcessingFlags);

                // Process each MIME part.
                if (mimeParts.Count > 0)
                {
                    // Keep track of S/MIME signing and envelope encryption.
                    bool allMimePartsSigned = true, allMimePartsEncrypted = true, allMimePartsTripleWrapped = true;

                    // Process each MIME part.
                    for (int j = 0; j < mimeParts.Count; j++)
                    {
                        MimePart mimePart = mimeParts[j];

                        int semicolon = mimePart.ContentType.IndexOf(";");
                        if (semicolon > -1)
                        {
                            string originalContentType = mimePart.ContentType;
                            mimePart.ContentType = mimePart.ContentType.Substring(0, semicolon);

                            if (mimePart.ContentType.ToUpper() == "MESSAGE/PARTIAL")
                            {
                                PartialMessageId = Functions.ExtractMimeParameter(originalContentType, "id");
                                int partialMessageNumber = 0;
                                if (int.TryParse(Functions.ExtractMimeParameter(originalContentType, "number"), out partialMessageNumber))
                                    PartialMessageNumber = partialMessageNumber;
                            }
                        }

                        // Extract any signing certificates.  If this MIME part isn't signed, the overall message isn't signed.
                        if (mimePart.SmimeSigned)
                        {
                            if (mimePart.SmimeSigningCertificates.Count > 0 && SmimeSigningCertificate == null)
                            {
                                foreach (X509Certificate2 signingCert in mimePart.SmimeSigningCertificates)
                                {
                                    if (!SmimeSigningCertificateChain.Contains(signingCert))
                                    {
                                        SmimeSigningCertificateChain.Add(signingCert);
                                        SmimeSigningCertificate = signingCert;
                                    }
                                }
                            }
                        }
                        else
                            allMimePartsSigned = false;

                        // If this MIME part isn't marked as being in an encrypted envelope, the overall message isn't encrypted.
                        if (!mimePart.SmimeEncryptedEnvelope)
                        {
                            // Ignore signatures and encryption blocks when determining if everything is encrypted.
                            if (!mimePart.ContentType.StartsWith("application/pkcs7-signature") && !mimePart.ContentType.StartsWith("application/x-pkcs7-signature") && !mimePart.ContentType.StartsWith("application/pkcs7-mime"))
                                allMimePartsEncrypted = false;
                        }

                        // If this MIME part isn't marked as being triple wrapped, the overall message isn't triple wrapped.
                        if (!mimePart.SmimeTripleWrapped)
                        {
                            // Ignore signatures and encryption blocks when determining if everything is triple wrapped.
                            if (!mimePart.ContentType.StartsWith("application/pkcs7-signature") && !mimePart.ContentType.StartsWith("application/x-pkcs7-signature") && !mimePart.ContentType.StartsWith("application/pkcs7-mime"))
                                allMimePartsTripleWrapped = false;
                        }

                        // Set the default primary body, defaulting to text/html and falling back to any text/*.
                        string contentTypeToUpper = mimePart.ContentType.ToUpper();
                        if (Body.Length < 1)
                        {
                            // If the MIME part is of type text/*, set it as the intial message body.
                            if (string.IsNullOrEmpty(mimePart.ContentType) || contentTypeToUpper.StartsWith("TEXT/"))
                            {
                                IsBodyHtml = contentTypeToUpper.StartsWith("TEXT/HTML");
                                Body = mimePart.Body;
                                CharSet = mimePart.CharSet;
                                ContentType = mimePart.ContentType;
                                if (mimePart.ContentTransferEncoding != TransferEncoding.Unknown)
                                    BodyTransferEncoding = mimePart.ContentTransferEncoding;
                            }
                            else
                            {
                                // If the MIME part isn't of type text/*, treat is as an attachment.
                                MemoryStream attachmentStream = new MemoryStream(mimePart.BodyBytes);
                                Attachment attachment;
                                if (mimePart.ContentType.IndexOf("/") > -1)
                                    attachment = new Attachment(attachmentStream, mimePart.Name, mimePart.ContentType);
                                else
                                    attachment = new Attachment(attachmentStream, mimePart.Name);

                                attachment.ContentId = mimePart.ContentID;
                                Attachments.Add(attachment);
                            }
                        }
                        else
                        {
                            // If the current body isn't text/html and this is, replace the default body with the current MIME part.
                            if (!ContentType.ToUpper().StartsWith("TEXT/HTML") && contentTypeToUpper.StartsWith("TEXT/HTML"))
                            {
                                // Add the previous default body as an alternate view.
                                MemoryStream alternateViewStream = new MemoryStream(Encoding.UTF8.GetBytes(Body));
                                AlternateView alternateView = new AlternateView(alternateViewStream, ContentType);
                                if (BodyTransferEncoding != TransferEncoding.Unknown)
                                    alternateView.TransferEncoding = BodyTransferEncoding;
                                AlternateViews.Add(alternateView);

                                IsBodyHtml = true;
                                Body = mimePart.Body;
                                CharSet = mimePart.CharSet;
                                ContentType = mimePart.ContentType;
                                if (mimePart.ContentTransferEncoding != TransferEncoding.Unknown)
                                    BodyTransferEncoding = mimePart.ContentTransferEncoding;
                            }
                            else
                            {
                                // If the MIME part isn't of type text/*, treat is as an attachment.
                                MemoryStream attachmentStream = new MemoryStream(mimePart.BodyBytes);
                                Attachment attachment;
                                if (mimePart.ContentType.IndexOf("/") > -1)
                                    attachment = new Attachment(attachmentStream, mimePart.Name, mimePart.ContentType);
                                else
                                    attachment = new Attachment(attachmentStream, mimePart.Name);
                                attachment.ContentId = mimePart.ContentID;
                                Attachments.Add(attachment);
                            }
                        }
                    }

                    // OpaqueMail optional setting for protecting the subject.
                    if (SubjectEncryption && Body.StartsWith("Subject: "))
                    {
                        int linebreakPosition = Body.IndexOf("\r\n");
                        if (linebreakPosition > -1)
                        {
                            subjectText = Body.Substring(9, linebreakPosition - 9);
                            Body = Body.Substring(linebreakPosition + 2);
                        }
                    }

                    // Set the message's S/MIME attributes.
                    SmimeSigned = allMimePartsSigned;
                    SmimeEncryptedEnvelope = allMimePartsEncrypted;
                    SmimeTripleWrapped = allMimePartsTripleWrapped;
                }
                else
                {
                    // Process non-MIME messages.
                    Body = body;
                }
            }

            // Parse String representations of addresses into MailAddress objects.
            if (fromText.Length > 0)
            {
                MailAddressCollection fromAddresses = Functions.FromMailAddressString(fromText);
                if (fromAddresses.Count > 0)
                    From = fromAddresses[0];
            }

            if (toText.Length > 0)
            {
                To.Clear();
                MailAddressCollection toAddresses = Functions.FromMailAddressString(toText);
                foreach (MailAddress toAddress in toAddresses)
                    To.Add(toAddress);

                // Add the address to the AllRecipients collection.
                foreach (MailAddress toAddress in toAddresses)
                {
                    if (!AllRecipients.Contains(toAddress.Address))
                        AllRecipients.Add(toAddress.Address);
                }
            }

            if (ccText.Length > 0)
            {
                CC.Clear();
                MailAddressCollection ccAddresses = Functions.FromMailAddressString(ccText);
                foreach (MailAddress ccAddress in ccAddresses)
                    CC.Add(ccAddress);

                // Add the address to the AllRecipients collection.
                foreach (MailAddress ccAddress in ccAddresses)
                {
                    if (!AllRecipients.Contains(ccAddress.Address))
                        AllRecipients.Add(ccAddress.Address);
                }
            }

            if (bccText.Length > 0)
            {
                Bcc.Clear();
                MailAddressCollection bccAddresses = Functions.FromMailAddressString(bccText);
                foreach (MailAddress bccAddress in bccAddresses)
                    Bcc.Add(bccAddress);

                // Add the address to the AllRecipients collection.
                foreach (MailAddress bccAddress in bccAddresses)
                {
                    if (!AllRecipients.Contains(bccAddress.Address))
                        AllRecipients.Add(bccAddress.Address);
                }
            }

            if (replyToText.Length > 0)
            {
                ReplyToList.Clear();
                MailAddressCollection replyToAddresses = Functions.FromMailAddressString(replyToText);
                foreach (MailAddress replyToAddress in replyToAddresses)
                    ReplyToList.Add(replyToAddress);
            }

            // Decode international strings and remove escaped linebreaks.
            Subject = Functions.DecodeMailHeader(subjectText).Replace("\r", "").Replace("\n", "");
        }
 /// <summary>
 /// Initializes a populated instance of the OpaqueMail.ReadOnlyMailMessage class representing the message text passed in with attachments procesed according to the attachment filter flags.
 /// </summary>
 /// <param name="messageText">The raw contents of the e-mail message.</param>
 /// <param name="processingFlags">Flags determining whether specialized properties are returned with a ReadOnlyMailMessage.</param>
 public ReadOnlyMailMessage(string messageText, ReadOnlyMailMessageProcessingFlags processingFlags) : this(messageText, processingFlags, false) { }