Skip to content

Commit

Permalink
Properly quote mailbox name (lettre#700)
Browse files Browse the repository at this point in the history
This quoting is performed according to https://datatracker.ietf.org/doc/html/rfc2822.

Note that the obsolete phrase specification allows periods in the mailbox name. This does not implement the obsolete specification, instead periods force the mailbox to use a quoted string.

Fixes lettre#698
  • Loading branch information
kevincox authored Nov 17, 2021
1 parent 60399a9 commit 5e3ebbb
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 8 deletions.
8 changes: 4 additions & 4 deletions src/message/header/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ mod test {

#[test]
fn format_single_with_name() {
let from = Mailboxes::new().with("K. <[email protected]>".parse().unwrap());
let from = Mailboxes::new().with("Kayo <[email protected]>".parse().unwrap());

let mut headers = Headers::new();
headers.set(From(from));

assert_eq!(headers.to_string(), "From: K. <[email protected]>\r\n");
assert_eq!(headers.to_string(), "From: Kayo <[email protected]>\r\n");
}

#[test]
Expand All @@ -201,7 +201,7 @@ mod test {
#[test]
fn format_multi_with_name() {
let from = vec![
"K. <[email protected]>".parse().unwrap(),
"Kayo <[email protected]>".parse().unwrap(),
"Pony P. <[email protected]>".parse().unwrap(),
];

Expand All @@ -210,7 +210,7 @@ mod test {

assert_eq!(
headers.to_string(),
"From: K. <[email protected]>, Pony P. <[email protected]>\r\n"
"From: Kayo <[email protected]>, \"Pony P.\" <[email protected]>\r\n"
);
}

Expand Down
115 changes: 112 additions & 3 deletions src/message/mailbox/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl Display for Mailbox {
if let Some(ref name) = self.name {
let name = name.trim();
if !name.is_empty() {
f.write_str(name)?;
write_word(f, name)?;
f.write_str(" <")?;
self.email.fmt(f)?;
return f.write_char('>');
Expand Down Expand Up @@ -327,6 +327,87 @@ impl FromStr for Mailboxes {
}
}

// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.6
fn write_word(f: &mut Formatter<'_>, s: &str) -> FmtResult {
if s.as_bytes().iter().copied().all(is_valid_atom_char) {
f.write_str(s)
} else {
// Quoted string: https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.5
f.write_char('"')?;
for &c in s.as_bytes() {
write_quoted_string_char(f, c)?;
}
f.write_char('"')?;

Ok(())
}
}

// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.4
fn is_valid_atom_char(c: u8) -> bool {
matches!(c,
// Not really allowed but can be inserted between atoms.
b'\t' |
b' ' |

b'!' |
b'#' |
b'$' |
b'%' |
b'&' |
b'\'' |
b'*' |
b'+' |
b'-' |
b'/' |
b'0'..=b'8' |
b'=' |
b'?' |
b'A'..=b'Z' |
b'^' |
b'_' |
b'`' |
b'a'..=b'z' |
b'{' |
b'|' |
b'}' |
b'~' |

// Not techically allowed but will be escaped into allowed characters.
128..=255)
}

// https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.5
fn write_quoted_string_char(f: &mut Formatter<'_>, c: u8) -> FmtResult {
match c {
// NO-WS-CTL: https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.1
1..=8 | 11 | 12 | 14..=31 | 127 |

// Note, not qcontent but can be put before or after any qcontent.
b'\t' |
b' ' |

// The rest of the US-ASCII except \ and "
33 |
35..=91 |
93..=126 |

// Non-ascii characters will be escaped separately later.
128..=255

=> f.write_char(c.into()),

// Can not be encoded.
b'\n' | b'\r' => Err(std::fmt::Error),

c => {
// quoted-pair https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.2
f.write_char('\\')?;
f.write_char(c.into())
}
}
}

#[cfg(test)]
mod test {
use super::Mailbox;
Expand All @@ -350,7 +431,35 @@ mod test {
"{}",
Mailbox::new(Some("K.".into()), "[email protected]".parse().unwrap())
),
"K. <[email protected]>"
"\"K.\" <[email protected]>"
);
}

#[test]
fn mailbox_format_address_with_comma() {
assert_eq!(
format!(
"{}",
Mailbox::new(
Some("Last, First".into()),
"[email protected]".parse().unwrap()
)
),
r#""Last, First" <[email protected]>"#
);
}

#[test]
fn mailbox_format_address_with_color() {
assert_eq!(
format!(
"{}",
Mailbox::new(
Some("Chris's Wiki :: blog".into()),
"[email protected]".parse().unwrap()
)
),
r#""Chris's Wiki :: blog" <[email protected]>"#
);
}

Expand All @@ -372,7 +481,7 @@ mod test {
"{}",
Mailbox::new(Some(" K. ".into()), "[email protected]".parse().unwrap())
),
"K. <[email protected]>"
"\"K.\" <[email protected]>"
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ mod test {
concat!(
"Date: Tue, 15 Nov 1994 08:12:31 -0000\r\n",
"From: =?utf-8?b?0JrQsNC4?= <[email protected]>\r\n",
"To: Pony O.P. <[email protected]>\r\n",
"To: \"Pony O.P.\" <[email protected]>\r\n",
"Subject: =?utf-8?b?0Y/So9CwINC10Lsg0LHQtdC705nQvSE=?=\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
"\r\n",
Expand Down

0 comments on commit 5e3ebbb

Please sign in to comment.