diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f80c543..21d4a905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This version includes **multiple breaking changes**. If you are upgrading from v - JMAP: - Invalid `receivedAt` headers after importing (#2939). - Sorting order issues when emails lack `receivedAt` headers. +- IMAP: Fix `BINARY` fetch responses (#2940). - ACME: Allow requesting apex domain certificates. - Reverse proxy issues. - OSS builds. diff --git a/crates/email/src/message/metadata.rs b/crates/email/src/message/metadata.rs index de55e88b..2fcf4263 100644 --- a/crates/email/src/message/metadata.rs +++ b/crates/email/src/message/metadata.rs @@ -309,6 +309,20 @@ impl<'x> DecodedParts<'x> { DecodedPartContent::Binary(binary) => binary.as_ref(), }) } + + #[inline] + pub fn transfer_decoded_contents( + &self, + message_id: usize, + part: &ArchivedMessageMetadataPart, + ) -> Option> { + match self.raw_messages.get(message_id)? { + DecodedRawMessage::Borrowed(chain) => Some(part.contents(chain)), + DecodedRawMessage::Owned(vec) => Some(Cow::Owned( + part.contents(&ChainedBytes::new(vec)).into_owned(), + )), + } + } } impl DecodedPartContent<'_> { diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index 4f2b371f..f0ce65ce 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -1077,12 +1077,12 @@ impl AsImapDataItem for ArchivedMessageMetadata { let part_offset = u32::from(part.offset_header) as usize; Ok(match &part.body { ArchivedMetadataPartType::Text | ArchivedMetadataPartType::Html => { - BodyContents::Text(String::from_utf8_lossy(get_partial_bytes( + BodyContents::Bytes(get_cow_partial_bytes( decoded - .binary_part(message_id, part_offset) + .transfer_decoded_contents(message_id, part) .unwrap_or_default(), partial, - ))) + )) .into() } ArchivedMetadataPartType::Binary | ArchivedMetadataPartType::InlineBinary => { @@ -1152,10 +1152,11 @@ impl AsImapDataItem for ArchivedMessageMetadata { } match &part.body { - ArchivedMetadataPartType::Text - | ArchivedMetadataPartType::Html - | ArchivedMetadataPartType::Binary - | ArchivedMetadataPartType::InlineBinary => decoded + ArchivedMetadataPartType::Text | ArchivedMetadataPartType::Html => decoded + .transfer_decoded_contents(message_id, part) + .map(|p| p.len()) + .unwrap_or_default(), + ArchivedMetadataPartType::Binary | ArchivedMetadataPartType::InlineBinary => decoded .part(message_id, u32::from(part.offset_header) as usize) .map(|p| p.len()) .unwrap_or_default(), diff --git a/resources/schema/schema.json.gz b/resources/schema/schema.json.gz index 79e03cf3..38e993b6 100644 Binary files a/resources/schema/schema.json.gz and b/resources/schema/schema.json.gz differ diff --git a/resources/schema/schema.json.sha256 b/resources/schema/schema.json.sha256 index d0d93ad4..79b6bb88 100644 --- a/resources/schema/schema.json.sha256 +++ b/resources/schema/schema.json.sha256 @@ -1 +1 @@ -aJJKvnpsjjwAEzJ0eKDWdfSLK_RvBKsLhk8BwCq-7qA \ No newline at end of file +_pIIRU33XTKDaP9kLFoPsYXayXbaRi79QFBUrdgixCY \ No newline at end of file diff --git a/tests/resources/imap/000.imap b/tests/resources/imap/000.imap index aeb40d3d..7ca76675 100644 --- a/tests/resources/imap/000.imap +++ b/tests/resources/imap/000.imap @@ -169,8 +169,81 @@ Content-Transfer-Encoding: Quoted-printable --unique-boundary-1-- -BINARY[] {16} -[binary content] +BINARY[] {1854} +MIME-Version: 1.0 +From: Nathaniel Borenstein +To: Ned Freed +Date: Fri, 07 Oct 1994 16:15:05 -0700 (PDT) +Subject: A multipart example +Content-Type: multipart/mixed; + boundary=unique-boundary-1 + +This is the preamble area of a multipart message. +Mail readers that understand multipart format +should ignore this preamble. + +If you are reading this text, you might want to +consider changing to a mail reader that understands +how to properly display multipart messages. + +--unique-boundary-1 + +... Some text appears here ... + +[Note that the blank between the boundary and the start +of the text in this part means no header fields were +given and this is text in the US-ASCII character set. +It could have been done with explicit typing as in the +next part.] + +--unique-boundary-1 +Content-type: text/plain; charset=US-ASCII + +This could have been part of the previous part, but +illustrates explicit versus implicit typing of body +parts. + +--unique-boundary-1 +Content-Type: multipart/parallel; boundary=unique-boundary-2 + +--unique-boundary-2 +Content-Type: audio/basic +Content-Transfer-Encoding: base64 + +... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here ... + +--unique-boundary-2 +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 + +... base64-encoded image data goes here ... + +--unique-boundary-2-- + +--unique-boundary-1 +Content-type: text/enriched + +This is enriched. +as defined in RFC 1896 + +Isn't it +cool? + +--unique-boundary-1 +Content-Type: message/rfc822 + +From: (mailbox in US-ASCII) +To: (address in US-ASCII) +Subject: (subject in US-ASCII) +Content-Type: Text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: Quoted-printable + +... Additional text in ISO-8859-1 goes here ... + +--unique-boundary-1-- + + BINARY.SIZE[] 1854 ---------------------------------- BODY[HEADER] {239} @@ -372,8 +445,24 @@ Content-Transfer-Encoding: base64 --unique-boundary-2-- -BINARY[3] {16} -[binary content] +BINARY[3] {376} +Content-Type: multipart/parallel; boundary=unique-boundary-2 + +--unique-boundary-2 +Content-Type: audio/basic +Content-Transfer-Encoding: base64 + +... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here ... + +--unique-boundary-2 +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 + +... base64-encoded image data goes here ... + +--unique-boundary-2-- + BINARY.SIZE[3] 376 ---------------------------------- BODY[3.HEADER] {62} @@ -525,8 +614,15 @@ Content-Transfer-Encoding: Quoted-printable ... Additional text in ISO-8859-1 goes here ... -BINARY[5] {16} -[binary content] +BINARY[5] {223} +From: (mailbox in US-ASCII) +To: (address in US-ASCII) +Subject: (subject in US-ASCII) +Content-Type: Text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: Quoted-printable + +... Additional text in ISO-8859-1 goes here ... + BINARY.SIZE[5] 223 ---------------------------------- BODY[5.HEADER] {175} diff --git a/tests/resources/imap/001.imap b/tests/resources/imap/001.imap index b16f8ce8..6d52a513 100644 --- a/tests/resources/imap/001.imap +++ b/tests/resources/imap/001.imap @@ -75,8 +75,49 @@ get RFC-MIME.DOC -BINARY[] {16} -[binary content] +BINARY[] {1109} +From: Whomever +To: Someone +Date: Whenever +Subject: whatever +MIME-Version: 1.0 +Message-ID: +Content-Type: multipart/alternative; boundary=42 +Content-ID: + +--42 +Content-Type: message/external-body; name="BodyFormats.ps"; + site="thumper.bellcore.com"; mode="image"; + access-type=ANON-FTP; directory="pub"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + +Content-type: application/postscript +Content-ID: + +--42 +Content-Type: message/external-body; access-type=local-file; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + +Content-type: application/postscript +Content-ID: + +--42 +Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + +Content-type: application/postscript +Content-ID: + +get RFC-MIME.DOC + +--42-- + + + BINARY.SIZE[] 1109 ---------------------------------- BODY[HEADER] {198} @@ -136,8 +177,10 @@ BODY[1] {79} Content-type: application/postscript Content-ID: -BINARY[1] {16} -[binary content] +BINARY[1] {79} +Content-type: application/postscript +Content-ID: + BINARY.SIZE[1] 79 ---------------------------------- BODY[1.HEADER] {230} @@ -165,16 +208,20 @@ BODY[1.1] {79} Content-type: application/postscript Content-ID: -BINARY[1.1] {16} -[binary content] +BINARY[1.1] {79} +Content-type: application/postscript +Content-ID: + BINARY.SIZE[1.1] 79 ---------------------------------- BODY[2] {79} Content-type: application/postscript Content-ID: -BINARY[2] {16} -[binary content] +BINARY[2] {79} +Content-type: application/postscript +Content-ID: + BINARY.SIZE[2] 79 ---------------------------------- BODY[2.HEADER] {218} @@ -202,8 +249,10 @@ BODY[2.1] {79} Content-type: application/postscript Content-ID: -BINARY[2.1] {16} -[binary content] +BINARY[2.1] {79} +Content-type: application/postscript +Content-ID: + BINARY.SIZE[2.1] 79 ---------------------------------- BODY[3] {97} @@ -212,8 +261,12 @@ Content-ID: get RFC-MIME.DOC -BINARY[3] {16} -[binary content] +BINARY[3] {97} +Content-type: application/postscript +Content-ID: + +get RFC-MIME.DOC + BINARY.SIZE[3] 97 ---------------------------------- BODY[3.HEADER] {181} @@ -245,8 +298,12 @@ Content-ID: get RFC-MIME.DOC -BINARY[3.1] {16} -[binary content] +BINARY[3.1] {97} +Content-type: application/postscript +Content-ID: + +get RFC-MIME.DOC + BINARY.SIZE[3.1] 97 ---------------------------------- BODY[HEADER.FIELDS (FROM TO)] {29} diff --git a/tests/resources/imap/002.imap b/tests/resources/imap/002.imap index f937fd70..ef8f3956 100644 --- a/tests/resources/imap/002.imap +++ b/tests/resources/imap/002.imap @@ -159,8 +159,81 @@ U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz --D7F------------D7FD5A0B8AB9C65CCDBFA872-- -BINARY[] {16} -[binary content] +BINARY[] {2652} +From: Al Gore +To: White House Transportation Coordinator + +Subject: [Fwd: Map of Argentina with Description] +Content-Type: multipart/mixed; + boundary="D7F------------D7FD5A0B8AB9C65CCDBFA872" + +This is a multi-part message in MIME format. +--D7F------------D7FD5A0B8AB9C65CCDBFA872 +Content-Type: text/plain; charset=us-ascii +Content-Transfer-Encoding: 7bit + +Fred, + +Fire up Air Force One! We're going South! + +Thanks, +Al +--D7F------------D7FD5A0B8AB9C65CCDBFA872 +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit +Content-Disposition: inline + +Return-Path: +Received: from mailhost.whitehouse.gov ([192.168.51.200]) + by heartbeat.whitehouse.gov (8.8.8/8.8.8) with ESMTP id SAA22453 + for ; + Mon, 13 Aug 1998 l8:14:23 +1000 +Received: from the_big_box.whitehouse.gov ([192.168.51.50]) + by mailhost.whitehouse.gov (8.8.8/8.8.7) with ESMTP id RAA20366 + for vice-president@whitehouse.gov; Mon, 13 Aug 1998 17:42:41 +1000 +Date: Mon, 13 Aug 1998 17:42:41 +1000 +Message-Id: <199804130742.RAA20366@mai1host.whitehouse.gov> +From: Bill Clinton +To: A1 (The Enforcer) Gore +Subject: Map of Argentina with Description +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="DC8------------DC8638F443D87A7F0726DEF7" + +This is a multi-part message in MIME format. +--DC8------------DC8638F443D87A7F0726DEF7 +Content-Type: text/plain; charset=us-ascii +Content-Transfer-Encoding: 7bit + +Hi A1, + +I finally figured out this MIME thing. Pretty cool. I'll send you +some sax music in .au files next week! + +Anyway, the attached image is really too small to get a good look at +Argentina. Try this for a much better map: + + http://www.1one1yp1anet.com/dest/sam/graphics/map-arg.htm + +Then again, shouldn't the CIA have something like that? + +Bill +--DC8------------DC8638F443D87A7F0726DEF7 +Content-Type: image/gif; name="map_of_Argentina.gif" +Content-Transfer-Encoding: base64 +Content-Disposition: inline; fi1ename="map_of_Argentina.gif" + +R01GOD1hJQA1AKIAAP/////78P/omn19fQAAAAAAAAAAAAAAACwAAAAAJQA1AAAD7Qi63P5w +wEmjBCLrnQnhYCgM1wh+pkgqqeC9XrutmBm7hAK3tP31gFcAiFKVQrGFR6kscnonTe7FAAad +GugmRu3CmiBt57fsVq3Y0VFKnpYdxPC6M7Ze4crnnHum4oN6LFJ1bn5NXTN7OF5fQkN5WYow +BEN2dkGQGWJtSzqGTICJgnQuTJN/WJsojad9qXMuhIWdjXKjY4tenjo6tjVssk2gaWq3uGNX +U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz +7itICBxISKDBgwgTKjyYAAA7 +--DC8------------DC8638F443D87A7F0726DEF7-- + +--D7F------------D7FD5A0B8AB9C65CCDBFA872-- + + BINARY.SIZE[] 2652 ---------------------------------- BODY[HEADER] {270} @@ -349,8 +422,55 @@ U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz 7itICBxISKDBgwgTKjyYAAA7 --DC8------------DC8638F443D87A7F0726DEF7-- -BINARY[2] {16} -[binary content] +BINARY[2] {1979} +Return-Path: +Received: from mailhost.whitehouse.gov ([192.168.51.200]) + by heartbeat.whitehouse.gov (8.8.8/8.8.8) with ESMTP id SAA22453 + for ; + Mon, 13 Aug 1998 l8:14:23 +1000 +Received: from the_big_box.whitehouse.gov ([192.168.51.50]) + by mailhost.whitehouse.gov (8.8.8/8.8.7) with ESMTP id RAA20366 + for vice-president@whitehouse.gov; Mon, 13 Aug 1998 17:42:41 +1000 +Date: Mon, 13 Aug 1998 17:42:41 +1000 +Message-Id: <199804130742.RAA20366@mai1host.whitehouse.gov> +From: Bill Clinton +To: A1 (The Enforcer) Gore +Subject: Map of Argentina with Description +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="DC8------------DC8638F443D87A7F0726DEF7" + +This is a multi-part message in MIME format. +--DC8------------DC8638F443D87A7F0726DEF7 +Content-Type: text/plain; charset=us-ascii +Content-Transfer-Encoding: 7bit + +Hi A1, + +I finally figured out this MIME thing. Pretty cool. I'll send you +some sax music in .au files next week! + +Anyway, the attached image is really too small to get a good look at +Argentina. Try this for a much better map: + + http://www.1one1yp1anet.com/dest/sam/graphics/map-arg.htm + +Then again, shouldn't the CIA have something like that? + +Bill +--DC8------------DC8638F443D87A7F0726DEF7 +Content-Type: image/gif; name="map_of_Argentina.gif" +Content-Transfer-Encoding: base64 +Content-Disposition: inline; fi1ename="map_of_Argentina.gif" + +R01GOD1hJQA1AKIAAP/////78P/omn19fQAAAAAAAAAAAAAAACwAAAAAJQA1AAAD7Qi63P5w +wEmjBCLrnQnhYCgM1wh+pkgqqeC9XrutmBm7hAK3tP31gFcAiFKVQrGFR6kscnonTe7FAAad +GugmRu3CmiBt57fsVq3Y0VFKnpYdxPC6M7Ze4crnnHum4oN6LFJ1bn5NXTN7OF5fQkN5WYow +BEN2dkGQGWJtSzqGTICJgnQuTJN/WJsojad9qXMuhIWdjXKjY4tenjo6tjVssk2gaWq3uGNX +U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz +7itICBxISKDBgwgTKjyYAAA7 +--DC8------------DC8638F443D87A7F0726DEF7-- + BINARY.SIZE[2] 1979 ---------------------------------- BODY[2.HEADER] {835} diff --git a/tests/resources/imap/003.imap b/tests/resources/imap/003.imap index 820a7157..6897def8 100644 --- a/tests/resources/imap/003.imap +++ b/tests/resources/imap/003.imap @@ -55,8 +55,33 @@ Content-Type: application/x-whatever --boundary42-- -BINARY[] {16} -[binary content] +BINARY[] {565} +From: Nathaniel Borenstein +To: Ned Freed +Date: Mon, 22 Mar 1993 09:41:09 -0800 (PST) +Subject: Formatted text mail +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary=boundary42 + +--boundary42 +Content-Type: text/plain; charset=us-ascii + +... plain text version of message goes here ... + +--boundary42 +Content-Type: text/enriched + +... RFC 1896 text/enriched version of same message + goes here ... + +--boundary42 +Content-Type: application/x-whatever + +... fanciest version of same message goes here ... + +--boundary42-- + + BINARY.SIZE[] 565 ---------------------------------- BODY[HEADER] {228} @@ -163,8 +188,9 @@ BINARY.SIZE[2.1] 69 BODY[3] {51} ... fanciest version of same message goes here ... -BINARY[3] {16} -[binary content] +BINARY[3] {51} +... fanciest version of same message goes here ... + BINARY.SIZE[3] 51 ---------------------------------- BODY[3.HEADER] {38} @@ -184,8 +210,9 @@ Content-Type: application/x-whatever BODY[3.1] {51} ... fanciest version of same message goes here ... -BINARY[3.1] {16} -[binary content] +BINARY[3.1] {51} +... fanciest version of same message goes here ... + BINARY.SIZE[3.1] 51 ---------------------------------- BODY[HEADER.FIELDS (FROM TO)] {81} diff --git a/tests/resources/imap/004.imap b/tests/resources/imap/004.imap index f42478a1..3afe3d32 100644 --- a/tests/resources/imap/004.imap +++ b/tests/resources/imap/004.imap @@ -140,8 +140,44 @@ Subject: my different opinion ------ main boundary ------ -BINARY[] {16} -[binary content] +BINARY[] {728} +From: Moderator-Address +To: Recipient-List +Date: Mon, 22 Mar 1994 13:34:51 +0000 +Subject: Internet Digest, volume 42 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="---- main boundary ----" + +------ main boundary ---- + +...Introductory text or table of contents... + +------ main boundary ---- +Content-Type: multipart/digest; + boundary="---- next message ----" + +------ next message ---- + +From: someone-else +Date: Fri, 26 Mar 1993 11:13:32 +0200 +Subject: my opinion + +...body goes here ... + +------ next message ---- + +From: someone-else-again +Date: Fri, 26 Mar 1993 10:07:13 -0500 +Subject: my different opinion + +... another body goes here ... + +------ next message ------ + +------ main boundary ------ + + BINARY.SIZE[] 728 ---------------------------------- BODY[HEADER] {214} @@ -239,8 +275,28 @@ Subject: my different opinion ------ next message ------ -BINARY[2] {16} -[binary content] +BINARY[2] {385} +Content-Type: multipart/digest; + boundary="---- next message ----" + +------ next message ---- + +From: someone-else +Date: Fri, 26 Mar 1993 11:13:32 +0200 +Subject: my opinion + +...body goes here ... + +------ next message ---- + +From: someone-else-again +Date: Fri, 26 Mar 1993 10:07:13 -0500 +Subject: my different opinion + +... another body goes here ... + +------ next message ------ + BINARY.SIZE[2] 385 ---------------------------------- BODY[2.HEADER] {79} @@ -282,8 +338,13 @@ Subject: my opinion ...body goes here ... -BINARY[2.1] {16} -[binary content] +BINARY[2.1] {100} +From: someone-else +Date: Fri, 26 Mar 1993 11:13:32 +0200 +Subject: my opinion + +...body goes here ... + BINARY.SIZE[2.1] 100 ---------------------------------- BODY[2.1.HEADER] {78} @@ -316,8 +377,13 @@ Subject: my different opinion ... another body goes here ... -BINARY[2.2] {16} -[binary content] +BINARY[2.2] {125} +From: someone-else-again +Date: Fri, 26 Mar 1993 10:07:13 -0500 +Subject: my different opinion + +... another body goes here ... + BINARY.SIZE[2.2] 125 ---------------------------------- BODY[2.2.HEADER] {94} diff --git a/tests/resources/imap/005.imap b/tests/resources/imap/005.imap index c41233e2..c68da4a8 100644 --- a/tests/resources/imap/005.imap +++ b/tests/resources/imap/005.imap @@ -51,8 +51,33 @@ It DOES end with a linebreak. This is the epilogue. It is also to be ignored. -BINARY[] {16} -[binary content] +BINARY[] {691} +From: Nathaniel Borenstein +To: Ned Freed +Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) +Subject: Sample message +MIME-Version: 1.0 +Content-type: multipart/mixed; boundary="simple boundary" + +This is the preamble. It is to be ignored, though it +is a handy place for composition agents to include an +explanatory note to non-MIME conformant readers. + +--simple boundary + +This is implicitly typed plain US-ASCII text. +It does NOT end with a linebreak. +--simple boundary +Content-type: text/plain; charset=us-ascii + +This is explicitly typed plain US-ASCII text. +It DOES end with a linebreak. + +--simple boundary-- + +This is the epilogue. It is also to be ignored. + + BINARY.SIZE[] 691 ---------------------------------- BODY[HEADER] {224} diff --git a/tests/resources/imap/006.imap b/tests/resources/imap/006.imap index a676c214..b8283be1 100644 --- a/tests/resources/imap/006.imap +++ b/tests/resources/imap/006.imap @@ -53,8 +53,31 @@ Content-Disposition: inline --boundary-string-- -BINARY[] {16} -[binary content] +BINARY[] {617} +From: sender@example.com +To: recipient@example.com +Subject: Multipart Email Example +Content-Type: multipart/alternative; boundary="boundary-string" + +--boundary-string +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: quoted-printable +Content-Disposition: inline + +Plain text email goes here! +This is the fallback if email client does not support HTML + +--boundary-string +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: quoted-printable +Content-Disposition: inline + +

This is the HTML Section!

+

This is what displays in most modern email clients

+ +--boundary-string-- + + BINARY.SIZE[] 617 ---------------------------------- BODY[HEADER] {149} diff --git a/tests/resources/imap/007.imap b/tests/resources/imap/007.imap index 13070ad2..b5ce88a5 100644 --- a/tests/resources/imap/007.imap +++ b/tests/resources/imap/007.imap @@ -215,8 +215,81 @@ K --1-- -BINARY[] {16} -[binary content] +BINARY[] {871} +Subject: RFC 8621 Section 4.1.4 test +Content-Type: multipart/mixed; boundary="1" + +--1 +Content-Type: text/plain +Content-Disposition: inline + +A +--1 +Content-Type: multipart/mixed; boundary="2" + +--2 +Content-Type: multipart/alternative; boundary="3" + +--3 +Content-Type: multipart/mixed; boundary="4" + +--4 +Content-Type: text/plain +Content-Disposition: inline + +B +--4 +Content-Type: image/jpeg +Content-Disposition: inline + +C +--4 +Content-Type: text/plain +Content-Disposition: inline + +D +--4-- + +--3 +Content-Type: multipart/related; boundary="5" + +--5 +Content-Type: text/html + +E +--5 +Content-Type: image/jpeg + +F +--5-- + +--3-- + +--2 +Content-Type: image/jpeg +Content-Disposition: attachment + +G +--2 +Content-Type: application/x-excel + +H +--2 +Content-Type: message/rfc822 + +Subject: J + +J +--2-- + +--1 +Content-Type: text/plain +Content-Disposition: inline + +K +--1-- + + BINARY.SIZE[] 871 ---------------------------------- BODY[HEADER] {82} @@ -386,8 +459,64 @@ Subject: J J --2-- -BINARY[2] {16} -[binary content] +BINARY[2] {653} +Content-Type: multipart/mixed; boundary="2" + +--2 +Content-Type: multipart/alternative; boundary="3" + +--3 +Content-Type: multipart/mixed; boundary="4" + +--4 +Content-Type: text/plain +Content-Disposition: inline + +B +--4 +Content-Type: image/jpeg +Content-Disposition: inline + +C +--4 +Content-Type: text/plain +Content-Disposition: inline + +D +--4-- + +--3 +Content-Type: multipart/related; boundary="5" + +--5 +Content-Type: text/html + +E +--5 +Content-Type: image/jpeg + +F +--5-- + +--3-- + +--2 +Content-Type: image/jpeg +Content-Disposition: attachment + +G +--2 +Content-Type: application/x-excel + +H +--2 +Content-Type: message/rfc822 + +Subject: J + +J +--2-- + BINARY.SIZE[2] 653 ---------------------------------- BODY[2.HEADER] {45} @@ -493,8 +622,44 @@ F --3-- -BINARY[2.1] {16} -[binary content] +BINARY[2.1] {437} +Content-Type: multipart/alternative; boundary="3" + +--3 +Content-Type: multipart/mixed; boundary="4" + +--4 +Content-Type: text/plain +Content-Disposition: inline + +B +--4 +Content-Type: image/jpeg +Content-Disposition: inline + +C +--4 +Content-Type: text/plain +Content-Disposition: inline + +D +--4-- + +--3 +Content-Type: multipart/related; boundary="5" + +--5 +Content-Type: text/html + +E +--5 +Content-Type: image/jpeg + +F +--5-- + +--3-- + BINARY.SIZE[2.1] 437 ---------------------------------- BODY[2.1.HEADER] {51} @@ -562,8 +727,26 @@ Content-Disposition: inline D --4-- -BINARY[2.1.1] {16} -[binary content] +BINARY[2.1.1] {235} +Content-Type: multipart/mixed; boundary="4" + +--4 +Content-Type: text/plain +Content-Disposition: inline + +B +--4 +Content-Type: image/jpeg +Content-Disposition: inline + +C +--4 +Content-Type: text/plain +Content-Disposition: inline + +D +--4-- + BINARY.SIZE[2.1.1] 235 ---------------------------------- BODY[2.1.1.HEADER] {45} @@ -624,8 +807,8 @@ BINARY.SIZE[2.1.1.1.1] 1 ---------------------------------- BODY[2.1.1.2] {1} C -BINARY[2.1.1.2] {16} -[binary content] +BINARY[2.1.1.2] {1} +C BINARY.SIZE[2.1.1.2] 1 ---------------------------------- BODY[2.1.1.2.HEADER] {54} @@ -645,8 +828,8 @@ Content-Disposition: inline ---------------------------------- BODY[2.1.1.2.1] {1} C -BINARY[2.1.1.2.1] {16} -[binary content] +BINARY[2.1.1.2.1] {1} +C BINARY.SIZE[2.1.1.2.1] 1 ---------------------------------- BODY[2.1.1.3] {1} @@ -687,8 +870,19 @@ Content-Type: image/jpeg F --5-- -BINARY[2.1.2] {16} -[binary content] +BINARY[2.1.2] {133} +Content-Type: multipart/related; boundary="5" + +--5 +Content-Type: text/html + +E +--5 +Content-Type: image/jpeg + +F +--5-- + BINARY.SIZE[2.1.2] 133 ---------------------------------- BODY[2.1.2.HEADER] {47} @@ -740,8 +934,8 @@ BINARY.SIZE[2.1.2.1.1] 14 ---------------------------------- BODY[2.1.2.2] {1} F -BINARY[2.1.2.2] {16} -[binary content] +BINARY[2.1.2.2] {1} +F BINARY.SIZE[2.1.2.2] 1 ---------------------------------- BODY[2.1.2.2.HEADER] {26} @@ -759,14 +953,14 @@ Content-Type: image/jpeg ---------------------------------- BODY[2.1.2.2.1] {1} F -BINARY[2.1.2.2.1] {16} -[binary content] +BINARY[2.1.2.2.1] {1} +F BINARY.SIZE[2.1.2.2.1] 1 ---------------------------------- BODY[2.2] {1} G -BINARY[2.2] {16} -[binary content] +BINARY[2.2] {1} +G BINARY.SIZE[2.2] 1 ---------------------------------- BODY[2.2.HEADER] {58} @@ -786,14 +980,14 @@ Content-Disposition: attachment ---------------------------------- BODY[2.2.1] {1} G -BINARY[2.2.1] {16} -[binary content] +BINARY[2.2.1] {1} +G BINARY.SIZE[2.2.1] 1 ---------------------------------- BODY[2.3] {1} H -BINARY[2.3] {16} -[binary content] +BINARY[2.3] {1} +H BINARY.SIZE[2.3] 1 ---------------------------------- BODY[2.3.HEADER] {35} @@ -811,16 +1005,18 @@ Content-Type: application/x-excel ---------------------------------- BODY[2.3.1] {1} H -BINARY[2.3.1] {16} -[binary content] +BINARY[2.3.1] {1} +H BINARY.SIZE[2.3.1] 1 ---------------------------------- BODY[2.4] {13} Subject: J J -BINARY[2.4] {16} -[binary content] +BINARY[2.4] {13} +Subject: J + +J BINARY.SIZE[2.4] 13 ---------------------------------- BODY[2.4.HEADER] {12} diff --git a/tests/resources/imap/008.imap b/tests/resources/imap/008.imap index 93e88071..4ce60d23 100644 --- a/tests/resources/imap/008.imap +++ b/tests/resources/imap/008.imap @@ -114,8 +114,38 @@ c2FnZQ0KICA8L2JvZHk+DQo8L2h0bWw+DQoNCi0tYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh YWFhYWFhYWFhYWFhYS0tDQo= --bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-- -BINARY[] {16} -[binary content] +BINARY[] {1649} +Content-Type: multipart/mixed; + boundary=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + +--bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + +This is a message with a base64 encoded attached email +--bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +Content-Disposition: attachment; filename="attached_email.eml" +Content-Type: message/rfc822 +Content-Transfer-Encoding: base64 + +VG86ICJlbWFpbEBleGFtcGxlLmNvbSIgPGVtYWlsQGV4YW1wbGUuY29tPg0KRnJvbTogTmFtZSA8 +ZW1haWxAZXhhbXBsZS5jb20+DQpTdWJqZWN0OiBIVE1MIHRlc3QNCk1lc3NhZ2UtSUQ6IDxyYW5k +b20tbWVzc2FnZS1pZEBleGFtcGxlLmNvbT4NCkRhdGU6IFR1ZSwgMTQgRGVjIDIwMjEgMTE6NDg6 +MjUgKzAxMDANCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IG11bHRpcGFydC9hbHRl +cm5hdGl2ZTsNCiBib3VuZGFyeT0iYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYSINCkNvbnRlbnQtTGFuZ3VhZ2U6IGVuLVVTDQoNClRoaXMgaXMgYSBtdWx0aS1wYXJ0IG1l +c3NhZ2UgaW4gTUlNRSBmb3JtYXQuDQotLWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYWFhYWENCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD11dGYtODsgZm9ybWF0 +PWZsb3dlZA0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogN2JpdA0KDQpUaGlzIGlzIGFuICpI +VE1MKiB0ZXN0IG1lc3NhZ2UNCi0tYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYQ0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgNCkNvbnRlbnQtVHJh +bnNmZXItRW5jb2Rpbmc6IDdiaXQNCg0KPGh0bWw+DQogIDxoZWFkPg0KICAgIDxtZXRhIGh0dHAt +ZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04Ij4N +CiAgPC9oZWFkPg0KICA8Ym9keT4NCiAgICBUaGlzIGlzIGFuIDxiPkhUTUw8L2I+IHRlc3QgbWVz +c2FnZQ0KICA8L2JvZHk+DQo8L2h0bWw+DQoNCi0tYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYWFhYWFhYWFhYS0tDQo= +--bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-- + BINARY.SIZE[] 1649 ---------------------------------- BODY[HEADER] {83} @@ -204,8 +234,26 @@ ZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04Ij4N CiAgPC9oZWFkPg0KICA8Ym9keT4NCiAgICBUaGlzIGlzIGFuIDxiPkhUTUw8L2I+IHRlc3QgbWVz c2FnZQ0KICA8L2JvZHk+DQo8L2h0bWw+DQoNCi0tYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh YWFhYWFhYWFhYWFhYS0tDQo= -BINARY[2] {16} -[binary content] +BINARY[2] {872} +Content-Type: multipart/mixed; + boundary=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + +--bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + +This is a message with a base64 encoded attached email +--bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +Content-Disposition: attachment; filename="attached_email.eml" +Content-Type: message/rfc822 +Content-Transfer-Encoding: base64 + +VG86ICJlbWFpbEBleGFtcGxlLmNvbSIgPGVtYWlsQGV4YW1wbGUuY29tPg0KRnJvbTogTmFtZSA8 +ZW1haWxAZXhhbXBsZS5jb20+DQpTdWJqZWN0OiBIVE1MIHRlc3QNCk1lc3NhZ2UtSUQ6IDxyYW5k +b20tbWVzc2FnZS1pZEBleGFtcGxlLmNvbT4NCkRhdGU6IFR1ZSwgMTQgRGVjIDIwMjEgMTE6NDg6 +MjUgKzAxMDANCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IG11bHRpcGFydC9hbHRl +cm5hdGl2ZTsNCiBib3VuZGFyeT0iYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYSINCkNvbnRlbnQtTGFuZ3VhZ2U6IGVuLVVTDQoNClRoaXMgaXMgYSBtdWx BINARY.SIZE[2] 872 ---------------------------------- BODY[2.HEADER] {319} diff --git a/tests/resources/imap/009.imap b/tests/resources/imap/009.imap index 5eb89a8b..efbcbde3 100644 --- a/tests/resources/imap/009.imap +++ b/tests/resources/imap/009.imap @@ -113,8 +113,47 @@ R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 --giddyup-- --festivus-- -BINARY[] {16} -[binary content] +BINARY[] {1457} +From: Art Vandelay (Vandelay Industries) +To: "Colleagues": "James Smythe" ; Friends: + jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= ; +Date: Sat, 20 Nov 2021 14:22:01 -0800 +Subject: Why not both importing AND exporting? =?utf-8?b?4pi6?= +Content-Type: multipart/mixed; boundary="festivus"; + +--festivus +Content-Type: text/html; charset="us-ascii" +Content-Transfer-Encoding: base64 + +PGh0bWw+PHA+SSB3YXMgdGhpbmtpbmcgYWJvdXQgcXVpdHRpbmcgdGhlICZsZHF1bztle +HBvcnRpbmcmcmRxdW87IHRvIGZvY3VzIGp1c3Qgb24gdGhlICZsZHF1bztpbXBvcnRpbm +cmcmRxdW87LDwvcD48cD5idXQgdGhlbiBJIHRob3VnaHQsIHdoeSBub3QgZG8gYm90aD8 +gJiN4MjYzQTs8L3A+PC9odG1sPg== +--festivus +Content-Type: message/rfc822 + +From: "Cosmo Kramer" +Subject: Exporting my book about coffee tables +Content-Type: multipart/mixed; boundary="giddyup"; + +--giddyup +Content-Type: text/plain; charset="utf-16" +Content-Transfer-Encoding: quoted-printable + +=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"= +=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD = +=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"= +=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00 +--giddyup +Content-Type: image/gif; name*1="about "; name*0="Book "; + name*2*=utf-8''%e2%98%95 tables.gif +Content-Transfer-Encoding: Base64 +Content-Disposition: attachment + +R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 +--giddyup-- +--festivus-- + BINARY.SIZE[] 1457 ---------------------------------- BODY[HEADER] {349} @@ -224,8 +263,27 @@ Content-Disposition: attachment R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 --giddyup-- -BINARY[2] {16} -[binary content] +BINARY[2] {723} +From: "Cosmo Kramer" +Subject: Exporting my book about coffee tables +Content-Type: multipart/mixed; boundary="giddyup"; + +--giddyup +Content-Type: text/plain; charset="utf-16" +Content-Transfer-Encoding: quoted-printable + +=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"= +=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD = +=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"= +=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00 +--giddyup +Content-Type: image/gif; name*1="about "; name*0="Book "; + name*2*=utf-8''%e2%98%95 tables.gif +Content-Transfer-Encoding: Base64 +Content-Disposition: attachment + +R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 +--giddyup-- BINARY.SIZE[2] 723 ---------------------------------- BODY[2.HEADER] {143} @@ -263,9 +321,9 @@ BODY[2.1] {228} =DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD = =005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"= =DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00 -BINARY[2.1] {101} -ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨 𝔭𝔩𝔢𝔞𝔰𝔢! -BINARY.SIZE[2.1] 101 +BINARY[2.1] {16} +[binary content] +BINARY.SIZE[2.1] 108 ---------------------------------- BODY[2.1.HEADER] {88} Content-Type: text/plain; charset="utf-16" @@ -290,9 +348,9 @@ BODY[2.1.1] {228} =DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD = =005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"= =DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00 -BINARY[2.1.1] {101} -ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨 𝔭𝔩𝔢𝔞𝔰𝔢! -BINARY.SIZE[2.1.1] 101 +BINARY[2.1.1] {16} +[binary content] +BINARY.SIZE[2.1.1] 108 ---------------------------------- BODY[2.2] {56} R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 diff --git a/tests/resources/imap/010.imap b/tests/resources/imap/010.imap index eeb82cec..441571bb 100644 --- a/tests/resources/imap/010.imap +++ b/tests/resources/imap/010.imap @@ -54,8 +54,13 @@ Subject: submsg Hello world -BINARY[] {16} -[binary content] +BINARY[] {88} +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg + +Hello world + BINARY.SIZE[] 88 ---------------------------------- BODY[HEADER] {108} @@ -86,8 +91,13 @@ Subject: submsg Hello world -BINARY[1] {16} -[binary content] +BINARY[1] {88} +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg + +Hello world + BINARY.SIZE[1] 88 ---------------------------------- BODY[1.HEADER] {76} diff --git a/tests/resources/imap/011.imap b/tests/resources/imap/011.imap index 7d776c6a..589ba460 100644 --- a/tests/resources/imap/011.imap +++ b/tests/resources/imap/011.imap @@ -152,8 +152,30 @@ m2 body epilogue -BINARY[] {16} -[binary content] +BINARY[] {260} +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg +Content-Type: multipart/digest; boundary="foo" + +prologue + +--foo + +From: m1@example.com +Subject: m1 + +m1 body + +--foo +X-Mime: m2 header + +From: m2@example.com +Subject: m2 + +m2 body + +--foo-- BINARY.SIZE[] 260 ---------------------------------- BODY[HEADER] {108} @@ -224,8 +246,30 @@ m2 body epilogue -BINARY[1] {16} -[binary content] +BINARY[1] {260} +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg +Content-Type: multipart/digest; boundary="foo" + +prologue + +--foo + +From: m1@example.com +Subject: m1 + +m1 body + +--foo +X-Mime: m2 header + +From: m2@example.com +Subject: m2 + +m2 body + +--foo-- BINARY.SIZE[1] 260 ---------------------------------- BODY[1.HEADER] {123} @@ -267,8 +311,12 @@ Subject: m1 m1 body -BINARY[1.1] {16} -[binary content] +BINARY[1.1] {42} +From: m1@example.com +Subject: m1 + +m1 body + BINARY.SIZE[1.1] 42 ---------------------------------- BODY[1.1.HEADER] {34} @@ -299,8 +347,12 @@ Subject: m2 m2 body -BINARY[1.2] {16} -[binary content] +BINARY[1.2] {42} +From: m2@example.com +Subject: m2 + +m2 body + BINARY.SIZE[1.2] 42 ---------------------------------- BODY[1.2.HEADER] {34} diff --git a/tests/resources/imap/012.imap b/tests/resources/imap/012.imap index 6c3e407f..5a02e5a7 100644 --- a/tests/resources/imap/012.imap +++ b/tests/resources/imap/012.imap @@ -109,8 +109,45 @@ Sub MIME epilogue --foo bar-- Root MIME epilogue -BINARY[] {16} -[binary content] +BINARY[] {565} +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + +Root MIME prologue + +--foo bar +Content-Type: text/x-myown; charset=us-ascii + +hello + +--foo bar +Content-Type: message/rfc822 + +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg +Content-Type: multipart/alternative; boundary="sub1" + +Sub MIME prologue +--sub1 +Content-Type: text/html + +

Hello world

+ +--sub1 +Content-Type: text/plain + +Hello another world + +--sub1-- +Sub MIME epilogue + +--foo bar-- +Root MIME epilogue + BINARY.SIZE[] 565 ---------------------------------- BODY[HEADER] {130} @@ -212,8 +249,26 @@ Hello another world --sub1-- Sub MIME epilogue -BINARY[2] {16} -[binary content] +BINARY[2] {280} +From: sub@domain.org +Date: Sun, 12 Aug 2012 12:34:56 +0300 +Subject: submsg +Content-Type: multipart/alternative; boundary="sub1" + +Sub MIME prologue +--sub1 +Content-Type: text/html + +

Hello world

+ +--sub1 +Content-Type: text/plain + +Hello another world + +--sub1-- +Sub MIME epilogue + BINARY.SIZE[2] 280 ---------------------------------- BODY[2.HEADER] {129} diff --git a/tests/resources/imap/014.imap b/tests/resources/imap/014.imap index 679a697d..dd3a5abe 100644 --- a/tests/resources/imap/014.imap +++ b/tests/resources/imap/014.imap @@ -49,8 +49,39 @@ foo_bar --foo bar-- Root MIME epilogue -BINARY[] {16} -[binary content] +BINARY[] {404} +From user@domain Fri Feb 22 17:06:23 2008 +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + +Root MIME prologue + +--foo bar +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +--foo bar-- +Root MIME epilogue + BINARY.SIZE[] 404 ---------------------------------- BODY[HEADER] {173} diff --git a/tests/src/imap/body_structure.rs b/tests/src/imap/body_structure.rs index de10cda0..75ebc301 100644 --- a/tests/src/imap/body_structure.rs +++ b/tests/src/imap/body_structure.rs @@ -138,9 +138,12 @@ pub fn test() { sections: sections.clone(), offset: None, contents: match contents { - BodyContents::Bytes(_) => { - BodyContents::Text("[binary content]".into()) - } + BodyContents::Bytes(bytes) => BodyContents::Text( + std::str::from_utf8(bytes.as_ref()) + .unwrap_or("[binary content]") + .to_string() + .into(), + ), text => text, }, } diff --git a/tests/src/imap/fetch.rs b/tests/src/imap/fetch.rs index 1c28337e..9310e01b 100644 --- a/tests/src/imap/fetch.rs +++ b/tests/src/imap/fetch.rs @@ -71,18 +71,22 @@ pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) { .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await - .assert_contains("BINARY[1] {175}") + .assert_contains("BINARY[1] ~{175}") .assert_contains("BINARY.SIZE[1] 175") .assert_contains("BODY[1.TEXT] {239}") .assert_contains("BODY[2.1.HEADER] {88}") - .assert_contains("BINARY[2.1] {101}") + .assert_contains("BINARY[2.1] ~{108}") .assert_contains("BODY[MIME] {54}") .assert_contains("BODY[HEADER.FIELDS (FROM)]<10> {8}") .assert_contains("“exporting”") .assert_contains("PGh0bWw+PHA+") .assert_contains("Content-Transfer-Encoding: quoted-printable") - .assert_contains("ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨") .assert_contains("Vandelay"); + let fraktur_utf16_le: Vec = "ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨" + .encode_utf16() + .flat_map(|c| c.to_le_bytes()) + .collect(); + imap.assert_last_contains_bytes(&fraktur_utf16_le); // We are in EXAMINE mode, fetching body should not set \Seen imap.send("UID FETCH 10 (FLAGS)").await; @@ -99,7 +103,7 @@ pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) { .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await - .assert_contains("BINARY[1] {175}") + .assert_contains("BINARY[1] ~{175}") .assert_contains("BINARY.SIZE[1] 175") .assert_contains("BODY[1.TEXT] {239}"); diff --git a/tests/src/imap/search.rs b/tests/src/imap/search.rs index 4ad5ad0c..7aa7e754 100644 --- a/tests/src/imap/search.rs +++ b/tests/src/imap/search.rs @@ -61,6 +61,17 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection, te .await .assert_equals("* SEARCH 10"); + imap_check + .send(concat!( + "UID SEARCH CHARSET UTF-8 TEXT {75+}\r\n", + "ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨" + )) + .await; + imap_check + .assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_equals("* SEARCH 10"); + imap_check .send("UID SEARCH NOT (FROM nathaniel ANSWERED)") .await; diff --git a/tests/src/utils/imap.rs b/tests/src/utils/imap.rs index cca19a89..785c5304 100644 --- a/tests/src/utils/imap.rs +++ b/tests/src/utils/imap.rs @@ -8,14 +8,33 @@ use base64::{Engine, engine::general_purpose}; use imap_proto::ResponseType; use std::time::Duration; use tokio::{ - io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf}, + io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf}, net::TcpStream, }; pub struct ImapConnection { tag: &'static [u8], - reader: Lines>>, + reader: BufReader>, writer: WriteHalf, + last_raw: Vec, +} + +async fn read_lossy_line( + reader: &mut BufReader>, +) -> std::io::Result)>> { + let mut buf = Vec::new(); + let n = reader.read_until(b'\n', &mut buf).await?; + if n == 0 { + return Ok(None); + } + let mut trimmed = buf.as_slice(); + if trimmed.last() == Some(&b'\n') { + trimmed = &trimmed[..trimmed.len() - 1]; + } + if trimmed.last() == Some(&b'\r') { + trimmed = &trimmed[..trimmed.len() - 1]; + } + Ok(Some((String::from_utf8_lossy(trimmed).into_owned(), buf))) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -35,11 +54,23 @@ impl ImapConnection { let (reader, writer) = tokio::io::split(TcpStream::connect(addr.as_ref()).await.unwrap()); ImapConnection { tag, - reader: BufReader::new(reader).lines(), + reader: BufReader::new(reader), writer, + last_raw: Vec::new(), } } + pub fn assert_last_contains_bytes(&self, pattern: &[u8]) -> &Self { + if !self.last_raw.windows(pattern.len()).any(|w| w == pattern) { + panic!( + "Expected byte sequence {:02x?} not found in last response ({} bytes).", + pattern, + self.last_raw.len() + ); + } + self + } + pub async fn assert_read(&mut self, t: Type, rt: ResponseType) -> Vec { let lines = self.read(t).await; let mut buf = Vec::with_capacity(10); @@ -63,9 +94,14 @@ impl ImapConnection { } pub async fn assert_disconnect(&mut self) { - match tokio::time::timeout(Duration::from_millis(1500), self.reader.next_line()).await { + match tokio::time::timeout( + Duration::from_millis(1500), + read_lossy_line(&mut self.reader), + ) + .await + { Ok(Ok(None)) => {} - Ok(Ok(Some(line))) => { + Ok(Ok(Some((line, _)))) => { panic!("Expected connection to be closed, but got {:?}", line); } Ok(Err(err)) => { @@ -77,9 +113,16 @@ impl ImapConnection { pub async fn read(&mut self, t: Type) -> Vec { let mut lines = Vec::new(); + self.last_raw.clear(); loop { - match tokio::time::timeout(Duration::from_millis(1500), self.reader.next_line()).await { - Ok(Ok(Some(line))) => { + match tokio::time::timeout( + Duration::from_millis(1500), + read_lossy_line(&mut self.reader), + ) + .await + { + Ok(Ok(Some((line, raw)))) => { + self.last_raw.extend_from_slice(&raw); let is_done = line.starts_with(match t { Type::Tagged => std::str::from_utf8(self.tag).unwrap(), Type::Untagged | Type::Status => "* ",