eml_codec/part/
composite.rs

1use nom::IResult;
2use std::fmt;
3
4use crate::header;
5use crate::imf;
6use crate::mime;
7use crate::part::{self, AnyPart};
8use crate::pointers;
9use crate::text::boundary::{boundary, Delimiter};
10
11//--- Multipart
12#[derive(PartialEq)]
13pub struct Multipart<'a> {
14    pub mime: mime::MIME<'a, mime::r#type::Multipart>,
15    pub children: Vec<AnyPart<'a>>,
16    pub raw_part_inner: &'a [u8],
17    pub raw_part_outer: &'a [u8],
18}
19impl<'a> fmt::Debug for Multipart<'a> {
20    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
21        fmt.debug_struct("part::Multipart")
22            .field("mime", &self.mime)
23            .field("children", &self.children)
24            .field(
25                "raw_part_inner",
26                &String::from_utf8_lossy(self.raw_part_inner),
27            )
28            .field(
29                "raw_part_outer",
30                &String::from_utf8_lossy(self.raw_part_outer),
31            )
32            .finish()
33    }
34}
35impl<'a> Multipart<'a> {
36    pub fn preamble(&self) -> &'a [u8] {
37        pointers::parsed(self.raw_part_outer, self.raw_part_inner)
38    }
39    pub fn epilogue(&self) -> &'a [u8] {
40        pointers::rest(self.raw_part_outer, self.raw_part_inner)
41    }
42    pub fn preamble_and_body(&self) -> &'a [u8] {
43        pointers::with_preamble(self.raw_part_outer, self.raw_part_inner)
44    }
45    pub fn body_and_epilogue(&self) -> &'a [u8] {
46        pointers::with_epilogue(self.raw_part_outer, self.raw_part_inner)
47    }
48}
49
50pub fn multipart<'a>(
51    m: mime::MIME<'a, mime::r#type::Multipart>,
52) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> {
53    let m = m.clone();
54
55    move |input| {
56        // init
57        let outer_orig = input;
58        let bound = m.interpreted_type.boundary.as_bytes();
59        let mut mparts: Vec<AnyPart> = vec![];
60
61        // skip preamble
62        let (mut input_loop, _) = part::part_raw(bound)(input)?;
63        let inner_orig = input_loop;
64
65        loop {
66            let input = match boundary(bound)(input_loop) {
67                Err(_) => {
68                    return Ok((
69                        input_loop,
70                        Multipart {
71                            mime: m.clone(),
72                            children: mparts,
73                            raw_part_inner: pointers::parsed(inner_orig, input_loop),
74                            raw_part_outer: pointers::parsed(outer_orig, input_loop),
75                        },
76                    ))
77                }
78                Ok((inp, Delimiter::Last)) => {
79                    return Ok((
80                        inp,
81                        Multipart {
82                            mime: m.clone(),
83                            children: mparts,
84                            raw_part_inner: pointers::parsed(inner_orig, inp),
85                            raw_part_outer: pointers::parsed(
86                                outer_orig,
87                                &outer_orig[outer_orig.len()..],
88                            ),
89                        },
90                    ))
91                }
92                Ok((inp, Delimiter::Next)) => inp,
93            };
94
95            // parse mime headers, otherwise pick default mime
96            let (input, naive_mime) = match header::header_kv(input) {
97                Ok((input_eom, fields)) => {
98                    let raw_hdrs = pointers::parsed(input, input_eom);
99                    let mime = fields
100                        .iter()
101                        .flat_map(mime::field::Content::try_from)
102                        .into_iter()
103                        .collect::<mime::NaiveMIME>();
104
105                    let mime = mime.with_kv(fields).with_raw(raw_hdrs);
106
107                    (input_eom, mime)
108                }
109                Err(_) => (input, mime::NaiveMIME::default()),
110            };
111
112            // interpret mime according to context
113            let mime = match m.interpreted_type.subtype {
114                mime::r#type::MultipartSubtype::Digest => naive_mime
115                    .to_interpreted::<mime::WithDigestDefault>()
116                    .into(),
117                _ => naive_mime
118                    .to_interpreted::<mime::WithGenericDefault>()
119                    .into(),
120            };
121
122            // parse raw part
123            let (input, rpart) = part::part_raw(bound)(input)?;
124
125            // parse mime body
126            // -- we do not keep the input as we are using the
127            // part_raw function as our cursor here.
128            let (_, part) = part::anypart(mime)(rpart)?;
129            mparts.push(part);
130
131            input_loop = input;
132        }
133    }
134}
135
136//--- Message
137
138#[derive(PartialEq)]
139pub struct Message<'a> {
140    pub mime: mime::MIME<'a, mime::r#type::DeductibleMessage>,
141    pub imf: imf::Imf<'a>,
142    pub child: Box<AnyPart<'a>>,
143
144    pub raw_part: &'a [u8],
145    pub raw_headers: &'a [u8],
146    pub raw_body: &'a [u8],
147}
148impl<'a> fmt::Debug for Message<'a> {
149    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
150        fmt.debug_struct("part::Message")
151            .field("mime", &self.mime)
152            .field("imf", &self.imf)
153            .field("child", &self.child)
154            .field("raw_part", &String::from_utf8_lossy(self.raw_part))
155            .field("raw_headers", &String::from_utf8_lossy(self.raw_headers))
156            .field("raw_body", &String::from_utf8_lossy(self.raw_body))
157            .finish()
158    }
159}
160
161pub fn message<'a>(
162    m: mime::MIME<'a, mime::r#type::DeductibleMessage>,
163) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
164    move |input: &[u8]| {
165        let orig = input;
166
167        // parse header fields
168        let (input, headers) = header::header_kv(input)?;
169
170        // extract raw parts 1/2
171        let raw_headers = pointers::parsed(orig, input);
172        let body_orig = input;
173
174        //---------------
175        // aggregate header fields
176        let (naive_mime, imf) = part::field::split_and_build(&headers);
177
178        // Bind headers to mime
179        let naive_mime = naive_mime.with_kv(headers);
180
181        // interpret headers to choose the child mime type
182        let in_mime = naive_mime
183            .with_raw(raw_headers)
184            .to_interpreted::<mime::WithGenericDefault>()
185            .into();
186        //---------------
187
188        // parse a part following this mime specification
189        let (input, part) = part::anypart(in_mime)(input)?;
190
191        // extract raw parts 2/2
192        let raw_body = pointers::parsed(body_orig, input);
193        let raw_part = pointers::parsed(orig, input);
194
195        Ok((
196            input,
197            Message {
198                mime: m.clone(),
199                imf,
200                raw_part,
201                raw_headers,
202                raw_body,
203                child: Box::new(part),
204            },
205        ))
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::part::discrete::Text;
213    use crate::part::AnyPart;
214    use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
215    use crate::text::misc_token::{MIMEWord, Phrase, UnstrToken, Unstructured, Word};
216    use crate::text::quoted::QuotedString;
217    use chrono::{FixedOffset, TimeZone};
218
219    #[test]
220    fn test_multipart() {
221        let base_mime = mime::MIME {
222            interpreted_type: mime::r#type::Multipart {
223                subtype: mime::r#type::MultipartSubtype::Alternative,
224                boundary: "simple boundary".to_string(),
225            },
226            fields: mime::NaiveMIME::default(),
227        };
228
229        let input = b"This is the preamble.  It is to be ignored, though it
230is a handy place for composition agents to include an
231explanatory note to non-MIME conformant readers.
232
233--simple boundary
234
235This is implicitly typed plain US-ASCII text.
236It does NOT end with a linebreak.
237--simple boundary
238Content-type: text/plain; charset=us-ascii
239
240This is explicitly typed plain US-ASCII text.
241It DOES end with a linebreak.
242
243--simple boundary--
244
245This is the epilogue. It is also to be ignored.
246";
247
248        let inner = b"
249--simple boundary
250
251This is implicitly typed plain US-ASCII text.
252It does NOT end with a linebreak.
253--simple boundary
254Content-type: text/plain; charset=us-ascii
255
256This is explicitly typed plain US-ASCII text.
257It DOES end with a linebreak.
258
259--simple boundary--
260";
261
262        assert_eq!(
263            multipart(base_mime.clone())(input),
264            Ok((&b"\nThis is the epilogue. It is also to be ignored.\n"[..],
265                Multipart {
266                    mime: base_mime,
267                    raw_part_outer: input,
268                    raw_part_inner: inner,
269                    children: vec![
270                        AnyPart::Txt(Text {
271                            mime: mime::MIME {
272                                interpreted_type: mime::r#type::Deductible::Inferred(mime::r#type::Text {
273                                    subtype: mime::r#type::TextSubtype::Plain,
274                                    charset: mime::r#type::Deductible::Inferred(mime::charset::EmailCharset::US_ASCII),
275                                }),
276                                fields: mime::NaiveMIME {
277                                    raw: &b"\n"[..],
278                                    ..mime::NaiveMIME::default()
279                                },
280                            },
281                            body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
282                        }),
283                        AnyPart::Txt(Text {
284                            mime: mime::MIME {
285                                interpreted_type: mime::r#type::Deductible::Explicit(mime::r#type::Text {
286                                    subtype: mime::r#type::TextSubtype::Plain,
287                                    charset: mime::r#type::Deductible::Explicit(mime::charset::EmailCharset::US_ASCII),
288                                }),
289                                fields: mime::NaiveMIME {
290                                    ctype: Some(mime::r#type::NaiveType {
291                                        main: &b"text"[..],
292                                        sub: &b"plain"[..],
293                                        params: vec![
294                                            mime::r#type::Parameter {
295                                                name: &b"charset"[..],
296                                                value: MIMEWord::Atom(&b"us-ascii"[..]),
297                                            }
298                                        ]
299                                    }),
300                                    raw: &b"Content-type: text/plain; charset=us-ascii\n\n"[..],
301                                    kv: vec![
302                                        header::Field::Good(header::Kv2(&b"Content-type"[..], &b"text/plain; charset=us-ascii"[..]))
303                                    ],
304                                    ..mime::NaiveMIME::default()
305                                },
306                            },
307                            body: &b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
308                        }),
309                    ],
310                },
311            ))
312        );
313    }
314
315    #[test]
316    fn test_message() {
317        let fullmail: &[u8] = r#"Date: Sat, 8 Jul 2023 07:14:29 +0200
318From: Grrrnd Zero <grrrndzero@example.org>
319To: John Doe <jdoe@machine.example>
320CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
321Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
322    =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
323X-Unknown: something something
324Bad entry
325  on multiple lines
326Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>
327MIME-Version: 1.0
328Content-Type: multipart/alternative;
329 boundary="b1_e376dc71bafc953c0b0fdeb9983a9956"
330Content-Transfer-Encoding: 7bit
331
332This is a multi-part message in MIME format.
333
334--b1_e376dc71bafc953c0b0fdeb9983a9956
335Content-Type: text/plain; charset=utf-8
336Content-Transfer-Encoding: quoted-printable
337
338GZ
339OoOoO
340oOoOoOoOo
341oOoOoOoOoOoOoOoOo
342oOoOoOoOoOoOoOoOoOoOoOo
343oOoOoOoOoOoOoOoOoOoOoOoOoOoOo
344OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
345
346--b1_e376dc71bafc953c0b0fdeb9983a9956
347Content-Type: text/html; charset=us-ascii
348
349<div style="text-align: center;"><strong>GZ</strong><br />
350OoOoO<br />
351oOoOoOoOo<br />
352oOoOoOoOoOoOoOoOo<br />
353oOoOoOoOoOoOoOoOoOoOoOo<br />
354oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
355OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
356</div>
357
358--b1_e376dc71bafc953c0b0fdeb9983a9956--
359"#
360        .as_bytes();
361
362        let hdrs = br#"Date: Sat, 8 Jul 2023 07:14:29 +0200
363From: Grrrnd Zero <grrrndzero@example.org>
364To: John Doe <jdoe@machine.example>
365CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
366Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
367    =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
368X-Unknown: something something
369Bad entry
370  on multiple lines
371Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>
372MIME-Version: 1.0
373Content-Type: multipart/alternative;
374 boundary="b1_e376dc71bafc953c0b0fdeb9983a9956"
375Content-Transfer-Encoding: 7bit
376
377"#;
378
379        let body = br#"This is a multi-part message in MIME format.
380
381--b1_e376dc71bafc953c0b0fdeb9983a9956
382Content-Type: text/plain; charset=utf-8
383Content-Transfer-Encoding: quoted-printable
384
385GZ
386OoOoO
387oOoOoOoOo
388oOoOoOoOoOoOoOoOo
389oOoOoOoOoOoOoOoOoOoOoOo
390oOoOoOoOoOoOoOoOoOoOoOoOoOoOo
391OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
392
393--b1_e376dc71bafc953c0b0fdeb9983a9956
394Content-Type: text/html; charset=us-ascii
395
396<div style="text-align: center;"><strong>GZ</strong><br />
397OoOoO<br />
398oOoOoOoOo<br />
399oOoOoOoOoOoOoOoOo<br />
400oOoOoOoOoOoOoOoOoOoOoOo<br />
401oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
402OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
403</div>
404
405--b1_e376dc71bafc953c0b0fdeb9983a9956--
406"#;
407
408        let inner = br#"
409--b1_e376dc71bafc953c0b0fdeb9983a9956
410Content-Type: text/plain; charset=utf-8
411Content-Transfer-Encoding: quoted-printable
412
413GZ
414OoOoO
415oOoOoOoOo
416oOoOoOoOoOoOoOoOo
417oOoOoOoOoOoOoOoOoOoOoOo
418oOoOoOoOoOoOoOoOoOoOoOoOoOoOo
419OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
420
421--b1_e376dc71bafc953c0b0fdeb9983a9956
422Content-Type: text/html; charset=us-ascii
423
424<div style="text-align: center;"><strong>GZ</strong><br />
425OoOoO<br />
426oOoOoOoOo<br />
427oOoOoOoOoOoOoOoOo<br />
428oOoOoOoOoOoOoOoOoOoOoOo<br />
429oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
430OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
431</div>
432
433--b1_e376dc71bafc953c0b0fdeb9983a9956--
434"#;
435
436        let base_mime = mime::MIME::<mime::r#type::DeductibleMessage>::default();
437        assert_eq!(
438            message(base_mime.clone())(fullmail),
439            Ok((
440                &[][..],
441                Message {
442                    mime: base_mime,
443                    raw_part: fullmail,
444                    raw_headers: hdrs,
445                    raw_body: body,
446                    imf: imf::Imf {
447                        date: Some(FixedOffset::east_opt(2 * 3600)
448                            .unwrap()
449                            .with_ymd_and_hms(2023, 07, 8, 7, 14, 29)
450                            .unwrap()),
451                        from: vec![
452                            imf::mailbox::MailboxRef {
453                                name: Some(Phrase(vec![Word::Atom(&b"Grrrnd"[..]), Word::Atom(&b"Zero"[..])])),
454                                addrspec: imf::mailbox::AddrSpec {
455                                    local_part: imf::mailbox::LocalPart(vec![
456                                        imf::mailbox::LocalPartToken::Word(Word::Atom(&b"grrrndzero"[..]))
457                                    ]),
458                                    domain: imf::mailbox::Domain::Atoms(vec![&b"example"[..], &b"org"[..]]),
459                                }
460                            },
461                        ],
462
463                        to: vec![imf::address::AddressRef::Single(imf::mailbox::MailboxRef {
464                                name: Some(Phrase(vec![Word::Atom(&b"John"[..]), Word::Atom(&b"Doe"[..])])),
465                                addrspec: imf::mailbox::AddrSpec {
466                                    local_part: imf::mailbox::LocalPart(vec![
467                                        imf::mailbox::LocalPartToken::Word(Word::Atom(&b"jdoe"[..]))
468                                    ]),
469                                    domain: imf::mailbox::Domain::Atoms(vec![&b"machine"[..], &b"example"[..]]),
470                                }
471                         })],
472
473                        cc: vec![imf::address::AddressRef::Single(imf::mailbox::MailboxRef {
474                            name: Some(Phrase(vec![
475                                Word::Encoded(EncodedWord::Quoted(QuotedWord {
476                                    enc: encoding_rs::WINDOWS_1252,
477                                    chunks: vec![
478                                        QuotedChunk::Safe(&b"Andr"[..]),
479                                        QuotedChunk::Encoded(vec![0xE9]),
480                                    ],
481                                })),
482                                Word::Atom(&b"Pirard"[..])
483                            ])),
484                            addrspec: imf::mailbox::AddrSpec {
485                                local_part: imf::mailbox::LocalPart(vec![
486                                    imf::mailbox::LocalPartToken::Word(Word::Atom(&b"PIRARD"[..]))
487                                ]),
488                                domain: imf::mailbox::Domain::Atoms(vec![
489                                    &b"vm1"[..], &b"ulg"[..], &b"ac"[..], &b"be"[..],
490                                ]),
491                            }
492                        })],
493
494                        subject: Some(Unstructured(vec![
495                            UnstrToken::Encoded(EncodedWord::Base64(Base64Word{
496                                enc: encoding_rs::WINDOWS_1252,
497                                content: &b"SWYgeW91IGNhbiByZWFkIHRoaXMgeW8"[..],
498                            })),
499                            UnstrToken::Encoded(EncodedWord::Base64(Base64Word{
500                                enc: encoding_rs::ISO_8859_2,
501                                content: &b"dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg"[..],
502                            })),
503                        ])),
504                        msg_id: Some(imf::identification::MessageID {
505                            left: &b"NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5"[..],
506                            right: &b"www.grrrndzero.org"[..],
507                        }),
508                        mime_version: Some(imf::mime::Version { major: 1, minor: 0}),
509                        ..imf::Imf::default()
510                    },
511                    child: Box::new(AnyPart::Mult(Multipart {
512                        mime: mime::MIME {
513                            interpreted_type: mime::r#type::Multipart {
514                                subtype: mime::r#type::MultipartSubtype::Alternative,
515                                boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(),
516                            },
517                            fields: mime::NaiveMIME {
518                                ctype: Some(mime::r#type::NaiveType {
519                                    main: &b"multipart"[..],
520                                    sub: &b"alternative"[..],
521                                    params: vec![
522                                        mime::r#type::Parameter {
523                                            name: &b"boundary"[..],
524                                            value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])),
525                                        }
526                                    ]
527                                }),
528                                raw: hdrs,
529                                kv: vec![
530                                    header::Field::Good(header::Kv2(&b"Date"[..], &b"Sat, 8 Jul 2023 07:14:29 +0200"[..])),
531                                    header::Field::Good(header::Kv2(&b"From"[..], &b"Grrrnd Zero <grrrndzero@example.org>"[..])),
532                                    header::Field::Good(header::Kv2(&b"To"[..], &b"John Doe <jdoe@machine.example>"[..])),
533                                    header::Field::Good(header::Kv2(&b"CC"[..], &b"=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>"[..])),
534                                    header::Field::Good(header::Kv2(&b"Subject"[..], &b"=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n    =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?="[..])),
535                                    header::Field::Good(header::Kv2(&b"X-Unknown"[..], &b"something something"[..])),
536                                    header::Field::Bad(&b"Bad entry\n  on multiple lines\n"[..]),
537                                    header::Field::Good(header::Kv2(&b"Message-ID"[..], &b"<NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>"[..])),
538                                    header::Field::Good(header::Kv2(&b"MIME-Version"[..], &b"1.0"[..])),
539                                    header::Field::Good(header::Kv2(&b"Content-Type"[..], &b"multipart/alternative;\n boundary=\"b1_e376dc71bafc953c0b0fdeb9983a9956\""[..])),
540                                    header::Field::Good(header::Kv2(&b"Content-Transfer-Encoding"[..], &b"7bit"[..])),
541                                ],
542                                ..mime::NaiveMIME::default()
543                            },
544                        },
545                        raw_part_inner: inner,
546                        raw_part_outer: body,
547                        children: vec![
548                            AnyPart::Txt(Text {
549                                mime: mime::MIME {
550                                    interpreted_type: mime::r#type::Deductible::Explicit(mime::r#type::Text {
551                                        subtype: mime::r#type::TextSubtype::Plain,
552                                        charset: mime::r#type::Deductible::Explicit(mime::charset::EmailCharset::UTF_8),
553                                    }),
554                                    fields: mime::NaiveMIME {
555                                        ctype: Some(mime::r#type::NaiveType {
556                                            main: &b"text"[..],
557                                            sub: &b"plain"[..],
558                                            params: vec![
559                                                mime::r#type::Parameter {
560                                                    name: &b"charset"[..],
561                                                    value: MIMEWord::Atom(&b"utf-8"[..]),
562                                                }
563                                            ]
564                                        }),
565                                        transfer_encoding: mime::mechanism::Mechanism::QuotedPrintable,
566                                        kv: vec![
567                                            header::Field::Good(header::Kv2(&b"Content-Type"[..], &b"text/plain; charset=utf-8"[..])),
568                                            header::Field::Good(header::Kv2(&b"Content-Transfer-Encoding"[..], &b"quoted-printable"[..])),
569                                        ],
570                                        raw: &b"Content-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n"[..],
571                                        ..mime::NaiveMIME::default()
572                                    }
573                                },
574                                body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
575                            }),
576                            AnyPart::Txt(Text {
577                                mime: mime::MIME {
578                                    interpreted_type: mime::r#type::Deductible::Explicit(mime::r#type::Text {
579                                        subtype: mime::r#type::TextSubtype::Html,
580                                        charset: mime::r#type::Deductible::Explicit(mime::charset::EmailCharset::US_ASCII),
581                                    }),
582
583                                    fields: mime::NaiveMIME {
584                                        ctype: Some(mime::r#type::NaiveType {
585                                            main: &b"text"[..],
586                                            sub: &b"html"[..],
587                                            params: vec![
588                                                mime::r#type::Parameter {
589                                                    name: &b"charset"[..],
590                                                    value: MIMEWord::Atom(&b"us-ascii"[..]),
591                                                }
592                                            ]
593                                        }),
594                                        kv: vec![
595                                            header::Field::Good(header::Kv2(&b"Content-Type"[..], &b"text/html; charset=us-ascii"[..])),
596                                        ],
597                                        raw: &b"Content-Type: text/html; charset=us-ascii\n\n"[..],
598                                        ..mime::NaiveMIME::default()
599                                    },
600                                },
601                                body: &br#"<div style="text-align: center;"><strong>GZ</strong><br />
602OoOoO<br />
603oOoOoOoOo<br />
604oOoOoOoOoOoOoOoOo<br />
605oOoOoOoOoOoOoOoOoOoOoOo<br />
606oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
607OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
608</div>
609"#[..],
610                            }),
611                        ],
612                    })),
613                },
614            ))
615        );
616    }
617}