/// <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 MailMessage.</param> /// <param name="depth">The nesting layer of this MIME part.</param> public static List <MimePart> ReturnSmimeDecryptedMimeParts(string contentType, string contentTransferEncoding, string envelopeText, MailMessageProcessingFlags 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); } }
/// <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 MailMessage.</param> public static List <MimePart> ExtractMIMEParts(string contentType, string charSet, string contentTransferEncoding, string body, MailMessageProcessingFlags 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 & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) == 0; // Decrypt the MIME part and recurse through embedded MIME parts. List <MimePart> returnedMIMEParts = ReturnSmimeDecryptedMimeParts(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 & MailMessageProcessingFlags.IncludeWinMailData) > 0) { processed = false; } else { // Unless a flag has been set to include this winmail.dat block, exclude it from attachments. if ((processingFlags & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.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 (VerifySmimeSignature(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 & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) > 0) { mimeParts.Add(new MimePart("smime.p7m", contentType, "", "", "", body)); } // Decrypt the MIME part and recurse through embedded MIME parts. List <MimePart> returnedMIMEParts = ReturnSmimeDecryptedMimeParts(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); }
/// <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 MailMessage.</param> /// <param name="depth">The nesting layer of this MIME part.</param> public static List<MimePart> ReturnSmimeDecryptedMimeParts(string contentType, string contentTransferEncoding, string envelopeText, MailMessageProcessingFlags 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; } }
/// <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 MailMessage.</param> public static List<MimePart> ExtractMIMEParts(string contentType, string charSet, string contentTransferEncoding, string body, MailMessageProcessingFlags 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 & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) == 0; // Decrypt the MIME part and recurse through embedded MIME parts. List<MimePart> returnedMIMEParts = ReturnSmimeDecryptedMimeParts(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 & MailMessageProcessingFlags.IncludeWinMailData) > 0) processed = false; else { // Unless a flag has been set to include this winmail.dat block, exclude it from attachments. if ((processingFlags & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.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 (VerifySmimeSignature(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 & MailMessageProcessingFlags.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 & MailMessageProcessingFlags.IncludeSmimeEncryptedEnvelopeData) > 0) mimeParts.Add(new MimePart("smime.p7m", contentType, "", "", "", body)); // Decrypt the MIME part and recurse through embedded MIME parts. List<MimePart> returnedMIMEParts = ReturnSmimeDecryptedMimeParts(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; }
/// <summary> /// Initializes a populated instance of the OpaqueMail.MailMessage 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 email message.</param> /// <param name="processingFlags">Flags determining whether specialized properties are returned with a MailMessage.</param> /// <param name="parseExtendedHeaders">Whether to populate the ExtendedHeaders object.</param> public MailMessage(string messageText, MailMessageProcessingFlags processingFlags, bool parseExtendedHeaders) : this() { // Default to no MIME boundary. MimeBoundaryName = null; // Prepare an object to track advanced headers. if (parseExtendedHeaders) ExtendedProperties = new ExtendedProperties(); // 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; // 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 = ""; // 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[headerType] = 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"); MimeBoundaryName = Functions.ExtractMimeParameter(ContentType, "boundary"); BodyContentType = ContentType.ToLower(); if (BodyContentType.IndexOf(";") > -1) BodyContentType = BodyContentType.Substring(0, BodyContentType.IndexOf(";")); IsBodyHtml = BodyContentType.StartsWith("text/html"); } 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 date; DateTime.TryParse(dateString, out date); Date = date; break; case "delivered-to": DeliveredTo = headerValue; break; case "from": fromText = headerValue; break; case "importance": Importance = headerValue; break; case "in-reply-to": InReplyTo = headerValue; // Loop through references. List<string> inReplyTo = new List<string>(); int inReplyToPos = 0; while (inReplyToPos > -1) { inReplyToPos = headerValue.IndexOf("<", inReplyToPos); if (inReplyToPos > -1) { int referencesPos2 = headerValue.IndexOf(">", inReplyToPos); if (referencesPos2 > -1) { inReplyTo.Add(headerValue.Substring(inReplyToPos + 1, referencesPos2 - inReplyToPos - 1)); inReplyToPos = referencesPos2 + 1; } else inReplyToPos = -1; } } // If any references were found, apply to the message's array. if (inReplyTo.Count > 0) InReplyToMessageIDs = inReplyTo.ToArray(); else { if (!string.IsNullOrEmpty(headerValue)) InReplyToMessageIDs = new string[] {headerValue}; } 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 "references": // Loop through references. List<string> references = new List<string>(); int referencesPos = 0; while (referencesPos > -1) { referencesPos = headerValue.IndexOf("<", referencesPos); if (referencesPos > -1) { int referencesPos2 = headerValue.IndexOf(">", referencesPos); if (referencesPos2 > -1) { references.Add(headerValue.Substring(referencesPos + 1, referencesPos2 - referencesPos - 1)); referencesPos = referencesPos2 + 1; } else referencesPos = -1; } } // If any references were found, apply to the message's array. if (references.Count > 0) References = references.ToArray(); 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 = MailAddressCollection.Parse(headerValue); if (senderCollection.Count > 0) this.Sender = senderCollection[0]; } break; case "subject": // Decode international strings and remove escaped linebreaks. Subject = Functions.DecodeMailHeader(headerValue).Replace("\r", "").Replace("\n", ""); 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 subjectEncryption; bool.TryParse(headerValue, out subjectEncryption); SubjectEncryption = subjectEncryption; break; default: break; } // Set header variables for advanced headers. if (parseExtendedHeaders) { 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 common headers are unsent, we're parsing the body only. if (string.IsNullOrEmpty(fromText) && string.IsNullOrEmpty(Subject)) body = messageText; else { HasHeaders = true; // Set the raw headers property if requested. if ((processingFlags & MailMessageProcessingFlags.IncludeRawHeaders) > 0) RawHeaders = headers; if (cutoff > -1) { body = messageText.Substring(cutoff + 4); if (body.Length > 0) { // Ignore closing message indicators. int closePos = body.Length; if (body.EndsWith(")")) closePos--; // Ignore trailing linebreaks. while (closePos > 1 && body.Substring(closePos - 2, 2) == "\r\n") closePos -= 2; if (closePos != body.Length) body = body.Substring(0, closePos); } } } if (!string.IsNullOrEmpty(body)) { HasBody = true; // Only process MIME parts if a boundary name is passed in. if (!string.IsNullOrEmpty(MimeBoundaryName)) { // Parse body into MIME parts and process them. MimeParts = MimePart.ExtractMIMEParts(ContentType, CharSet, ContentTransferEncoding, body, ProcessingFlags, 0); if (MimeParts.Count > 0) Task.Run(() => ProcessMimeParts()).Wait(); else { // We can no longer trust the external encoding, so infer if this body is Base-64 or Quoted-Printable encoded. if (Functions.AppearsBase64(body)) ContentTransferEncoding = "base64"; else if (Functions.AppearsQuotedPrintable(body)) ContentTransferEncoding = "quoted-printable"; string encodedBody = body; Body = Functions.Decode(body, ContentTransferEncoding, CharSet, BodyEncoding); if (Body != encodedBody) BodyDecoded = true; if (Body.StartsWith("-----BEGIN PGP MESSAGE-----")) pgpEncrypted = true; else if (Body.StartsWith("-----BEGIN PGP SIGNED MESSAGE-----")) pgpSigned = true; BodyContentType = ContentType; if (BodyContentType.IndexOf(";") > -1) BodyContentType = BodyContentType.Substring(0, BodyContentType.IndexOf(";")); // Infer if text/html when no content type is specified. if (BodyContentType != "text/html" && Body.ToUpper().Contains("<BODY")) BodyContentType = "text/html"; } if (!((processingFlags & MailMessageProcessingFlags.IncludeMIMEParts) > 0)) MimeParts = null; } else { // If no encoding is specified (such as when requesting partial headers only), infer if this body is Base-64 or Quoted-Printable encoded. if (string.IsNullOrEmpty(ContentTransferEncoding)) { if (Functions.AppearsBase64(body)) ContentTransferEncoding = "base64"; else if (Functions.AppearsQuotedPrintable(body)) ContentTransferEncoding = "quoted-printable"; } string encodedBody = body; Body = Functions.Decode(body, ContentTransferEncoding, CharSet, BodyEncoding); if (Body != encodedBody) BodyDecoded = true; if (Body.StartsWith("-----BEGIN PGP MESSAGE-----")) pgpEncrypted = true; else if (Body.StartsWith("-----BEGIN PGP SIGNED MESSAGE-----")) pgpSigned = true; BodyContentType = ContentType; if (BodyContentType.IndexOf(";") > -1) BodyContentType = BodyContentType.Substring(0, BodyContentType.IndexOf(";")); // Infer if text/html when no content type is specified. if (BodyContentType != "text/html" && Body.ToUpper().Contains("<BODY")) BodyContentType = "text/html"; } // Set the raw body property if requested. if ((processingFlags & MailMessageProcessingFlags.IncludeRawBody) > 0) RawBody = body; } // Parse String representations of addresses into MailAddress objects. if (fromText.Length > 0) { MailAddressCollection fromAddresses = MailAddressCollection.Parse(fromText); if (fromAddresses.Count > 0) From = fromAddresses[0]; } if (toText.Length > 0) { To = MailAddressCollection.Parse(toText); // Add the address to the AllRecipients collection. foreach (MailAddress toAddress in To) { if (!AllRecipients.Contains(toAddress.Address)) AllRecipients.Add(toAddress.Address); } } if (ccText.Length > 0) { CC = MailAddressCollection.Parse(ccText); // Add the address to the AllRecipients collection. foreach (MailAddress ccAddress in CC) { if (!AllRecipients.Contains(ccAddress.Address)) AllRecipients.Add(ccAddress.Address); } } if (bccText.Length > 0) { Bcc = MailAddressCollection.Parse(bccText); // Add the address to the AllRecipients collection. foreach (MailAddress bccAddress in Bcc) { if (!AllRecipients.Contains(bccAddress.Address)) AllRecipients.Add(bccAddress.Address); } } if (replyToText.Length > 0) { ReplyToList = MailAddressCollection.Parse(replyToText); } }
/// <summary> /// Initializes a populated instance of the OpaqueMail.MailMessage 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 email message.</param> /// <param name="processingFlags">Flags determining whether specialized properties are returned with a MailMessage.</param> public MailMessage(string messageText, MailMessageProcessingFlags processingFlags) : this(messageText, processingFlags, false) { }