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