email_parser/parsing/
fields.rs

1use crate::address::*;
2use crate::parsing::time::*;
3use crate::prelude::*;
4use std::borrow::Cow;
5#[cfg(feature = "mime")]
6use std::collections::HashMap;
7
8#[derive(Debug)]
9pub enum TraceField<'a> {
10    Date(DateTime),
11    From(Vec<Mailbox<'a>>),
12    Sender(Mailbox<'a>),
13    To(Vec<Address<'a>>),
14    Cc(Vec<Address<'a>>),
15    Bcc(Vec<Address<'a>>),
16    MessageId((Cow<'a, str>, Cow<'a, str>)),
17}
18
19#[derive(Debug)]
20pub enum Field<'a> {
21    #[cfg(feature = "date")]
22    Date(DateTime),
23    #[cfg(feature = "from")]
24    From(Vec<Mailbox<'a>>),
25    #[cfg(feature = "sender")]
26    Sender(Mailbox<'a>),
27    #[cfg(feature = "reply-to")]
28    ReplyTo(Vec<Address<'a>>),
29    #[cfg(feature = "to")]
30    To(Vec<Address<'a>>),
31    #[cfg(feature = "cc")]
32    Cc(Vec<Address<'a>>),
33    #[cfg(feature = "bcc")]
34    Bcc(Vec<Address<'a>>),
35    #[cfg(feature = "message-id")]
36    MessageId((Cow<'a, str>, Cow<'a, str>)),
37    #[cfg(feature = "in-reply-to")]
38    InReplyTo(Vec<(Cow<'a, str>, Cow<'a, str>)>),
39    #[cfg(feature = "references")]
40    References(Vec<(Cow<'a, str>, Cow<'a, str>)>),
41    #[cfg(feature = "subject")]
42    Subject(Cow<'a, str>),
43    #[cfg(feature = "comments")]
44    Comments(Cow<'a, str>),
45    #[cfg(feature = "keywords")]
46    Keywords(Vec<Vec<Cow<'a, str>>>),
47    #[cfg(feature = "mime")]
48    MimeVersion(u8, u8),
49    #[cfg(feature = "mime")]
50    ContentType {
51        mime_type: ContentType<'a>,
52        subtype: Cow<'a, str>,
53        parameters: HashMap<Cow<'a, str>, Cow<'a, str>>,
54    },
55    #[cfg(feature = "mime")]
56    ContentTransferEncoding(ContentTransferEncoding<'a>),
57    #[cfg(feature = "mime")]
58    ContentId((Cow<'a, str>, Cow<'a, str>)),
59    #[cfg(feature = "mime")]
60    ContentDescription(Cow<'a, str>),
61    #[cfg(feature = "content-disposition")]
62    ContentDisposition(Disposition<'a>),
63    #[cfg(feature = "trace")]
64    Trace {
65        return_path: Option<Option<EmailAddress<'a>>>,
66        received: Vec<(Vec<ReceivedToken<'a>>, DateTime)>,
67        fields: Vec<TraceField<'a>>,
68    },
69    Unknown {
70        name: &'a str,
71        value: Cow<'a, str>,
72    },
73}
74
75pub fn fields(mut input: &[u8]) -> Res<Vec<Field>> {
76    let mut fields: Vec<Field> = Vec::new();
77
78    #[cfg(feature = "trace")]
79    while let Ok((new_input, trace)) = trace(input) {
80        input = new_input;
81        let mut trace_fields = Vec::new();
82
83        while let Ok((new_input, new_result)) = match_parsers(
84            input,
85            &mut [
86                |i| resent_date(i).map(|(i, v)| (i, TraceField::Date(v))),
87                |i| resent_from(i).map(|(i, v)| (i, TraceField::From(v))),
88                |i| resent_sender(i).map(|(i, v)| (i, TraceField::Sender(v))),
89                |i| resent_to(i).map(|(i, v)| (i, TraceField::To(v))),
90                |i| resent_cc(i).map(|(i, v)| (i, TraceField::Cc(v))),
91                |i| resent_bcc(i).map(|(i, v)| (i, TraceField::Bcc(v))),
92                |i| resent_message_id(i).map(|(i, v)| (i, TraceField::MessageId(v))),
93            ][..],
94        ) {
95            input = new_input;
96            trace_fields.push(new_result);
97        }
98
99        // TODO optional fields
100
101        fields.push(Field::Trace {
102            return_path: trace.0,
103            received: trace.1,
104            fields: trace_fields,
105        });
106    }
107
108    while let Ok((new_input, field)) = match_parsers(
109        input,
110        &mut [
111            #[cfg(feature = "date")]
112            |i| date(i).map(|(i, v)| (i, Field::Date(v))),
113            #[cfg(feature = "from")]
114            |i| from(i).map(|(i, v)| (i, Field::From(v))),
115            #[cfg(feature = "sender")]
116            |i| sender(i).map(|(i, v)| (i, Field::Sender(v))),
117            #[cfg(feature = "reply-to")]
118            |i| reply_to(i).map(|(i, v)| (i, Field::ReplyTo(v))),
119            #[cfg(feature = "to")]
120            |i| to(i).map(|(i, v)| (i, Field::To(v))),
121            #[cfg(feature = "cc")]
122            |i| cc(i).map(|(i, v)| (i, Field::Cc(v))),
123            #[cfg(feature = "bcc")]
124            |i| bcc(i).map(|(i, v)| (i, Field::Bcc(v))),
125            #[cfg(feature = "message-id")]
126            |i| message_id(i).map(|(i, v)| (i, Field::MessageId(v))),
127            #[cfg(feature = "in-reply-to")]
128            |i| in_reply_to(i).map(|(i, v)| (i, Field::InReplyTo(v))),
129            #[cfg(feature = "references")]
130            |i| references(i).map(|(i, v)| (i, Field::References(v))),
131            #[cfg(feature = "subject")]
132            |i| subject(i).map(|(i, v)| (i, Field::Subject(v))),
133            #[cfg(feature = "comments")]
134            |i| comments(i).map(|(i, v)| (i, Field::Comments(v))),
135            #[cfg(feature = "mime")]
136            |i| mime_version(i).map(|(i, (mj, mn))| (i, Field::MimeVersion(mj, mn))),
137            #[cfg(feature = "mime")]
138            |i| {
139                content_type(i).map(|(i, (t, st, p))| {
140                    (
141                        i,
142                        Field::ContentType {
143                            mime_type: t,
144                            subtype: st,
145                            parameters: p,
146                        },
147                    )
148                })
149            },
150            #[cfg(feature = "mime")]
151            |i| content_transfer_encoding(i).map(|(i, e)| (i, Field::ContentTransferEncoding(e))),
152            #[cfg(feature = "mime")]
153            |i| content_id(i).map(|(i, v)| (i, Field::ContentId(v))),
154            #[cfg(feature = "mime")]
155            |i| content_description(i).map(|(i, d)| (i, Field::ContentDescription(d))),
156            #[cfg(feature = "content-disposition")]
157            |i| content_disposition(i).map(|(i, d)| (i, Field::ContentDisposition(d))),
158            #[cfg(feature = "keywords")]
159            |i| keywords(i).map(|(i, v)| (i, Field::Keywords(v))),
160            |i| unknown(i).map(|(i, (name, value))| (i, Field::Unknown { name, value })),
161        ][..],
162    ) {
163        input = new_input;
164        fields.push(field);
165    }
166
167    Ok((input, fields))
168}
169
170pub fn date(input: &[u8]) -> Res<DateTime> {
171    let (input, ()) = tag_no_case(input, b"Date:", b"dATE:")?;
172    let (input, date_time) = date_time(input)?;
173    let (input, ()) = tag(input, b"\r\n")?;
174
175    Ok((input, date_time))
176}
177
178pub fn from(input: &[u8]) -> Res<Vec<Mailbox>> {
179    let (input, ()) = tag_no_case(input, b"From:", b"fROM:")?;
180    let (input, mailbox_list) = mailbox_list(input)?;
181    let (input, ()) = tag(input, b"\r\n")?;
182
183    Ok((input, mailbox_list))
184}
185
186pub fn sender(input: &[u8]) -> Res<Mailbox> {
187    let (input, ()) = tag_no_case(input, b"Sender:", b"sENDER:")?;
188    let (input, mailbox) = mailbox(input)?;
189    let (input, ()) = tag(input, b"\r\n")?;
190
191    Ok((input, mailbox))
192}
193
194pub fn reply_to(input: &[u8]) -> Res<Vec<Address>> {
195    let (input, ()) = tag_no_case(input, b"Reply-To:", b"rEPLY-tO:")?;
196    let (input, mailbox) = address_list(input)?;
197    let (input, ()) = tag(input, b"\r\n")?;
198
199    Ok((input, mailbox))
200}
201
202pub fn to(input: &[u8]) -> Res<Vec<Address>> {
203    let (input, ()) = tag_no_case(input, b"To:", b"tO:")?;
204    let (input, mailbox) = address_list(input)?;
205    let (input, ()) = tag(input, b"\r\n")?;
206
207    Ok((input, mailbox))
208}
209
210pub fn cc(input: &[u8]) -> Res<Vec<Address>> {
211    let (input, ()) = tag_no_case(input, b"Cc:", b"cC:")?;
212    let (input, mailbox) = address_list(input)?;
213    let (input, ()) = tag(input, b"\r\n")?;
214
215    Ok((input, mailbox))
216}
217
218pub fn bcc(input: &[u8]) -> Res<Vec<Address>> {
219    let (input, ()) = tag_no_case(input, b"Bcc:", b"bCC:")?;
220    let (input, mailbox) = if let Ok((input, list)) = address_list(input) {
221        (input, list)
222    } else if let Ok((input, _cfws)) = cfws(input) {
223        (input, Vec::new())
224    } else {
225        return Err(Error::Unknown("Invalid bcc field"));
226    };
227    let (input, ()) = tag(input, b"\r\n")?;
228
229    Ok((input, mailbox))
230}
231
232pub fn message_id(input: &[u8]) -> Res<(Cow<str>, Cow<str>)> {
233    let (input, ()) = tag_no_case(input, b"Message-ID:", b"mESSAGE-id:")?;
234    let (input, id) = crate::parsing::address::message_id(input)?;
235    let (input, ()) = tag(input, b"\r\n")?;
236
237    Ok((input, id))
238}
239
240pub fn in_reply_to(input: &[u8]) -> Res<Vec<(Cow<str>, Cow<str>)>> {
241    let (input, ()) = tag_no_case(input, b"In-Reply-To:", b"iN-rEPLY-tO:")?;
242    let (input, ids) = many1(input, crate::parsing::address::message_id)?;
243    let (input, ()) = tag(input, b"\r\n")?;
244
245    Ok((input, ids))
246}
247
248pub fn references(input: &[u8]) -> Res<Vec<(Cow<str>, Cow<str>)>> {
249    let (input, ()) = tag_no_case(input, b"References:", b"rEFERENCES:")?;
250    let (input, ids) = many1(input, crate::parsing::address::message_id)?;
251    let (input, ()) = tag(input, b"\r\n")?;
252
253    Ok((input, ids))
254}
255
256pub fn subject(input: &[u8]) -> Res<Cow<str>> {
257    let (input, ()) = tag_no_case(input, b"Subject:", b"sUBJECT:")?;
258    #[cfg(not(feature = "mime"))]
259    let (input, subject) = unstructured(input)?;
260    #[cfg(feature = "mime")]
261    let (input, subject) = mime_unstructured(input)?;
262    let (input, ()) = tag(input, b"\r\n")?;
263
264    Ok((input, subject))
265}
266
267pub fn comments(input: &[u8]) -> Res<Cow<str>> {
268    let (input, ()) = tag_no_case(input, b"Comments:", b"cOMMENTS:")?;
269    #[cfg(not(feature = "mime"))]
270    let (input, comments) = unstructured(input)?;
271    #[cfg(feature = "mime")]
272    let (input, comments) = mime_unstructured(input)?;
273    let (input, ()) = tag(input, b"\r\n")?;
274
275    Ok((input, comments))
276}
277
278pub fn keywords(input: &[u8]) -> Res<Vec<Vec<Cow<str>>>> {
279    let (input, ()) = tag_no_case(input, b"Keywords:", b"kEYWORDS:")?;
280
281    let mut keywords = Vec::new();
282    let (mut input, first_keyword) = phrase(input)?;
283    keywords.push(first_keyword);
284
285    while let Ok((new_input, new_keyword)) = prefixed(input, phrase, ",") {
286        input = new_input;
287        keywords.push(new_keyword);
288    }
289
290    let (input, ()) = tag(input, b"\r\n")?;
291
292    Ok((input, keywords))
293}
294
295pub fn resent_date(input: &[u8]) -> Res<DateTime> {
296    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
297    let (input, date) = date(input)?;
298
299    Ok((input, date))
300}
301
302pub fn resent_from(input: &[u8]) -> Res<Vec<Mailbox>> {
303    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
304    let (input, from) = from(input)?;
305
306    Ok((input, from))
307}
308
309pub fn resent_sender(input: &[u8]) -> Res<Mailbox> {
310    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
311    let (input, sender) = sender(input)?;
312
313    Ok((input, sender))
314}
315
316pub fn resent_to(input: &[u8]) -> Res<Vec<Address>> {
317    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
318    let (input, to) = to(input)?;
319
320    Ok((input, to))
321}
322
323pub fn resent_cc(input: &[u8]) -> Res<Vec<Address>> {
324    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
325    let (input, cc) = cc(input)?;
326
327    Ok((input, cc))
328}
329
330pub fn resent_bcc(input: &[u8]) -> Res<Vec<Address>> {
331    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
332    let (input, bcc) = bcc(input)?;
333
334    Ok((input, bcc))
335}
336
337pub fn resent_message_id(input: &[u8]) -> Res<(Cow<str>, Cow<str>)> {
338    let (input, ()) = tag_no_case(input, b"Resent-", b"rESENT-")?;
339    let (input, id) = message_id(input)?;
340
341    Ok((input, id))
342}
343
344pub fn return_path(input: &[u8]) -> Res<Option<EmailAddress>> {
345    fn empty_path(input: &[u8]) -> Res<()> {
346        let (input, _cfws) = optional(input, cfws);
347        let (input, ()) = tag(input, b"<")?;
348        let (input, _cfws) = optional(input, cfws);
349        let (input, ()) = tag(input, b">")?;
350        let (input, _cfws) = optional(input, cfws);
351        Ok((input, ()))
352    }
353
354    let (input, ()) = tag_no_case(input, b"Return-Path:", b"rETURN-pATH:")?;
355    let (input, addr) = match_parsers(
356        input,
357        &mut [
358            (|i| angle_addr(i).map(|(i, v)| (i, Some(v))))
359                as fn(input: &[u8]) -> Res<Option<EmailAddress>>,
360            (|i| empty_path(i).map(|(i, _)| (i, None)))
361                as fn(input: &[u8]) -> Res<Option<EmailAddress>>,
362        ][..],
363    )?;
364    let (input, ()) = tag(input, b"\r\n")?;
365
366    Ok((input, addr))
367}
368
369#[derive(Debug)]
370pub enum ReceivedToken<'a> {
371    Word(Cow<'a, str>),
372    Addr(EmailAddress<'a>),
373    Domain(Cow<'a, str>),
374}
375
376pub fn received(input: &[u8]) -> Res<(Vec<ReceivedToken>, DateTime)> {
377    let (input, ()) = tag_no_case(input, b"Received:", b"rECEIVED:")?;
378    let (input, received_tokens) = many(input, |input| {
379        if let Ok((word_input, word)) = word(input) {
380            if let Ok((domain_input, domain)) = domain(input) {
381                if domain.len() > word.len() {
382                    return Ok((domain_input, ReceivedToken::Domain(domain)));
383                }
384            }
385            Ok((word_input, ReceivedToken::Word(word)))
386        } else if let Ok((input, addr)) = angle_addr(input) {
387            Ok((input, ReceivedToken::Addr(addr)))
388        } else if let Ok((input, addr)) = addr_spec(input) {
389            Ok((input, ReceivedToken::Addr(addr)))
390        } else if let Ok((input, domain)) = domain(input) {
391            Ok((input, ReceivedToken::Domain(domain)))
392        } else {
393            Err(Error::Unknown("match error"))
394        }
395    })?;
396    let (input, ()) = tag(input, b";")?;
397    let (input, date_time) = date_time(input)?;
398    let (input, ()) = tag(input, b"\r\n")?;
399
400    Ok((input, (received_tokens, date_time)))
401}
402
403pub fn trace(
404    input: &[u8],
405) -> Res<(
406    Option<Option<EmailAddress>>,
407    Vec<(Vec<ReceivedToken>, DateTime)>,
408)> {
409    let (input, return_path) = optional(input, return_path);
410    let (input, received) = many1(input, received)?;
411
412    Ok((input, (return_path, received)))
413}
414
415pub fn unknown(input: &[u8]) -> Res<(&str, Cow<str>)> {
416    let (input, name) = take_while1(input, is_ftext)?;
417    let (input, ()) = tag(input, b":")?;
418    #[cfg(not(feature = "unrecognized-headers"))]
419    let (input, value) = unstructured(input)?;
420    #[cfg(feature = "unrecognized-headers")]
421    let (input, value) = mime_unstructured(input)?;
422
423    let (input, ()) = tag(input, b"\r\n")?;
424
425    Ok((input, (name, value)))
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_fields() {
434        assert!(fields(
435            b"To: Mubelotix <mubelotix@gmail.com>\r\nFrOm: Mubelotix <mubelotix@gmail.com>\r\n"
436        )
437        .unwrap()
438        .0
439        .is_empty());
440        //println!("{:#?}", fields(include_bytes!("../../mail.txt")).unwrap().1);
441    }
442
443    #[test]
444    fn test_unknown_field() {
445        assert_eq!(
446            unknown(b"hidden-field:hidden message\r\n").unwrap().1 .1,
447            "hidden message"
448        );
449        assert_eq!(
450            unknown(b"hidden-field:hidden message\r\n").unwrap().1 .0,
451            "hidden-field"
452        );
453    }
454
455    #[test]
456    fn test_trace() {
457        assert!(return_path(b"Return-Path:<>\r\n").unwrap().1.is_none());
458        assert_eq!(
459            return_path(b"Return-Path:<mubelotix@gmail.com>\r\n")
460                .unwrap()
461                .1
462                .unwrap()
463                .local_part,
464            "mubelotix"
465        );
466
467        assert!(matches!(
468            received(b"Received:test<mubelotix@gmail.com>;5 May 2003 18:59:03 +0000\r\n")
469                .unwrap()
470                .1
471                 .0[0],
472            ReceivedToken::Word(_)
473        ));
474        assert!(matches!(
475            received(b"Received:test<mubelotix@gmail.com>;5 May 2003 18:59:03 +0000\r\n")
476                .unwrap()
477                .1
478                 .0[1],
479            ReceivedToken::Addr(_)
480        ));
481        assert!(matches!(
482            received(b"Received:mubelotix.dev;5 May 2003 18:59:03 +0000\r\n")
483                .unwrap()
484                .1
485                 .0[0],
486            ReceivedToken::Domain(_)
487        ));
488
489        assert!(trace(b"Return-Path:<>\r\nReceived:akala miam miam;5 May 2003 18:59:03 +0000\r\nReceived:mubelotix.dev;5 May 2003 18:59:03 +0000\r\n").unwrap().0.is_empty());
490    }
491
492    #[test]
493    fn test_resent() {
494        assert!(resent_date(b"Resent-Date:5 May 2003 18:59:03 +0000\r\n").is_ok());
495        assert_eq!(
496            resent_from(b"Resent-FrOm: Mubelotix <mubelotix@gmail.com>\r\n")
497                .unwrap()
498                .1[0]
499                .address
500                .local_part,
501            "mubelotix"
502        );
503        assert_eq!(
504            resent_sender(b"Resent-sender: Mubelotix <mubelotix@gmail.com>\r\n")
505                .unwrap()
506                .1
507                .address
508                .domain,
509            "gmail.com"
510        );
511        assert!(
512            !resent_to(b"Resent-To: Mubelotix <mubelotix@gmail.com>\r\n")
513                .unwrap()
514                .1
515                .is_empty()
516        );
517        assert!(
518            !resent_cc(b"Resent-Cc: Mubelotix <mubelotix@gmail.com>\r\n")
519                .unwrap()
520                .1
521                .is_empty()
522        );
523        assert!(
524            !resent_bcc(b"Resent-Bcc: Mubelotix <mubelotix@gmail.com>\r\n")
525                .unwrap()
526                .1
527                .is_empty()
528        );
529    }
530
531    #[test]
532    fn test_date() {
533        assert!(date(b"Date:5 May 2003 18:59:03 +0000\r\n").is_ok());
534    }
535
536    #[test]
537    fn test_originators() {
538        assert_eq!(
539            from(b"FrOm: Mubelotix <mubelotix@gmail.com>\r\n")
540                .unwrap()
541                .1[0]
542                .address
543                .local_part,
544            "mubelotix"
545        );
546        assert_eq!(
547            sender(b"sender: Mubelotix <mubelotix@gmail.com>\r\n")
548                .unwrap()
549                .1
550                .address
551                .domain,
552            "gmail.com"
553        );
554        assert_eq!(
555            reply_to(b"Reply-to: Mubelotix <mubelotix@gmail.com>\r\n")
556                .unwrap()
557                .1
558                .len(),
559            1
560        );
561    }
562
563    #[test]
564    fn test_destination() {
565        assert!(!to(b"To: Mubelotix <mubelotix@gmail.com>\r\n")
566            .unwrap()
567            .1
568            .is_empty());
569        assert!(!cc(b"Cc: Mubelotix <mubelotix@gmail.com>\r\n")
570            .unwrap()
571            .1
572            .is_empty());
573        assert!(!bcc(b"Bcc: Mubelotix <mubelotix@gmail.com>\r\n")
574            .unwrap()
575            .1
576            .is_empty());
577        assert!(bcc(b"Bcc: \r\n \r\n").unwrap().1.is_empty());
578    }
579
580    #[test]
581    fn test_ids() {
582        assert_eq!(
583            message_id(b"Message-ID:<556100154@gmail.com>\r\n")
584                .unwrap()
585                .1
586                 .0,
587            "556100154"
588        );
589        assert_eq!(
590            message_id(b"Message-ID:<556100154@gmail.com>\r\n")
591                .unwrap()
592                .1
593                 .1,
594            "gmail.com"
595        );
596
597        assert_eq!(
598            references(b"References:<qzdzdq@qdz.com><dzdzjd@zdzdj.dz>\r\n")
599                .unwrap()
600                .1
601                .len(),
602            2
603        );
604
605        assert_eq!(
606            in_reply_to(b"In-Reply-To:<eefes@qzd.fr><52@s.dz><adzd@zd.d>\r\n")
607                .unwrap()
608                .1
609                .len(),
610            3
611        );
612    }
613
614    #[test]
615    fn test_informational() {
616        assert_eq!(
617            subject(b"Subject:French school is boring\r\n").unwrap().1,
618            "French school is boring"
619        );
620        assert_eq!(
621            subject(b"Subject:Folding\r\n is slow\r\n").unwrap().1,
622            "Folding is slow"
623        );
624
625        assert_eq!(
626            comments(b"Comments:Rust is great\r\n").unwrap().1,
627            "Rust is great"
628        );
629
630        assert_eq!(
631            keywords(b"Keywords:rust parser fast zero copy,email rfc5322\r\n")
632                .unwrap()
633                .1
634                .len(),
635            2
636        );
637    }
638
639    #[test]
640    #[cfg(all(feature = "mime", feature = "unrecognized-headers"))]
641    fn test_mime_encoding() {
642        assert_eq!(
643            subject(b"Subject: =?UTF-8?B?8J+OiEJpcnRoZGF5IEdpdmVhd2F58J+OiA==?= Win free stickers\r\n from daily.dev =?UTF-8?B?8J+MiA==?=\r\n").unwrap().1,
644            " 🎈Birthday Giveaway🎈 Win free stickers from daily.dev 🌈"
645        );
646
647        assert_eq!(
648            comments(b"Comments: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\r\n =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\r\n").unwrap().1,
649            " If you can read this you understand the example."
650        );
651
652        assert_eq!(
653            from(b"From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\r\n")
654                .unwrap()
655                .1[0]
656                .name
657                .as_ref()
658                .unwrap()[0],
659            "Keith Moore"
660        );
661
662        assert_eq!(
663            unknown(b"X-SG-EID:\r\n =?us-ascii?Q?t3vk5cTFE=2FYEGeQ8h3SwrnzIAGc=2F+ADymlys=2FfRFW4Zjpt=2F3MuaO9JNHS2enYQ?=\r\n =?us-ascii?Q?Jsv0=2FpYrPem+YssHetKlrE5nJnOfr=2FYdJOyJFf8?=\r\n =?us-ascii?Q?3mRuMRE9KGu=2F5O75=2FwwN6dG14nuP4SyMIZwbMdG?=\r\n =?us-ascii?Q?vXmM2kgcM=2FOalKeT03BMp4YCg9h1LhkV6PZEoHB?=\r\n =?us-ascii?Q?d4tcAvNZQqLaA4ykI1EpNxKVVyZXVWqTp2uisdf?=\r\n =?us-ascii?Q?HB=2F6BKcIs+XSDNeakQqmn=2FwAqOk78AvtRB5LnNL?=\r\n =?us-ascii?Q?lz3oRXlMZbdFgRH+KAyLQ=3D=3D?=\r\n").unwrap().1.1,
664            " t3vk5cTFE/YEGeQ8h3SwrnzIAGc/+ADymlys/fRFW4Zjpt/3MuaO9JNHS2enYQJsv0/pYrPem+YssHetKlrE5nJnOfr/YdJOyJFf83mRuMRE9KGu/5O75/wwN6dG14nuP4SyMIZwbMdGvXmM2kgcM/OalKeT03BMp4YCg9h1LhkV6PZEoHBd4tcAvNZQqLaA4ykI1EpNxKVVyZXVWqTp2uisdfHB/6BKcIs+XSDNeakQqmn/wAqOk78AvtRB5LnNLlz3oRXlMZbdFgRH+KAyLQ=="
665        );
666    }
667}