/// <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", ""); }