1use std::{
2 fmt::{Display, Formatter, Result as FmtResult, Write},
3 mem,
4 slice::Iter,
5 str::FromStr,
6};
7
8use chumsky::prelude::*;
9use email_encoding::headers::writer::EmailWriter;
10
11use super::parsers;
12use crate::address::{Address, AddressError};
13
14#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
45pub struct Mailbox {
46 pub name: Option<String>,
48
49 pub email: Address,
51}
52
53impl Mailbox {
54 pub fn new(name: Option<String>, email: Address) -> Self {
69 Mailbox { name, email }
70 }
71
72 pub(crate) fn encode(&self, w: &mut EmailWriter<'_>) -> FmtResult {
73 if let Some(name) = &self.name {
74 email_encoding::headers::quoted_string::encode(name, w)?;
75 w.space();
76 w.write_char('<')?;
77 }
78
79 w.write_str(self.email.as_ref())?;
80
81 if self.name.is_some() {
82 w.write_char('>')?;
83 }
84
85 Ok(())
86 }
87}
88
89impl Display for Mailbox {
90 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
91 if let Some(name) = &self.name {
92 let name = name.trim();
93 if !name.is_empty() {
94 write_word(f, name)?;
95 f.write_str(" <")?;
96 self.email.fmt(f)?;
97 return f.write_char('>');
98 }
99 }
100 self.email.fmt(f)
101 }
102}
103
104impl<S: Into<String>, T: Into<String>> TryFrom<(S, T)> for Mailbox {
105 type Error = AddressError;
106
107 fn try_from(header: (S, T)) -> Result<Self, Self::Error> {
108 let (name, address) = header;
109 Ok(Mailbox::new(Some(name.into()), address.into().parse()?))
110 }
111}
112
113impl FromStr for Mailbox {
114 type Err = AddressError;
115
116 fn from_str(src: &str) -> Result<Mailbox, Self::Err> {
117 let (name, (user, domain)) = parsers::mailbox().parse(src).map_err(|_errs| {
118 AddressError::InvalidInput
120 })?;
121
122 let mailbox = Mailbox::new(name, Address::new(user, domain)?);
123
124 Ok(mailbox)
125 }
126}
127
128impl From<Address> for Mailbox {
129 fn from(value: Address) -> Self {
130 Self::new(None, value)
131 }
132}
133
134#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
140pub struct Mailboxes(Vec<Mailbox>);
141
142impl Mailboxes {
143 pub fn new() -> Self {
152 Mailboxes(Vec::new())
153 }
154
155 pub fn with(mut self, mbox: Mailbox) -> Self {
173 self.0.push(mbox);
174 self
175 }
176
177 pub fn push(&mut self, mbox: Mailbox) {
196 self.0.push(mbox);
197 }
198
199 pub fn into_single(self) -> Option<Mailbox> {
223 self.into()
224 }
225
226 pub fn iter(&self) -> Iter<'_, Mailbox> {
256 self.0.iter()
257 }
258
259 pub(crate) fn encode(&self, w: &mut EmailWriter<'_>) -> FmtResult {
260 let mut first = true;
261 for mailbox in self.iter() {
262 if !mem::take(&mut first) {
263 w.write_char(',')?;
264 w.space();
265 }
266
267 mailbox.encode(w)?;
268 }
269
270 Ok(())
271 }
272}
273
274impl Default for Mailboxes {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280impl From<Mailbox> for Mailboxes {
281 fn from(mailbox: Mailbox) -> Self {
282 Mailboxes(vec![mailbox])
283 }
284}
285
286impl From<Mailboxes> for Option<Mailbox> {
287 fn from(mailboxes: Mailboxes) -> Option<Mailbox> {
288 mailboxes.into_iter().next()
289 }
290}
291
292impl From<Vec<Mailbox>> for Mailboxes {
293 fn from(vec: Vec<Mailbox>) -> Self {
294 Mailboxes(vec)
295 }
296}
297
298impl From<Mailboxes> for Vec<Mailbox> {
299 fn from(mailboxes: Mailboxes) -> Vec<Mailbox> {
300 mailboxes.0
301 }
302}
303
304impl FromIterator<Mailbox> for Mailboxes {
305 fn from_iter<T: IntoIterator<Item = Mailbox>>(iter: T) -> Self {
306 Self(Vec::from_iter(iter))
307 }
308}
309
310impl Extend<Mailbox> for Mailboxes {
311 fn extend<T: IntoIterator<Item = Mailbox>>(&mut self, iter: T) {
312 self.0.extend(iter);
313 }
314}
315
316impl IntoIterator for Mailboxes {
317 type Item = Mailbox;
318 type IntoIter = ::std::vec::IntoIter<Mailbox>;
319
320 fn into_iter(self) -> Self::IntoIter {
321 self.0.into_iter()
322 }
323}
324
325impl Display for Mailboxes {
326 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
327 let mut iter = self.iter();
328
329 if let Some(mbox) = iter.next() {
330 mbox.fmt(f)?;
331
332 for mbox in iter {
333 f.write_str(", ")?;
334 mbox.fmt(f)?;
335 }
336 }
337
338 Ok(())
339 }
340}
341
342impl FromStr for Mailboxes {
343 type Err = AddressError;
344
345 fn from_str(src: &str) -> Result<Self, Self::Err> {
346 let mut mailboxes = Vec::new();
347
348 let parsed_mailboxes = parsers::mailbox_list().parse(src).map_err(|_errs| {
349 AddressError::InvalidInput
351 })?;
352
353 for (name, (user, domain)) in parsed_mailboxes {
354 mailboxes.push(Mailbox::new(name, Address::new(user, domain)?));
355 }
356
357 Ok(Mailboxes(mailboxes))
358 }
359}
360
361fn write_word(f: &mut Formatter<'_>, s: &str) -> FmtResult {
363 if s.as_bytes().iter().copied().all(is_valid_atom_char) {
364 f.write_str(s)
365 } else {
366 f.write_char('"')?;
368 for c in s.chars() {
369 write_quoted_string_char(f, c)?;
370 }
371 f.write_char('"')?;
372
373 Ok(())
374 }
375}
376
377fn is_valid_atom_char(c: u8) -> bool {
379 matches!(c,
380 b'\t' |
382 b' ' |
383
384 b'!' |
385 b'#' |
386 b'$' |
387 b'%' |
388 b'&' |
389 b'\'' |
390 b'*' |
391 b'+' |
392 b'-' |
393 b'/' |
394 b'0'..=b'8' |
395 b'=' |
396 b'?' |
397 b'A'..=b'Z' |
398 b'^' |
399 b'_' |
400 b'`' |
401 b'a'..=b'z' |
402 b'{' |
403 b'|' |
404 b'}' |
405 b'~' |
406
407 128..=255)
409}
410
411fn write_quoted_string_char(f: &mut Formatter<'_>, c: char) -> FmtResult {
413 match c {
414 '\n' | '\r' => Err(std::fmt::Error),
416
417 '\t' | ' ' => f.write_char(c),
419
420 c if match c as u32 {
421 1..=8 | 11 | 12 | 14..=31 | 127 |
423
424 33 |
426 35..=91 |
427 93..=126 |
428
429 128.. => true,
431 _ => false,
432 } =>
433 {
434 f.write_char(c)
435 }
436
437 _ => {
438 f.write_char('\\')?;
440 f.write_char(c)
441 }
442 }
443}
444
445#[cfg(test)]
446mod test {
447 use pretty_assertions::assert_eq;
448
449 use super::Mailbox;
450
451 #[test]
452 fn mailbox_format_address_only() {
453 assert_eq!(
454 format!(
455 "{}",
456 Mailbox::new(None, "kayo@example.com".parse().unwrap())
457 ),
458 "kayo@example.com"
459 );
460 }
461
462 #[test]
463 fn mailbox_format_address_with_name() {
464 assert_eq!(
465 format!(
466 "{}",
467 Mailbox::new(Some("K.".into()), "kayo@example.com".parse().unwrap())
468 ),
469 "\"K.\" <kayo@example.com>"
470 );
471 }
472
473 #[test]
474 fn mailbox_format_address_with_comma() {
475 assert_eq!(
476 format!(
477 "{}",
478 Mailbox::new(
479 Some("Last, First".into()),
480 "kayo@example.com".parse().unwrap()
481 )
482 ),
483 r#""Last, First" <kayo@example.com>"#
484 );
485 }
486
487 #[test]
488 fn mailbox_format_address_with_comma_and_non_ascii() {
489 assert_eq!(
490 format!(
491 "{}",
492 Mailbox::new(
493 Some("Laşt, First".into()),
494 "kayo@example.com".parse().unwrap()
495 )
496 ),
497 r#""Laşt, First" <kayo@example.com>"#
498 );
499 }
500
501 #[test]
502 fn mailbox_format_address_with_comma_and_quoted_non_ascii() {
503 assert_eq!(
504 format!(
505 "{}",
506 Mailbox::new(
507 Some(r#"Laşt, "First""#.into()),
508 "kayo@example.com".parse().unwrap()
509 )
510 ),
511 r#""Laşt, \"First\"" <kayo@example.com>"#
512 );
513 }
514
515 #[test]
516 fn mailbox_format_address_with_color() {
517 assert_eq!(
518 format!(
519 "{}",
520 Mailbox::new(
521 Some("Chris's Wiki :: blog".into()),
522 "kayo@example.com".parse().unwrap()
523 )
524 ),
525 r#""Chris's Wiki :: blog" <kayo@example.com>"#
526 );
527 }
528
529 #[test]
530 fn format_address_with_empty_name() {
531 assert_eq!(
532 format!(
533 "{}",
534 Mailbox::new(Some("".to_owned()), "kayo@example.com".parse().unwrap())
535 ),
536 "kayo@example.com"
537 );
538 }
539
540 #[test]
541 fn format_address_with_name_trim() {
542 assert_eq!(
543 format!(
544 "{}",
545 Mailbox::new(Some(" K. ".into()), "kayo@example.com".parse().unwrap())
546 ),
547 "\"K.\" <kayo@example.com>"
548 );
549 }
550
551 #[test]
552 fn parse_address_only() {
553 assert_eq!(
554 "kayo@example.com".parse(),
555 Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
556 );
557 }
558
559 #[test]
560 fn parse_address_only_trim() {
561 assert_eq!(
562 " kayo@example.com ".parse(),
563 Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
564 );
565 }
566
567 #[test]
568 fn parse_address_with_name() {
569 assert_eq!(
570 "K. <kayo@example.com>".parse(),
571 Ok(Mailbox::new(
572 Some("K.".into()),
573 "kayo@example.com".parse().unwrap()
574 ))
575 );
576 }
577
578 #[test]
579 fn parse_address_with_name_trim() {
580 assert_eq!(
581 " K. <kayo@example.com> ".parse(),
582 Ok(Mailbox::new(
583 Some("K.".into()),
584 "kayo@example.com".parse().unwrap()
585 ))
586 );
587 }
588
589 #[test]
590 fn parse_address_with_empty_name() {
591 assert_eq!(
592 "<kayo@example.com>".parse(),
593 Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
594 );
595 }
596
597 #[test]
598 fn parse_address_with_empty_name_trim() {
599 assert_eq!(
600 " <kayo@example.com> ".parse(),
601 Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
602 );
603 }
604
605 #[test]
606 fn parse_address_from_tuple() {
607 assert_eq!(
608 ("K.".to_owned(), "kayo@example.com".to_owned()).try_into(),
609 Ok(Mailbox::new(
610 Some("K.".into()),
611 "kayo@example.com".parse().unwrap()
612 ))
613 );
614 }
615}