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 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 }
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}