lettre/message/mailbox/
types.rs

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/// Represents an email address with an optional name for the sender/recipient.
15///
16/// This type contains email address and the sender/recipient name (_Some Name \<user@domain.tld\>_ or _withoutname@domain.tld_).
17///
18/// **NOTE**: Enable feature "serde" to be able to serialize/deserialize it using [serde](https://serde.rs/).
19///
20/// # Examples
21///
22/// You can create a `Mailbox` from a string and an [`Address`]:
23///
24/// ```
25/// # use lettre::{Address, message::Mailbox};
26/// # use std::error::Error;
27/// # fn main() -> Result<(), Box<dyn Error>> {
28/// let address = Address::new("example", "email.com")?;
29/// let mailbox = Mailbox::new(None, address);
30/// # Ok(())
31/// # }
32/// ```
33///
34/// You can also create one from a string literal:
35///
36/// ```
37/// # use lettre::message::Mailbox;
38/// # use std::error::Error;
39/// # fn main() -> Result<(), Box<dyn Error>> {
40/// let mailbox: Mailbox = "John Smith <example@email.com>".parse()?;
41/// # Ok(())
42/// # }
43/// ```
44#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
45pub struct Mailbox {
46    /// The name associated with the address.
47    pub name: Option<String>,
48
49    /// The email address itself.
50    pub email: Address,
51}
52
53impl Mailbox {
54    /// Creates a new `Mailbox` using an email address and the name of the recipient if there is one.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use lettre::{message::Mailbox, Address};
60    ///
61    /// # use std::error::Error;
62    /// # fn main() -> Result<(), Box<dyn Error>> {
63    /// let address = Address::new("example", "email.com")?;
64    /// let mailbox = Mailbox::new(None, address);
65    /// # Ok(())
66    /// # }
67    /// ```
68    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            // TODO: improve error management
119            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/// Represents a sequence of [`Mailbox`] instances.
135///
136/// This type contains a sequence of mailboxes (_Some Name \<user@domain.tld\>, Another Name \<other@domain.tld\>, withoutname@domain.tld, ..._).
137///
138/// **NOTE**: Enable feature "serde" to be able to serialize/deserialize it using [serde](https://serde.rs/).
139#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
140pub struct Mailboxes(Vec<Mailbox>);
141
142impl Mailboxes {
143    /// Creates a new list of [`Mailbox`] instances.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use lettre::message::Mailboxes;
149    /// let mailboxes = Mailboxes::new();
150    /// ```
151    pub fn new() -> Self {
152        Mailboxes(Vec::new())
153    }
154
155    /// Adds a new [`Mailbox`] to the list, in a builder style pattern.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use lettre::{
161    ///     message::{Mailbox, Mailboxes},
162    ///     Address,
163    /// };
164    ///
165    /// # use std::error::Error;
166    /// # fn main() -> Result<(), Box<dyn Error>> {
167    /// let address = Address::new("example", "email.com")?;
168    /// let mut mailboxes = Mailboxes::new().with(Mailbox::new(None, address));
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub fn with(mut self, mbox: Mailbox) -> Self {
173        self.0.push(mbox);
174        self
175    }
176
177    /// Adds a new [`Mailbox`] to the list, in a `Vec::push` style pattern.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use lettre::{
183    ///     message::{Mailbox, Mailboxes},
184    ///     Address,
185    /// };
186    ///
187    /// # use std::error::Error;
188    /// # fn main() -> Result<(), Box<dyn Error>> {
189    /// let address = Address::new("example", "email.com")?;
190    /// let mut mailboxes = Mailboxes::new();
191    /// mailboxes.push(Mailbox::new(None, address));
192    /// # Ok(())
193    /// # }
194    /// ```
195    pub fn push(&mut self, mbox: Mailbox) {
196        self.0.push(mbox);
197    }
198
199    /// Extracts the first [`Mailbox`] if it exists.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use lettre::{
205    ///     message::{Mailbox, Mailboxes},
206    ///     Address,
207    /// };
208    ///
209    /// # use std::error::Error;
210    /// # fn main() -> Result<(), Box<dyn Error>> {
211    /// let empty = Mailboxes::new();
212    /// assert!(empty.into_single().is_none());
213    ///
214    /// let mut mailboxes = Mailboxes::new();
215    /// let address = Address::new("example", "email.com")?;
216    ///
217    /// mailboxes.push(Mailbox::new(None, address));
218    /// assert!(mailboxes.into_single().is_some());
219    /// # Ok(())
220    /// # }
221    /// ```
222    pub fn into_single(self) -> Option<Mailbox> {
223        self.into()
224    }
225
226    /// Creates an iterator over the [`Mailbox`] instances that are currently stored.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use lettre::{
232    ///     message::{Mailbox, Mailboxes},
233    ///     Address,
234    /// };
235    ///
236    /// # use std::error::Error;
237    /// # fn main() -> Result<(), Box<dyn Error>> {
238    /// let mut mailboxes = Mailboxes::new();
239    ///
240    /// let address = Address::new("example", "email.com")?;
241    /// mailboxes.push(Mailbox::new(None, address));
242    ///
243    /// let address = Address::new("example", "email.com")?;
244    /// mailboxes.push(Mailbox::new(None, address));
245    ///
246    /// let mut iter = mailboxes.iter();
247    ///
248    /// assert!(iter.next().is_some());
249    /// assert!(iter.next().is_some());
250    ///
251    /// assert!(iter.next().is_none());
252    /// # Ok(())
253    /// # }
254    /// ```
255    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            // TODO: improve error management
350            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
361// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.6
362fn 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        // Quoted string: https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.5
367        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
377// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.4
378fn is_valid_atom_char(c: u8) -> bool {
379    matches!(c,
380		// Not really allowed but can be inserted between atoms.
381		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		// Not technically allowed but will be escaped into allowed characters.
408		128..=255)
409}
410
411// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.5
412fn write_quoted_string_char(f: &mut Formatter<'_>, c: char) -> FmtResult {
413    match c {
414        // Can not be encoded.
415        '\n' | '\r' => Err(std::fmt::Error),
416
417        // Note, not qcontent but can be put before or after any qcontent.
418        '\t' | ' ' => f.write_char(c),
419
420        c if match c as u32 {
421            // NO-WS-CTL: https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.1
422            1..=8 | 11 | 12 | 14..=31 | 127 |
423
424            // The rest of the US-ASCII except \ and "
425            33 |
426            35..=91 |
427            93..=126 |
428
429            // Non-ascii characters will be escaped separately later.
430            128.. => true,
431            _ => false,
432        } =>
433        {
434            f.write_char(c)
435        }
436
437        _ => {
438            // quoted-pair https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.2
439            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}