diff --git a/MimeKit/AsyncMimeReader.cs b/MimeKit/AsyncMimeReader.cs index ad94fb2041..a6ff259d2d 100644 --- a/MimeKit/AsyncMimeReader.cs +++ b/MimeKit/AsyncMimeReader.cs @@ -157,13 +157,15 @@ async Task StepHeadersAsync (CancellationToken cancellationToken) left = await ReadAheadAsync (2, 0, cancellationToken).ConfigureAwait (false); if (left == 0) { - if (toplevel && headerCount == 0 && headerBlockBegin == GetOffset (inputIndex)) { + // Note: The only way to get here is if this is the first-pass throgh this loop and we're at EOF, so headerCount should ALWAYS be 0. + + if (toplevel && headerCount == 0) { // EOF has been reached before any headers have been parsed for Parse[Headers,Entity,Message]Async. state = MimeParserState.Eos; return; } - // FIXME: Should this be Content or Error? + // Note: This can happen if a message is truncated immediately after a boundary marker (e.g. where subpart headers would begin). state = MimeParserState.Content; break; } diff --git a/MimeKit/MimeReader.cs b/MimeKit/MimeReader.cs index ab3a3c9e1d..ff4751edb9 100644 --- a/MimeKit/MimeReader.cs +++ b/MimeKit/MimeReader.cs @@ -1698,13 +1698,15 @@ unsafe void StepHeaders (byte* inbuf, CancellationToken cancellationToken) left = ReadAhead (2, 0, cancellationToken); if (left == 0) { - if (toplevel && headerCount == 0 && headerBlockBegin == GetOffset (inputIndex)) { + // Note: The only way to get here is if this is the first-pass throgh this loop and we're at EOF, so headerCount should ALWAYS be 0. + + if (toplevel && headerCount == 0) { // EOF has been reached before any headers have been parsed for Parse[Headers,Entity,Message]. state = MimeParserState.Eos; return; } - // FIXME: Should this be Content or Error? + // Note: This can happen if a message is truncated immediately after a boundary marker (e.g. where subpart headers would begin). state = MimeParserState.Content; break; } diff --git a/UnitTests/ExperimentalMimeParserTests.cs b/UnitTests/ExperimentalMimeParserTests.cs index 74c82ee1b6..1628820b79 100644 --- a/UnitTests/ExperimentalMimeParserTests.cs +++ b/UnitTests/ExperimentalMimeParserTests.cs @@ -941,6 +941,94 @@ This is the message body. } } + [Test] + public void TestMultipartTruncatedImmediatelyAfterBoundary () + { + string text = @"From: mimekit@example.com +To: mimekit@example.com +Subject: test of multipart boundary w/o trailing newline +Date: Tue, 12 Nov 2013 09:12:42 -0500 +MIME-Version: 1.0 +Message-ID: <54AD68C9E3B0184CAC6041320424FD1B5B81E74D@localhost.localdomain> +X-Mailer: Microsoft Office Outlook 12.0 +Content-Type: multipart/mixed; + boundary=""----=_NextPart_000_003F_01CE98CE.6E826F90"" + + +------=_NextPart_000_003F_01CE98CE.6E826F90 +".Replace ("\r\n", "\n"); + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) { + var parser = new ExperimentalMimeParser (stream, MimeFormat.Entity); + var message = parser.ParseMessage (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text.Replace ("\n", "\r\n")), false)) { + var parser = new ExperimentalMimeParser (stream, MimeFormat.Entity); + var message = parser.ParseMessage (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + } + + [Test] + public async Task TestMultipartTruncatedImmediatelyAfterBoundaryAsync () + { + string text = @"From: mimekit@example.com +To: mimekit@example.com +Subject: test of multipart boundary w/o trailing newline +Date: Tue, 12 Nov 2013 09:12:42 -0500 +MIME-Version: 1.0 +Message-ID: <54AD68C9E3B0184CAC6041320424FD1B5B81E74D@localhost.localdomain> +X-Mailer: Microsoft Office Outlook 12.0 +Content-Type: multipart/mixed; + boundary=""----=_NextPart_000_003F_01CE98CE.6E826F90"" + + +------=_NextPart_000_003F_01CE98CE.6E826F90 +".Replace ("\r\n", "\n"); + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) { + var parser = new ExperimentalMimeParser (stream, MimeFormat.Entity); + var message = await parser.ParseMessageAsync (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text.Replace ("\n", "\r\n")), false)) { + var parser = new ExperimentalMimeParser (stream, MimeFormat.Entity); + var message = await parser.ParseMessageAsync (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + } + [Test] public void TestMultipartBoundaryWithoutTrailingNewline () { diff --git a/UnitTests/MimeParserTests.cs b/UnitTests/MimeParserTests.cs index 18cfae2ac7..b8f80dedfc 100644 --- a/UnitTests/MimeParserTests.cs +++ b/UnitTests/MimeParserTests.cs @@ -933,6 +933,94 @@ This is the message body. } } + [Test] + public void TestMultipartTruncatedImmediatelyAfterBoundary () + { + string text = @"From: mimekit@example.com +To: mimekit@example.com +Subject: test of multipart boundary w/o trailing newline +Date: Tue, 12 Nov 2013 09:12:42 -0500 +MIME-Version: 1.0 +Message-ID: <54AD68C9E3B0184CAC6041320424FD1B5B81E74D@localhost.localdomain> +X-Mailer: Microsoft Office Outlook 12.0 +Content-Type: multipart/mixed; + boundary=""----=_NextPart_000_003F_01CE98CE.6E826F90"" + + +------=_NextPart_000_003F_01CE98CE.6E826F90 +".Replace ("\r\n", "\n"); + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) { + var parser = new MimeParser (stream, MimeFormat.Entity); + var message = parser.ParseMessage (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text.Replace ("\n", "\r\n")), false)) { + var parser = new MimeParser (stream, MimeFormat.Entity); + var message = parser.ParseMessage (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + } + + [Test] + public async Task TestMultipartTruncatedImmediatelyAfterBoundaryAsync () + { + string text = @"From: mimekit@example.com +To: mimekit@example.com +Subject: test of multipart boundary w/o trailing newline +Date: Tue, 12 Nov 2013 09:12:42 -0500 +MIME-Version: 1.0 +Message-ID: <54AD68C9E3B0184CAC6041320424FD1B5B81E74D@localhost.localdomain> +X-Mailer: Microsoft Office Outlook 12.0 +Content-Type: multipart/mixed; + boundary=""----=_NextPart_000_003F_01CE98CE.6E826F90"" + + +------=_NextPart_000_003F_01CE98CE.6E826F90 +".Replace ("\r\n", "\n"); + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) { + var parser = new MimeParser (stream, MimeFormat.Entity); + var message = await parser.ParseMessageAsync (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + + using (var stream = new MemoryStream (Encoding.ASCII.GetBytes (text.Replace ("\n", "\r\n")), false)) { + var parser = new MimeParser (stream, MimeFormat.Entity); + var message = await parser.ParseMessageAsync (); + + Assert.That (message.Body, Is.InstanceOf (), "Expected top-level to be a multipart"); + var multipart = (Multipart) message.Body; + Assert.That (multipart.Count, Is.EqualTo (1)); + Assert.That (multipart[0], Is.InstanceOf (), "Expected first child of the multipart to be text/plain"); + var body = (TextPart) multipart[0]; + + Assert.That (body.Text, Is.EqualTo (string.Empty)); + } + } + [Test] public void TestMultipartBoundaryWithoutTrailingNewline () {