-
Notifications
You must be signed in to change notification settings - Fork 983
fix: quote display names with RFC 2822 special characters in +reply #513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@googleworkspace/cli": patch | ||
| --- | ||
|
|
||
| Fix `+reply` failing with "Invalid To header" when the sender's display name contains commas or parentheses (e.g. `"Anderson, Rich (CORP)"`). Display names with RFC 2822 special characters are now re-quoted in `encode_address_header()`. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -491,7 +491,32 @@ pub(super) fn encode_address_header(value: &str) -> String { | |||||||||||||||||
|
|
||||||||||||||||||
| // ASCII display name — reconstruct from parsed components | ||||||||||||||||||
| // to strip any potential residual injection data. | ||||||||||||||||||
| format!("{} <{}>", display, email) | ||||||||||||||||||
| // Re-quote if the display name contains RFC 2822 special characters | ||||||||||||||||||
| // (commas, parens, etc.) to prevent header parsing issues. | ||||||||||||||||||
| if display.contains(|c: char| ",;()<>@\\\".[]".contains(c)) { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of RFC 2822 special characters used to determine if a display name needs re-quoting is incomplete. According to RFC 5322 (section 3.2.3), the characters This can result in malformed email headers and potential delivery issues or incorrect parsing by email clients.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of special characters used to determine if a display name needs quoting appears to be missing the colon (
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to RFC 2822, the list of While less common in display names than commas or parentheses, omitting it could lead to incorrectly formatted headers if a name like Please add the colon to the list of special characters to ensure full compliance and prevent potential parsing issues.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of special characters used to determine if a display name needs quoting is missing the colon (
Suggested change
|
||||||||||||||||||
| // Build a properly escaped quoted-string in one pass. | ||||||||||||||||||
| // extract_display_name strips outer quotes but leaves inner | ||||||||||||||||||
| // escapes (e.g. \"), so we skip already-escaped chars and | ||||||||||||||||||
| // escape any bare quotes or backslashes. | ||||||||||||||||||
| let mut escaped = String::with_capacity(display.len()); | ||||||||||||||||||
| let mut chars = display.chars().peekable(); | ||||||||||||||||||
| while let Some(ch) = chars.next() { | ||||||||||||||||||
| if ch == '\\' && chars.peek().map_or(false, |&c| c == '"' || c == '\\') { | ||||||||||||||||||
| // Already escaped — pass through as-is | ||||||||||||||||||
| escaped.push(ch); | ||||||||||||||||||
| escaped.push(chars.next().unwrap()); | ||||||||||||||||||
| } else if ch == '"' || ch == '\\' { | ||||||||||||||||||
| // Bare special char — escape it | ||||||||||||||||||
| escaped.push('\\'); | ||||||||||||||||||
| escaped.push(ch); | ||||||||||||||||||
| } else { | ||||||||||||||||||
| escaped.push(ch); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| format!("\"{}\" <{}>", escaped, email) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| format!("{} <{}>", display, email) | ||||||||||||||||||
| } | ||||||||||||||||||
| }) | ||||||||||||||||||
| .collect(); | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -1489,6 +1514,41 @@ mod tests { | |||||||||||||||||
| assert_eq!(encode_address_header(""), ""); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn test_encode_address_header_display_name_with_comma() { | ||||||||||||||||||
| // Corporate email: "Last, First (DEPT)" <email@corp.com> | ||||||||||||||||||
| let input = "\"Anderson, Rich (CORP)\" <richard.anderson@adp.com>"; | ||||||||||||||||||
| let result = encode_address_header(input); | ||||||||||||||||||
| assert_eq!( | ||||||||||||||||||
| result, | ||||||||||||||||||
| "\"Anderson, Rich (CORP)\" <richard.anderson@adp.com>", | ||||||||||||||||||
| "Display name with comma and parens must be quoted: {result}" | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn test_encode_address_header_display_name_with_parens() { | ||||||||||||||||||
| let input = "Rich (CORP) <rich@example.com>"; | ||||||||||||||||||
| let result = encode_address_header(input); | ||||||||||||||||||
| assert_eq!( | ||||||||||||||||||
| result, | ||||||||||||||||||
| "\"Rich (CORP)\" <rich@example.com>", | ||||||||||||||||||
| "Display name with parentheses must be quoted: {result}" | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn test_encode_address_header_display_name_with_escaped_quotes() { | ||||||||||||||||||
| // Display name with already-escaped quotes must not double-escape | ||||||||||||||||||
| let input = "\"Rich \\\"The Man\\\" Anderson\" <rich@example.com>"; | ||||||||||||||||||
| let result = encode_address_header(input); | ||||||||||||||||||
| assert_eq!( | ||||||||||||||||||
| result, | ||||||||||||||||||
| "\"Rich \\\"The Man\\\" Anderson\" <rich@example.com>", | ||||||||||||||||||
| "Already-escaped quotes must not be double-escaped: {result}" | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn test_message_builder_basic() { | ||||||||||||||||||
| let raw = MessageBuilder { | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list of special characters to check for is missing the colon (
:). According to RFC 5322, the colon is one of thespecialsthat requires the display name to be quoted. Without this, a display name likeGroup: Workwould not be quoted, potentially leading to it being misinterpreted as a group address syntax.