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}