From ee8e536bf499e10a59cda568e1dc2cae662999b9 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Wed, 21 Aug 2019 07:58:32 -0400 Subject: [PATCH] Fixed Multipart to properly ensure the epilogue ends w/ a new-line when EnsureNewLine is true Partial fix for issue #499 --- MimeKit/Multipart.cs | 16 +++---- UnitTests/MimeMessageTests.cs | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/MimeKit/Multipart.cs b/MimeKit/Multipart.cs index e3f0149792..300b402cc2 100644 --- a/MimeKit/Multipart.cs +++ b/MimeKit/Multipart.cs @@ -356,10 +356,10 @@ internal static string FoldPreambleOrEpilogue (FormatOptions options, string tex return builder.ToString (); } - static void WriteBytes (FormatOptions options, Stream stream, byte[] bytes, CancellationToken cancellationToken) + static void WriteBytes (FormatOptions options, Stream stream, byte[] bytes, bool ensureNewLine, CancellationToken cancellationToken) { var cancellable = stream as ICancellableStream; - var filter = options.CreateNewLineFilter (); + var filter = options.CreateNewLineFilter (ensureNewLine); int index, length; var output = filter.Flush (bytes, 0, bytes.Length, out index, out length); @@ -372,9 +372,9 @@ static void WriteBytes (FormatOptions options, Stream stream, byte[] bytes, Canc } } - static Task WriteBytesAsync (FormatOptions options, Stream stream, byte[] bytes, CancellationToken cancellationToken) + static Task WriteBytesAsync (FormatOptions options, Stream stream, byte[] bytes, bool ensureNewLine, CancellationToken cancellationToken) { - var filter = options.CreateNewLineFilter (); + var filter = options.CreateNewLineFilter (ensureNewLine); int index, length; var output = filter.Flush (bytes, 0, bytes.Length, out index, out length); @@ -444,7 +444,7 @@ public override void Prepare (EncodingConstraint constraint, int maxLineLength = var cancellable = stream as ICancellableStream; if (RawPreamble != null && RawPreamble.Length > 0) - WriteBytes (options, stream, RawPreamble, cancellationToken); + WriteBytes (options, stream, RawPreamble, true, cancellationToken); var boundary = Encoding.ASCII.GetBytes ("--" + Boundary + "--"); @@ -513,7 +513,7 @@ public override void Prepare (EncodingConstraint constraint, int maxLineLength = } if (RawEpilogue != null && RawEpilogue.Length > 0) - WriteBytes (options, stream, RawEpilogue, cancellationToken); + WriteBytes (options, stream, RawEpilogue, EnsureNewLine, cancellationToken); } /// @@ -555,7 +555,7 @@ public override void Prepare (EncodingConstraint constraint, int maxLineLength = } if (RawPreamble != null && RawPreamble.Length > 0) - await WriteBytesAsync (options, stream, RawPreamble, cancellationToken).ConfigureAwait (false); + await WriteBytesAsync (options, stream, RawPreamble, true, cancellationToken).ConfigureAwait (false); var boundary = Encoding.ASCII.GetBytes ("--" + Boundary + "--"); @@ -589,7 +589,7 @@ public override void Prepare (EncodingConstraint constraint, int maxLineLength = await stream.WriteAsync (options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait (false); if (RawEpilogue != null && RawEpilogue.Length > 0) - await WriteBytesAsync (options, stream, RawEpilogue, cancellationToken).ConfigureAwait (false); + await WriteBytesAsync (options, stream, RawEpilogue, EnsureNewLine, cancellationToken).ConfigureAwait (false); } #region ICollection implementation diff --git a/UnitTests/MimeMessageTests.cs b/UnitTests/MimeMessageTests.cs index 18872ff7a4..38c4350cfb 100644 --- a/UnitTests/MimeMessageTests.cs +++ b/UnitTests/MimeMessageTests.cs @@ -398,6 +398,85 @@ ENCODING mime } } + [Test] + public async Task TestReserializationEpilogue () + { + string rawMessageText = @"From: Example Test +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary=""simple boundary"" + +This is the preamble. + +--simple boundary +Content-TypeS: text/plain + +This is a test. + +--simple boundary +Content-Type: text/plain +Content-Disposition: attachment +Content-Transfer-Encoding: 7bit + +Another test. + +--simple boundary-- + + +This is the epilogue.".Replace ("\r\n", "\n"); + + 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); + + var result = Encoding.UTF8.GetString (serialized.ToArray ()); + + Assert.AreEqual (rawMessageText, result, "Reserialized message is not identical to the original."); + } + + using (var serialized = new MemoryStream ()) { + var options = FormatOptions.Default.Clone (); + options.NewLineFormat = NewLineFormat.Unix; + + await message.WriteToAsync (options, serialized); + + var result = Encoding.UTF8.GetString (serialized.ToArray ()); + + Assert.AreEqual (rawMessageText, result, "Reserialized (async) message is not identical to the original."); + } + + using (var serialized = new MemoryStream ()) { + var options = FormatOptions.Default.Clone (); + options.NewLineFormat = NewLineFormat.Unix; + options.EnsureNewLine = true; + + message.WriteTo (options, serialized); + + var result = Encoding.UTF8.GetString (serialized.ToArray ()); + + Assert.AreEqual (rawMessageText + "\n", result, "Reserialized message is not identical to the original (EnsureNewLine)."); + } + + using (var serialized = new MemoryStream ()) { + var options = FormatOptions.Default.Clone (); + options.NewLineFormat = NewLineFormat.Unix; + options.EnsureNewLine = true; + + await message.WriteToAsync (options, serialized); + + var result = Encoding.UTF8.GetString (serialized.ToArray ()); + + Assert.AreEqual (rawMessageText + "\n", result, "Reserialized (async) message is not identical to the original (EnsureNewLine)."); + } + } + } + [Test] public void TestMailMessageToMimeMessage () {