From 1b3d8f33b7b39d22e51fcb4aa22a2f5aae42a526 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Thu, 2 Nov 2023 11:05:29 +0100 Subject: [PATCH 1/3] Metadata: escape display name in email addresses --- src/metadata.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/metadata.rs b/src/metadata.rs index f8fa70d59..d3cdaa854 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -226,7 +226,7 @@ impl Metadata21 { for author in authors { match (&author.name, &author.email) { (Some(name), Some(email)) => { - emails.push(format!("{name} <{email}>")); + emails.push(format_email_with_display_name(name, email)); } (Some(name), None) => { names.push(name.as_str()); @@ -251,7 +251,7 @@ impl Metadata21 { for maintainer in maintainers { match (&maintainer.name, &maintainer.email) { (Some(name), Some(email)) => { - emails.push(format!("{name} <{email}>")); + emails.push(format_email_with_display_name(name, email)); } (Some(name), None) => { names.push(name.as_str()); @@ -555,6 +555,23 @@ impl Metadata21 { } } +/// Escape email addresses with display name if necessary +/// according to RFC 822 Section 3.3. "specials". +fn format_email_with_display_name(display_name: &str, email: &str) -> String { + if display_name.chars().any(|c| { + matches!( + c, + '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '\\' | '"' | '.' | '[' | ']' + ) + }) { + return format!( + "\"{}\" <{email}>", + display_name.replace('\\', "\\\\").replace('\"', "\\\"") + ); + } + format!("{display_name} <{email}>") +} + /// Fold long header field according to RFC 5322 section 2.2.3 /// https://datatracker.ietf.org/doc/html/rfc5322#section-2.2.3 fn fold_header(text: &str) -> String { From 2e09bc1b2c19ae52889652318de58337e991de91 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Thu, 2 Nov 2023 12:05:36 +0100 Subject: [PATCH 2/3] Rename function Co-authored-by: messense --- src/metadata.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metadata.rs b/src/metadata.rs index d3cdaa854..ef01f3fc1 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -226,7 +226,7 @@ impl Metadata21 { for author in authors { match (&author.name, &author.email) { (Some(name), Some(email)) => { - emails.push(format_email_with_display_name(name, email)); + emails.push(escape_email_with_display_name(name, email)); } (Some(name), None) => { names.push(name.as_str()); @@ -251,7 +251,7 @@ impl Metadata21 { for maintainer in maintainers { match (&maintainer.name, &maintainer.email) { (Some(name), Some(email)) => { - emails.push(format_email_with_display_name(name, email)); + emails.push(escape_email_with_display_name(name, email)); } (Some(name), None) => { names.push(name.as_str()); @@ -557,7 +557,7 @@ impl Metadata21 { /// Escape email addresses with display name if necessary /// according to RFC 822 Section 3.3. "specials". -fn format_email_with_display_name(display_name: &str, email: &str) -> String { +fn escape_email_with_display_name(display_name: &str, email: &str) -> String { if display_name.chars().any(|c| { matches!( c, From 693f81fb222da68671f71e16385f72831c4a88b3 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Thu, 2 Nov 2023 13:37:11 +0100 Subject: [PATCH 3/3] Add unit tests for escape_email_with_display_name --- src/metadata.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/metadata.rs b/src/metadata.rs index ef01f3fc1..c83d46c3d 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -828,4 +828,43 @@ mod test { assert_eq!(metadata.license_files[2], manifest_dir.join("NOTICE.md")); assert_eq!(metadata.license_files[3], manifest_dir.join("AUTHORS.txt")); } + + #[test] + fn test_escape_email_with_display_name_without_special_characters() { + let display_name = "Foo Bar !#$%&'*+-/=?^_`{|}~ 123"; + let email = "foobar-123@example.com"; + let result = escape_email_with_display_name(display_name, email); + assert_eq!( + result, + "Foo Bar !#$%&'*+-/=?^_`{|}~ 123 " + ); + } + + #[test] + fn test_escape_email_with_display_name_with_special_characters() { + let tests = [ + ("Foo ( Bar", "\"Foo ( Bar\""), + ("Foo ) Bar", "\"Foo ) Bar\""), + ("Foo < Bar", "\"Foo < Bar\""), + ("Foo > Bar", "\"Foo > Bar\""), + ("Foo @ Bar", "\"Foo @ Bar\""), + ("Foo , Bar", "\"Foo , Bar\""), + ("Foo ; Bar", "\"Foo ; Bar\""), + ("Foo : Bar", "\"Foo : Bar\""), + ("Foo \\ Bar", "\"Foo \\\\ Bar\""), + ("Foo \" Bar", "\"Foo \\\" Bar\""), + ("Foo . Bar", "\"Foo . Bar\""), + ("Foo [ Bar", "\"Foo [ Bar\""), + ("Foo ] Bar", "\"Foo ] Bar\""), + ("Foo ) Bar", "\"Foo ) Bar\""), + ("Foo ) Bar", "\"Foo ) Bar\""), + ("Foo, Bar", "\"Foo, Bar\""), + ]; + for (display_name, expected_name) in tests { + let email = "foobar-123@example.com"; + let result = escape_email_with_display_name(display_name, email); + let expected = format!("{expected_name} <{email}>"); + assert_eq!(result, expected); + } + } }