public static MimeMessage ParseMessage(Stream inputStream, HTTPHeaders headers, CancellationToken cancellationToken) { if (inputStream == null || inputStream == Stream.Null) { return(null); } var headerArray = headers.ToArray(); if (IsMimeMessage(headerArray) == false) { return(null); } string headerString = String.Join("\r\n", headerArray.Select(h => $"{h.Name}: {h.Value}")); try { using (ChainedStream streamWithHeaders = new ChainedStream()) { streamWithHeaders.Add(new MemoryStream(Encoding.UTF8.GetBytes(headerString)), false); streamWithHeaders.Add(inputStream, false); var parser = new MimeKit.MimeParser(streamWithHeaders); return(parser.ParseMessage(cancellationToken)); } } catch (FormatException) { return(null); } }
static MimeMessage Load (string path) { using (var file = File.OpenRead (path)) { var parser = new MimeParser (file); return parser.ParseMessage (); } }
public void TestJwzMbox() { var summary = File.ReadAllText ("../../TestData/mbox/jwz-summary.txt"); var builder = new StringBuilder (); using (var stream = File.OpenRead ("../../TestData/mbox/jwz.mbox.txt")) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { var message = parser.ParseMessage (); builder.AppendFormat ("{0}\n", parser.MboxMarker); if (message.From.Count > 0) builder.AppendFormat ("From: {0}\n", message.From); if (message.To.Count > 0) builder.AppendFormat ("To: {0}\n", message.To); builder.AppendFormat ("Subject: {0}\n", message.Subject); builder.AppendFormat ("Date: {0}\n", DateUtils.FormatDate (message.Date)); DumpMimeTree (builder, message.Body, 0); builder.Append ("\n"); } } string actual = builder.ToString (); // WORKAROUND: Mono's iso-2022-jp decoder breaks on this input in versions <= 3.2.3 but is fixed in 3.2.4+ string iso2022jp = Encoding.GetEncoding ("iso-2022-jp").GetString (Convert.FromBase64String ("GyRAOjRGI0stGyhK")); if (iso2022jp != "佐藤豊") actual = actual.Replace (iso2022jp, "佐藤豊"); Assert.AreEqual (summary, actual, "Summaries do not match for jwz.mbox"); }
/// <summary> /// Join the specified message/partial parts into the complete message. /// </summary> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> public static MimeMessage Join(ParserOptions options, IEnumerable <MessagePartial> partials) { if (options == null) { throw new ArgumentNullException("options"); } if (partials == null) { throw new ArgumentNullException("partials"); } var parts = partials.ToList(); if (parts.Count == 0) { return(null); } parts.Sort(PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) { throw new ArgumentException("partials"); } int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) { throw new ArgumentException("partials"); } string id = parts[0].Id; using (var chained = new ChainedStream()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) { throw new ArgumentException("partials"); } var content = parts[i].ContentObject; content.Stream.Seek(0, SeekOrigin.Begin); var filtered = new FilteredStream(content.Stream); filtered.Add(DecoderFilter.Create(content.Encoding)); chained.Add(filtered); } var parser = new MimeParser(options, chained); return(parser.ParseMessage()); } }
/// <summary> /// Joins the specified message/partial parts into the complete message. /// </summary> /// <remarks> /// Combines all of the message/partial fragments into its original, /// complete, message. /// </remarks> /// <returns>The re-combined message.</returns> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>The last partial does not have a Total.</para> /// <para>-or-</para> /// <para>The number of partials provided does not match the expected count.</para> /// <para>-or-</para> /// <para>One or more partials is missing.</para> /// </exception> public static MimeMessage Join(ParserOptions options, IEnumerable <MessagePartial> partials) { if (options == null) { throw new ArgumentNullException("options"); } if (partials == null) { throw new ArgumentNullException("partials"); } var parts = partials.ToList(); if (parts.Count == 0) { return(null); } parts.Sort(PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) { throw new ArgumentException("The last partial does not have a Total.", "partials"); } int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) { throw new ArgumentException("The number of partials provided does not match the expected count.", "partials"); } string id = parts[0].Id; using (var chained = new ChainedStream()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) { throw new ArgumentException("One or more partials is missing.", "partials"); } var content = parts[i].ContentObject; chained.Add(content.Open()); } var parser = new MimeParser(options, chained); return(parser.ParseMessage()); } }
static void Main( ) { // Use the system console object FileStream stream = new FileStream("/home/bhj/Maildir/SentMails/cur/1419597900.M517484P16503Q0.bhj-home:2,S", FileMode.Open); var parser = new MimeParser (stream, MimeFormat.Entity); var message = parser.ParseMessage (); FileStream output = new FileStream("/home/bhj/tmp/out.txt", FileMode.Create); foreach (var part in message.BodyParts) { part.ContentObject.DecodeTo (output); // do something } }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified stream. /// </summary> /// <remarks> /// <para>Loads a <see cref="MimeEntity"/> from the given stream, using the /// specified <see cref="ParserOptions"/>.</para> /// <para>If <paramref name="persistent"/> is <c>true</c> and <paramref name="stream"/> is seekable, then /// the <see cref="MimeParser"/> will not copy the content of <see cref="MimePart"/>s into memory. Instead, /// it will use a <see cref="MimeKit.IO.BoundStream"/> to reference a substream of <paramref name="stream"/>. /// This has the potential to not only save mmeory usage, but also improve <see cref="MimeParser"/> /// performance.</para> /// </remarks> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="persistent"><c>true</c> if the stream is persistent; otherwise <c>false</c>.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, Stream stream, bool persistent, CancellationToken cancellationToken = default(CancellationToken)) { if (options == null) { throw new ArgumentNullException("options"); } if (stream == null) { throw new ArgumentNullException("stream"); } var parser = new MimeParser(options, stream, MimeFormat.Entity, persistent); return(parser.ParseEntity(cancellationToken)); }
/// <summary> /// Load a <see cref="HeaderList"/> from the specified stream. /// </summary> /// <remarks> /// Loads a <see cref="HeaderList"/> from the given stream, using the /// specified <see cref="ParserOptions"/>. /// </remarks> /// <returns>The parsed list of headers.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the headers. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static HeaderList Load(ParserOptions options, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { if (options == null) { throw new ArgumentNullException("options"); } if (stream == null) { throw new ArgumentNullException("stream"); } var parser = new MimeParser(options, stream, MimeFormat.Entity); return(parser.ParseHeaders(cancellationToken)); }
public void TestEmptyMultipartAlternative() { string expected = @"Content-Type: multipart/mixed Content-Type: multipart/alternative Content-Type: text/plain "; using (var stream = File.OpenRead ("../../TestData/messages/empty-multipart.txt")) { var parser = new MimeParser (stream, MimeFormat.Entity); var message = parser.ParseMessage (); var builder = new StringBuilder (); DumpMimeTree (builder, message.Body, 0); Assert.AreEqual (expected, builder.ToString (), "Unexpected MIME tree structure."); } }
public void TestMimeVisitor () { var dataDir = Path.Combine ("..", "..", "TestData", "mbox"); var visitor = new HtmlPreviewVisitor (); int index = 0; using (var stream = File.OpenRead (Path.Combine (dataDir, "jwz.mbox.txt"))) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { var filename = string.Format ("jwz.body.{0}.html", index); var path = Path.Combine (dataDir, filename); var message = parser.ParseMessage (); string expected, actual; visitor.Visit (message); actual = visitor.HtmlBody; if (!string.IsNullOrEmpty (actual)) actual = actual.Replace ("\r\n", "\n"); if (!File.Exists (path) && actual != null) File.WriteAllText (path, actual); if (File.Exists (path)) expected = File.ReadAllText (path, Encoding.UTF8).Replace ("\r\n", "\n"); else expected = null; if (index != 6 && index != 13 && index != 31) { // message 6, 13 and 31 contain some japanese text that is broken in Mono Assert.AreEqual (expected, actual, "The bodies do not match for message {0}", index); } visitor.Reset (); index++; } } }
public void TestSecureMimeDecryptThunderbird() { var p12 = Path.Combine ("..", "..", "TestData", "smime", "gnome.p12"); MimeMessage message; if (!File.Exists (p12)) return; using (var file = File.OpenRead (Path.Combine ("..", "..", "TestData", "smime", "thunderbird-encrypted.txt"))) { var parser = new MimeParser (file, MimeFormat.Default); message = parser.ParseMessage (); } using (var ctx = CreateContext ()) { var encrypted = (ApplicationPkcs7Mime) message.Body; MimeEntity decrypted = null; using (var file = File.OpenRead (p12)) { ctx.Import (file, "no.secret"); } var type = encrypted.ContentType.Parameters["smime-type"]; Assert.AreEqual ("enveloped-data", type, "Unexpected smime-type parameter."); try { decrypted = encrypted.Decrypt (ctx); } catch (Exception ex) { Console.WriteLine (ex); Assert.Fail ("Failed to decrypt thunderbird message: {0}", ex); } // The decrypted part should be a multipart/mixed with a text/plain part and an image attachment, // very much like the thunderbird-signed.txt message. Assert.IsInstanceOfType (typeof (Multipart), decrypted, "Expected the decrypted part to be a Multipart."); var multipart = (Multipart) decrypted; Assert.IsInstanceOfType (typeof (TextPart), multipart[0], "Expected the first part of the decrypted multipart to be a TextPart."); Assert.IsInstanceOfType (typeof (MimePart), multipart[1], "Expected the second part of the decrypted multipart to be a MimePart."); } }
public void TestSimpleMbox () { using (var stream = File.OpenRead ("../../TestData/mbox/simple.mbox.txt")) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { var message = parser.ParseMessage (); Multipart multipart; MimeEntity entity; Assert.IsInstanceOfType (typeof (Multipart), message.Body); multipart = (Multipart) message.Body; Assert.AreEqual (1, multipart.Count); entity = multipart[0]; Assert.IsInstanceOfType (typeof (Multipart), entity); multipart = (Multipart) entity; Assert.AreEqual (1, multipart.Count); entity = multipart[0]; Assert.IsInstanceOfType (typeof (Multipart), entity); multipart = (Multipart) entity; Assert.AreEqual (1, multipart.Count); entity = multipart[0]; Assert.IsInstanceOfType (typeof (TextPart), entity); using (var memory = new MemoryStream ()) { entity.WriteTo (UnixFormatOptions, memory); var text = Encoding.ASCII.GetString (memory.ToArray ()); Assert.IsTrue (text.StartsWith ("Content-Type: text/plain\n\n", StringComparison.Ordinal), "Headers are not properly terminated."); } } } }
/// <summary> /// Joins the specified message/partial parts into the complete message. /// </summary> /// <remarks> /// Combines all of the message/partial fragments into its original, /// complete, message. /// </remarks> /// <returns>The re-combined message.</returns> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>The last partial does not have a Total.</para> /// <para>-or-</para> /// <para>The number of partials provided does not match the expected count.</para> /// <para>-or-</para> /// <para>One or more partials is missing.</para> /// </exception> public static MimeMessage Join (ParserOptions options, IEnumerable<MessagePartial> partials) { if (options == null) throw new ArgumentNullException ("options"); if (partials == null) throw new ArgumentNullException ("partials"); var parts = partials.ToList (); if (parts.Count == 0) return null; parts.Sort (PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) throw new ArgumentException ("The last partial does not have a Total.", "partials"); int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) throw new ArgumentException ("The number of partials provided does not match the expected count.", "partials"); string id = parts[0].Id; using (var chained = new ChainedStream ()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) throw new ArgumentException ("One or more partials is missing.", "partials"); var content = parts[i].ContentObject; chained.Add (content.Open ()); } var parser = new MimeParser (options, chained); return parser.ParseMessage (); } }
MimeMessage ParseMessage(CancellationToken cancellationToken) { if (parser == null) parser = new MimeParser (ParserOptions.Default, engine.Stream); else parser.SetStream (ParserOptions.Default, engine.Stream); return parser.ParseMessage (cancellationToken); }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified stream. /// </summary> /// <remarks> /// Loads a <see cref="MimeEntity"/> from the given stream, using the /// specified <see cref="ParserOptions"/>. /// </remarks> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, Stream stream, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); var parser = new MimeParser (options, stream, MimeFormat.Entity); return parser.ParseEntity (cancellationToken); }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified stream. /// </summary> /// <returns>The parsed MIME entity.</returns> /// <param name="stream">The stream.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="stream"/> is <c>null</c>. /// </exception> public static MimeEntity Load(Stream stream) { if (stream == null) throw new ArgumentNullException ("stream"); var parser = new MimeParser (stream, MimeFormat.Entity); return parser.ParseEntity (); }
//[Ignore] public void TestDkimSignVerifyJwzMbox () { using (var stream = File.OpenRead ("../../TestData/mbox/jwz.mbox.txt")) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { var message = parser.ParseMessage (); TestDkimSignVerify (message, DkimSignatureAlgorithm.RsaSha1, DkimCanonicalizationAlgorithm.Relaxed, DkimCanonicalizationAlgorithm.Relaxed); TestDkimSignVerify (message, DkimSignatureAlgorithm.RsaSha256, DkimCanonicalizationAlgorithm.Relaxed, DkimCanonicalizationAlgorithm.Simple); TestDkimSignVerify (message, DkimSignatureAlgorithm.RsaSha1, DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm.Relaxed); TestDkimSignVerify (message, DkimSignatureAlgorithm.RsaSha256, DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm.Simple); } } }
public void TestReserialization () { string rawMessageText = @"X-Andrew-Authenticated-As: 4099;greenbush.galaxy;Nathaniel Borenstein Received: from Messages.8.5.N.CUILIB.3.45.SNAP.NOT.LINKED.greenbush.galaxy.sun4.41 via MS.5.6.greenbush.galaxy.sun4_41; Fri, 12 Jun 1992 13:29:05 -0400 (EDT) Message-ID : <*****@*****.**> Date: Fri, 12 Jun 1992 13:29:05 -0400 (EDT) From: Nathaniel Borenstein <nsb> X-Andrew-Message-Size: 152+1 MIME-Version: 1.0 Content-Type: multipart/alternative; boundary=""Interpart.Boundary.IeCBvV20M2YtEoUA0A"" To: Ned Freed <*****@*****.**>, [email protected] (Yutaka Sato =?ISO-2022-JP?B?GyRAOjRGI0stGyhK?= ) Subject: MIME & int'l mail > THIS IS A MESSAGE IN 'MIME' FORMAT. Your mail reader does not support MIME. > Please read the first section, which is plain text, and ignore the rest. --Interpart.Boundary.IeCBvV20M2YtEoUA0A Content-type: text/plain; charset=US-ASCII In honor of the Communications Week error about MIME's ability to handle international character sets. a screen dump: [An Andrew ToolKit view (mailobjv) was included here, but could not be displayed.] Just for fun.... -- Nathaniel --Interpart.Boundary.IeCBvV20M2YtEoUA0A Content-Type: multipart/mixed; boundary=""Alternative.Boundary.IeCBvV20M2Yt4oU=wd"" --Alternative.Boundary.IeCBvV20M2Yt4oU=wd Content-type: text/richtext; charset=US-ASCII Content-Transfer-Encoding: quoted-printable In honor of the <italic>Communications Week</italic> error about MIME's abilit= y to handle international character sets. a screen dump:<nl> <nl> --Alternative.Boundary.IeCBvV20M2Yt4oU=wd Content-type: image/gif Content-Description: Some international characters Content-Transfer-Encoding: base64 R0lGODdhEgLiAKEAAAAAAP///wAA////4CwAAAAAEgLiAAAC/oSPqcvtD6OctNqLs968 ... R+mUIAiVUTmCU0mVJmiVV5mCfaiVQtaUXVlKXwmWZiSWY3lDZWmWIISWaalUWcmW+bWW b9lAcSmXCUSXdWlKbomX7HWXe4llXOmXQAmYgTmUg0mYRmmYh5mUscGYjemYjwmZkSmZ k0mZlWmZl4mZqVEAADs= --Alternative.Boundary.IeCBvV20M2Yt4oU=wd Content-type: text/richtext; charset=US-ASCII Content-Transfer-Encoding: quoted-printable <nl> <nl> Just for fun.... -- Nathaniel<nl> --Interpart.Boundary.IeCBvV20M2YtEoUA0A-- ".Replace ("\r\n", "\n"); string result; using (var source = new MemoryStream (Encoding.UTF8.GetBytes (rawMessageText))) { var parser = new MimeParser (source, MimeFormat.Default); var message = parser.ParseMessage (); using (var serialized = new MemoryStream ()) { var options = FormatOptions.Default.Clone (); options.NewLineFormat = NewLineFormat.Unix; message.WriteTo (options, serialized); result = Encoding.UTF8.GetString (serialized.ToArray ()); } } Assert.AreEqual (rawMessageText, result, "Reserialized message is not identical to the original."); }
/// <summary> /// Load a <see cref="MimeMessage"/> from the specified stream. /// </summary> /// <remarks> /// <para>Loads a <see cref="MimeMessage"/> from the given stream, using the /// specified <see cref="ParserOptions"/>.</para> /// <para>If <paramref name="persistent"/> is <c>true</c> and <paramref name="stream"/> is seekable, then /// the <see cref="MimeParser"/> will not copy the content of <see cref="MimePart"/>s into memory. Instead, /// it will use a <see cref="MimeKit.IO.BoundStream"/> to reference a substream of <paramref name="stream"/>. /// This has the potential to not only save mmeory usage, but also improve <see cref="MimeParser"/> /// performance.</para> /// </remarks> /// <returns>The parsed message.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="persistent"><c>true</c> if the stream is persistent; otherwise <c>false</c>.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent); return parser.ParseMessage (cancellationToken); }
void HandlePgpMime(Outlook.MailItem mailItem, Outlook.Attachment encryptedMime, Outlook.Attachment sigMime, string sigHash = "sha1") { logger.Trace("> HandlePgpMime"); CryptoContext Context = null; byte[] cyphertext = null; byte[] clearbytes = null; var cleartext = mailItem.Body; // 1. Decrypt attachement if (encryptedMime != null) { logger.Trace("Decrypting cypher text."); var tempfile = Path.GetTempFileName(); encryptedMime.SaveAsFile(tempfile); cyphertext = File.ReadAllBytes(tempfile); File.Delete(tempfile); clearbytes = DecryptAndVerify(mailItem.To, cyphertext, out Context); if (clearbytes == null) return; cleartext = this._encoding.GetString(clearbytes); } // 2. Verify signature if (sigMime != null) { Context = new CryptoContext(PasswordCallback, _settings.Cipher, _settings.Digest); var Crypto = new PgpCrypto(Context); var mailType = mailItem.BodyFormat; try { logger.Trace("Verify detached signature"); var tempfile = Path.GetTempFileName(); sigMime.SaveAsFile(tempfile); var detachedsig = File.ReadAllText(tempfile); File.Delete(tempfile); // Build up a clearsignature format for validation // the rules for are the same with the addition of two heaer fields. // Ultimately we need to get these fields out of email itself. // NOTE: encoding could be uppercase or lowercase. Try both. // this is definetly hacky :/ var encoding = GetEncodingFromMail(mailItem); var body = string.Empty; // Try two different methods to get the mime body try { body = encoding.GetString( (byte[])mailItem.PropertyAccessor.GetProperty( "http://schemas.microsoft.com/mapi/string/{4E3A7680-B77A-11D0-9DA5-00C04FD65685}/Internet Charset Body/0x00000102")); } catch (Exception) { body = (string)mailItem.PropertyAccessor.GetProperty( "http://schemas.microsoft.com/mapi/proptag/0x1000001F"); // PR_BODY } var clearsigUpper = new StringBuilder(); clearsigUpper.Append(string.Format("-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: {0}\r\nCharset: {1}\r\n\r\n", sigHash, encoding.BodyName.ToUpper())); clearsigUpper.Append("Content-Type: text/plain; charset="); clearsigUpper.Append(encoding.BodyName.ToUpper()); clearsigUpper.Append("\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n"); clearsigUpper.Append(PgpClearDashEscapeAndQuoteEncode(body)); clearsigUpper.Append("\r\n"); clearsigUpper.Append(detachedsig); var clearsigLower = new StringBuilder(clearsigUpper.Length); clearsigLower.Append(string.Format("-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: {0}\r\nCharset: {1}\r\n\r\n", sigHash, encoding.BodyName.ToUpper())); clearsigLower.Append("Content-Type: text/plain; charset="); clearsigLower.Append(encoding.BodyName.ToLower()); clearsigLower.Append("\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n"); clearsigLower.Append(PgpClearDashEscapeAndQuoteEncode(body)); clearsigLower.Append("\r\n"); clearsigLower.Append(detachedsig); logger.Trace(clearsigUpper.ToString()); if (Crypto.VerifyClear(_encoding.GetBytes(clearsigUpper.ToString())) || Crypto.VerifyClear(_encoding.GetBytes(clearsigLower.ToString()))) { Context = Crypto.Context; var message = "** " + string.Format(Localized.MsgValidSig, Context.SignedByUserId, Context.SignedByKeyId) + "\n\n"; if (mailType == Outlook.OlBodyFormat.olFormatPlain) mailItem.Body = message + mailItem.Body; else mailItem.HTMLBody = AddMessageToHtmlBody(mailItem.HTMLBody, message); } else { Context = Crypto.Context; var message = "** " + string.Format(Localized.MsgInvalidSig, Context.SignedByUserId, Context.SignedByKeyId) + "\n\n"; if (mailType == Outlook.OlBodyFormat.olFormatPlain) mailItem.Body = message + mailItem.Body; else mailItem.HTMLBody = AddMessageToHtmlBody(mailItem.HTMLBody, message); } } catch (PublicKeyNotFoundException ex) { logger.Debug(ex.ToString()); Context = Crypto.Context; var message = "** " + Localized.MsgSigMissingPubKey + "\n\n"; if (mailType == Outlook.OlBodyFormat.olFormatPlain) mailItem.Body = message + mailItem.Body; else mailItem.HTMLBody = AddMessageToHtmlBody(mailItem.HTMLBody, message); } catch (Exception ex) { logger.Debug(ex.ToString()); WriteErrorData("VerifyEmail", ex); MessageBox.Show( ex.Message, Localized.ErrorDialogTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } return; } if (Context == null) return; // Extract files from MIME data MimeMessage msg = null; TextPart textPart = null; MimeEntity htmlPart = null; var isHtml = false; using(var sin = new MemoryStream(clearbytes)) { var parser = new MimeParser(sin); msg = parser.ParseMessage(); var iter = new MimeIterator(msg); while(iter.MoveNext()) { var part = iter.Current as TextPart; if (part == null) continue; if (part.IsAttachment) continue; // message could include both text and html // if we find html use that over text. if (part.IsHtml) { htmlPart = part; isHtml = true; } else { textPart = part; } } } var DecryptAndVerifyHeaderMessage = "** "; if (Context.IsEncrypted) DecryptAndVerifyHeaderMessage += Localized.MsgDecrypt + " "; if (Context.FailedIntegrityCheck) DecryptAndVerifyHeaderMessage += Localized.MsgFailedIntegrityCheck + " "; if (Context.IsSigned && Context.SignatureValidated) { DecryptAndVerifyHeaderMessage += string.Format(Localized.MsgValidSig, Context.SignedByUserId, Context.SignedByKeyId); } else if (Context.IsSigned) { DecryptAndVerifyHeaderMessage += string.Format(Localized.MsgInvalidSig, Context.SignedByUserId, Context.SignedByKeyId); } else DecryptAndVerifyHeaderMessage += Localized.MsgUnsigned; DecryptAndVerifyHeaderMessage += "\n\n"; if(isHtml) { var htmlBody = msg.HtmlBody; var related = msg.Body as MultipartRelated; var doc = new HtmlAgilityPack.HtmlDocument(); var savedImages = new List<MimePart>(); doc.LoadHtml(htmlBody); // Find any embedded images foreach (var img in doc.DocumentNode.SelectNodes("//img[@src]")) { var src = img.Attributes["src"]; Uri uri; if (src == null || src.Value == null) continue; // parse the <img src=...> attribute value into a Uri if (Uri.IsWellFormedUriString(src.Value, UriKind.Absolute)) uri = new Uri(src.Value, UriKind.Absolute); else uri = new Uri(src.Value, UriKind.Relative); // locate the index of the attachment within the multipart/related (if it exists) string imageCid = src.Value.Substring(4); var iter = new MimeIterator(msg); MimePart attachment = null; while (iter.MoveNext()) { if (iter.Current.ContentId == imageCid) { attachment = iter.Current as MimePart; break; } } if (attachment == null) continue; string fileName; // save the attachment (if we haven't already saved it) if (!savedImages.Contains(attachment)) { fileName = attachment.FileName; if (string.IsNullOrEmpty(fileName)) fileName = Guid.NewGuid().ToString(); using (var stream = File.Create(fileName)) attachment.ContentObject.DecodeTo(stream); try { var att = mailItem.Attachments.Add(fileName, Outlook.OlAttachmentType.olEmbeddeditem, null, ""); att.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E", imageCid); savedImages.Add(attachment); } finally { // try not to leak temp files :) File.Delete(fileName); } } } mailItem.BodyFormat = Outlook.OlBodyFormat.olFormatHTML; mailItem.HTMLBody = AddMessageToHtmlBody(htmlBody, DecryptAndVerifyHeaderMessage); } else { // NOTE: For some reason we cannot change the BodyFormat once it's set. // So if we are set to HTML we need to wrap the plain text so it's // displayed okay. Also of course prevent XSS. if (mailItem.BodyFormat == Outlook.OlBodyFormat.olFormatPlain) { mailItem.Body = DecryptAndVerifyHeaderMessage + msg.TextBody; } else { var sb = new StringBuilder(msg.TextBody.Length + 100); sb.Append("<html><body><pre>"); sb.Append(WebUtility.HtmlEncode(DecryptAndVerifyHeaderMessage)); sb.Append(WebUtility.HtmlEncode(msg.TextBody)); sb.Append("</pre></body></html>"); mailItem.HTMLBody = sb.ToString(); } } // NOTE: Removing existing attachments is perminant, even if the message // is not saved. foreach (var mimeAttachment in msg.Attachments) { var fileName = mimeAttachment.FileName; var tempFile = Path.Combine(Path.GetTempPath(), fileName); using (var fout = File.OpenWrite(tempFile)) { mimeAttachment.ContentObject.DecodeTo(fout); } mailItem.Attachments.Add(tempFile, Outlook.OlAttachmentType.olByValue, 1, fileName); File.Delete(tempFile); } }
/// <summary> /// Load a <see cref="MimeMessage"/> from the specified stream. /// </summary> /// <returns>The parsed message.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> public static MimeMessage Load(ParserOptions options, Stream stream) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); var parser = new MimeParser (options, stream, MimeFormat.Entity); return parser.ParseMessage (); }
public MimeMessage ParseMessage(Stream stream, bool persistent, CancellationToken cancellationToken) { if (parser == null) parser = new MimeParser (ParserOptions.Default, stream, persistent); else parser.SetStream (ParserOptions.Default, stream, persistent); return parser.ParseMessage (cancellationToken); }
public void TestJwzMbox () { var summary = File.ReadAllText (Path.Combine (MboxDataDir, "jwz-summary.txt")).Replace ("\r\n", "\n"); var options = FormatOptions.Default.Clone (); var original = new MemoryBlockStream (); var output = new MemoryBlockStream (); var builder = new StringBuilder (); var expected = new byte[4096]; var buffer = new byte[4096]; int nx, n; options.NewLineFormat = NewLineFormat.Unix; using (var stream = File.OpenRead (Path.Combine (MboxDataDir, "jwz.mbox.txt"))) { var parser = new MimeParser (stream, MimeFormat.Mbox); int count = 0; while (!parser.IsEndOfStream) { var message = parser.ParseMessage (); builder.AppendFormat ("{0}", parser.MboxMarker).Append ('\n'); if (message.From.Count > 0) builder.AppendFormat ("From: {0}", message.From).Append ('\n'); if (message.To.Count > 0) builder.AppendFormat ("To: {0}", message.To).Append ('\n'); builder.AppendFormat ("Subject: {0}", message.Subject).Append ('\n'); builder.AppendFormat ("Date: {0}", DateUtils.FormatDate (message.Date)).Append ('\n'); DumpMimeTree (builder, message); builder.Append ('\n'); var marker = Encoding.UTF8.GetBytes ((count > 0 ? "\n" : string.Empty) + parser.MboxMarker + "\n"); output.Write (marker, 0, marker.Length); message.WriteTo (options, output); count++; } } string actual = builder.ToString (); // WORKAROUND: Mono's iso-2022-jp decoder breaks on this input in versions <= 3.2.3 but is fixed in 3.2.4+ string iso2022jp = Encoding.GetEncoding ("iso-2022-jp").GetString (Convert.FromBase64String ("GyRAOjRGI0stGyhK")); if (iso2022jp != "佐藤豊") actual = actual.Replace (iso2022jp, "佐藤豊"); Assert.AreEqual (summary, actual, "Summaries do not match for jwz.mbox"); using (var stream = File.OpenRead (Path.Combine (MboxDataDir, "jwz.mbox.txt"))) { using (var filtered = new FilteredStream (original)) { filtered.Add (new Dos2UnixFilter ()); stream.CopyTo (filtered); filtered.Flush (); } } original.Position = 0; output.Position = 0; Assert.AreEqual (original.Length, output.Length, "The length of the mbox did not match."); do { var position = original.Position; nx = original.Read (expected, 0, expected.Length); n = output.Read (buffer, 0, buffer.Length); if (nx == 0) break; for (int i = 0; i < nx; i++) { if (buffer[i] == expected[i]) continue; var strExpected = CharsetUtils.Latin1.GetString (expected, 0, nx); var strActual = CharsetUtils.Latin1.GetString (buffer, 0, n); Assert.AreEqual (strExpected, strActual, "The mbox differs at position {0}", position + i); } } while (true); }
public void TestUnmungedFromLines () { int count = 0; using (var stream = File.OpenRead ("../../TestData/mbox/unmunged.mbox.txt")) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { parser.ParseMessage (); var marker = parser.MboxMarker; if ((count % 2) == 0) { Assert.AreEqual ("From -", marker.TrimEnd (), "Message #{0}", count); } else { Assert.AreEqual ("From Russia with love", marker.TrimEnd (), "Message #{0}", count); } count++; } } Assert.AreEqual (4, count, "Expected to find 4 messages."); }
public void TestSecureMimeVerifyThunderbird () { MimeMessage message; using (var file = File.OpenRead (Path.Combine ("..", "..", "TestData", "smime", "thunderbird-signed.txt"))) { var parser = new MimeParser (file, MimeFormat.Default); message = parser.ParseMessage (); } using (var ctx = CreateContext ()) { var multipart = (MultipartSigned) message.Body; var protocol = multipart.ContentType.Parameters["protocol"]; Assert.IsTrue (ctx.Supports (protocol), "The multipart/signed protocol is not supported."); Assert.IsInstanceOfType (typeof (ApplicationPkcs7Signature), multipart[1], "The second child is not a detached signature."); var signatures = multipart.Verify (ctx); Assert.AreEqual (1, signatures.Count, "Verify returned an unexpected number of signatures."); foreach (var signature in signatures) { try { bool valid = signature.Verify (); Assert.IsTrue (valid, "Bad signature from {0}", signature.SignerCertificate.Email); } catch (DigitalSignatureVerifyException ex) { Assert.Fail ("Failed to verify signature: {0}", ex); } var algorithms = ((SecureMimeDigitalSignature) signature).EncryptionAlgorithms; Assert.AreEqual (EncryptionAlgorithm.Aes256, algorithms[0], "Expected AES-256 capability"); Assert.AreEqual (EncryptionAlgorithm.Aes128, algorithms[1], "Expected AES-128 capability"); Assert.AreEqual (EncryptionAlgorithm.TripleDes, algorithms[2], "Expected Triple-DES capability"); Assert.AreEqual (EncryptionAlgorithm.RC2128, algorithms[3], "Expected RC2-128 capability"); Assert.AreEqual (EncryptionAlgorithm.RC264, algorithms[4], "Expected RC2-64 capability"); Assert.AreEqual (EncryptionAlgorithm.Des, algorithms[5], "Expected DES capability"); Assert.AreEqual (EncryptionAlgorithm.RC240, algorithms[6], "Expected RC2-40 capability"); } } }
public void TestEmptyMultipartAlternative () { string expected = @"Content-Type: multipart/mixed Content-Type: multipart/alternative Content-Type: text/plain ".Replace ("\r\n", "\n"); using (var stream = File.OpenRead (Path.Combine (MessagesDataDir, "empty-multipart.txt"))) { var parser = new MimeParser (stream, MimeFormat.Entity); var message = parser.ParseMessage (); var builder = new StringBuilder (); DumpMimeTree (builder, message); Assert.AreEqual (expected, builder.ToString (), "Unexpected MIME tree structure."); } }
public void TestReserializationEmptyParts () { string rawMessageText = @"Date: Fri, 22 Jan 2016 8:44:05 -0500 (EST) From: MimeKit Unit Tests <*****@*****.**> To: MimeKit Unit Tests <*****@*****.**> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary=""Interpart.Boundary.IeCBvV20M2YtEoUA0A"" Subject: Reserialization test of empty mime parts THIS IS A MESSAGE IN 'MIME' FORMAT. Your mail reader does not support MIME. Please read the first section, which is plain text, and ignore the rest. --Interpart.Boundary.IeCBvV20M2YtEoUA0A Content-type: text/plain; charset=US-ASCII This is the body. --Interpart.Boundary.IeCBvV20M2YtEoUA0A Content-type: text/plain; charset=US-ASCII; name=empty.txt Content-Description: this part contains no content --Interpart.Boundary.IeCBvV20M2YtEoUA0A Content-type: text/plain; charset=US-ASCII; name=blank-line.txt Content-Description: this part contains a single blank line --Interpart.Boundary.IeCBvV20M2YtEoUA0A-- ".Replace ("\r\n", "\n"); string result; using (var source = new MemoryStream (Encoding.UTF8.GetBytes (rawMessageText))) { var parser = new MimeParser (source, MimeFormat.Default); var message = parser.ParseMessage (); using (var serialized = new MemoryStream ()) { var options = FormatOptions.Default.Clone (); options.NewLineFormat = NewLineFormat.Unix; message.WriteTo (options, serialized); result = Encoding.UTF8.GetString (serialized.ToArray ()); } } Assert.AreEqual (rawMessageText, result, "Reserialized message is not identical to the original."); }
public void TestSecureMimeDecryptVerifyThunderbird () { var p12 = Path.Combine ("..", "..", "TestData", "smime", "gnome.p12"); MimeMessage message; if (!File.Exists (p12)) return; using (var file = File.OpenRead (Path.Combine ("..", "..", "TestData", "smime", "thunderbird-signed-encrypted.txt"))) { var parser = new MimeParser (file, MimeFormat.Default); message = parser.ParseMessage (); } using (var ctx = CreateContext ()) { var encrypted = (ApplicationPkcs7Mime) message.Body; MimeEntity decrypted = null; using (var file = File.OpenRead (p12)) { ctx.Import (file, "no.secret"); } var type = encrypted.ContentType.Parameters["smime-type"]; Assert.AreEqual ("enveloped-data", type, "Unexpected smime-type parameter."); try { decrypted = encrypted.Decrypt (ctx); } catch (Exception ex) { Console.WriteLine (ex); Assert.Fail ("Failed to decrypt thunderbird message: {0}", ex); } // The decrypted part should be a multipart/signed Assert.IsInstanceOfType (typeof (MultipartSigned), decrypted, "Expected the decrypted part to be a multipart/signed."); var signed = (MultipartSigned) decrypted; // The first part of the multipart/signed should be a multipart/mixed with a text/plain part and 2 image attachments, // very much like the thunderbird-signed.txt message. Assert.IsInstanceOfType (typeof (Multipart), signed[0], "Expected the first part of the multipart/signed to be a multipart."); Assert.IsInstanceOfType (typeof (ApplicationPkcs7Signature), signed[1], "Expected second part of the multipart/signed to be a pkcs7-signature."); var multipart = (Multipart) signed[0]; Assert.IsInstanceOfType (typeof (TextPart), multipart[0], "Expected the first part of the decrypted multipart to be a TextPart."); Assert.IsInstanceOfType (typeof (MimePart), multipart[1], "Expected the second part of the decrypted multipart to be a MimePart."); Assert.IsInstanceOfType (typeof (MimePart), multipart[2], "Expected the third part of the decrypted multipart to be a MimePart."); var signatures = signed.Verify (ctx); Assert.AreEqual (1, signatures.Count, "Verify returned an unexpected number of signatures."); foreach (var signature in signatures) { try { bool valid = signature.Verify (); Assert.IsTrue (valid, "Bad signature from {0}", signature.SignerCertificate.Email); } catch (DigitalSignatureVerifyException ex) { Assert.Fail ("Failed to verify signature: {0}", ex); } var algorithms = ((SecureMimeDigitalSignature) signature).EncryptionAlgorithms; Assert.AreEqual (EncryptionAlgorithm.Aes256, algorithms[0], "Expected AES-256 capability"); Assert.AreEqual (EncryptionAlgorithm.Aes128, algorithms[1], "Expected AES-128 capability"); Assert.AreEqual (EncryptionAlgorithm.TripleDes, algorithms[2], "Expected Triple-DES capability"); Assert.AreEqual (EncryptionAlgorithm.RC2128, algorithms[3], "Expected RC2-128 capability"); Assert.AreEqual (EncryptionAlgorithm.RC264, algorithms[4], "Expected RC2-64 capability"); Assert.AreEqual (EncryptionAlgorithm.Des, algorithms[5], "Expected DES capability"); Assert.AreEqual (EncryptionAlgorithm.RC240, algorithms[6], "Expected RC2-40 capability"); } } }
/// <summary> /// Join the specified message/partial parts into the complete message. /// </summary> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> public static MimeMessage Join(ParserOptions options, IEnumerable<MessagePartial> partials) { if (options == null) throw new ArgumentNullException ("options"); if (partials == null) throw new ArgumentNullException ("partials"); var parts = partials.ToList (); if (parts.Count == 0) return null; parts.Sort (PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) throw new ArgumentException ("partials"); int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) throw new ArgumentException ("partials"); string id = parts[0].Id; using (var chained = new ChainedStream ()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) throw new ArgumentException ("partials"); var content = parts[i].ContentObject; content.Stream.Seek (0, SeekOrigin.Begin); var filtered = new FilteredStream (content.Stream); filtered.Add (DecoderFilter.Create (content.Encoding)); chained.Add (filtered); } var parser = new MimeParser (options, chained); return parser.ParseMessage (); } }
public void TestJwzPersistentMbox () { var summary = File.ReadAllText ("../../TestData/mbox/jwz-summary.txt").Replace ("\r\n", "\n"); var builder = new StringBuilder (); using (var stream = File.OpenRead ("../../TestData/mbox/jwz.mbox.txt")) { var parser = new MimeParser (stream, MimeFormat.Mbox); while (!parser.IsEndOfStream) { var message = parser.ParseMessage (); builder.AppendFormat ("{0}", parser.MboxMarker).Append ('\n'); if (message.From.Count > 0) builder.AppendFormat ("From: {0}", message.From).Append ('\n'); if (message.To.Count > 0) builder.AppendFormat ("To: {0}", message.To).Append ('\n'); builder.AppendFormat ("Subject: {0}", message.Subject).Append ('\n'); builder.AppendFormat ("Date: {0}", DateUtils.FormatDate (message.Date)).Append ('\n'); DumpMimeTree (builder, message); builder.Append ('\n'); // Force the various MimePart objects to write their content streams. // The idea is that by forcing the MimeParts to seek in their content, // we will test to make sure that parser correctly deals with it. message.WriteTo (Stream.Null); } } string actual = builder.ToString (); // WORKAROUND: Mono's iso-2022-jp decoder breaks on this input in versions <= 3.2.3 but is fixed in 3.2.4+ string iso2022jp = Encoding.GetEncoding ("iso-2022-jp").GetString (Convert.FromBase64String ("GyRAOjRGI0stGyhK")); if (iso2022jp != "佐藤豊") actual = actual.Replace (iso2022jp, "佐藤豊"); Assert.AreEqual (summary, actual, "Summaries do not match for jwz.mbox"); }