Skip to content

Commit 2aa19b9

Browse files
fix: quote display names with RFC 2822 special characters in +reply
When replying to emails from corporate senders with display names like "Anderson, Rich (CORP)" <email@adp.com>, the +reply command fails with "Invalid To header" (400) from the Gmail API. The root cause: encode_address_header() strips quotes from the display name via extract_display_name(), then reconstructs the address without re-quoting. When the display name contains RFC 2822 special characters (commas, parentheses), the unquoted form is ambiguous — commas split it into multiple malformed mailboxes and parentheses are interpreted as RFC 2822 comments. Fix: re-quote the display name when it contains any RFC 2822 special characters, using a single-pass character iterator that preserves already-escaped sequences and escapes bare quotes/backslashes. Fixes #512
1 parent 1308786 commit 2aa19b9

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@googleworkspace/cli": patch
3+
---
4+
5+
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()`.

src/helpers/gmail/mod.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,32 @@ pub(super) fn encode_address_header(value: &str) -> String {
491491

492492
// ASCII display name — reconstruct from parsed components
493493
// to strip any potential residual injection data.
494-
format!("{} <{}>", display, email)
494+
// Re-quote if the display name contains RFC 2822 special characters
495+
// (commas, parens, etc.) to prevent header parsing issues.
496+
if display.contains(|c: char| ",;()<>@\\\".[]".contains(c)) {
497+
// Build a properly escaped quoted-string in one pass.
498+
// extract_display_name strips outer quotes but leaves inner
499+
// escapes (e.g. \"), so we skip already-escaped chars and
500+
// escape any bare quotes or backslashes.
501+
let mut escaped = String::with_capacity(display.len());
502+
let mut chars = display.chars().peekable();
503+
while let Some(ch) = chars.next() {
504+
if ch == '\\' && chars.peek().map_or(false, |&c| c == '"' || c == '\\') {
505+
// Already escaped — pass through as-is
506+
escaped.push(ch);
507+
escaped.push(chars.next().unwrap());
508+
} else if ch == '"' || ch == '\\' {
509+
// Bare special char — escape it
510+
escaped.push('\\');
511+
escaped.push(ch);
512+
} else {
513+
escaped.push(ch);
514+
}
515+
}
516+
format!("\"{}\" <{}>", escaped, email)
517+
} else {
518+
format!("{} <{}>", display, email)
519+
}
495520
})
496521
.collect();
497522

@@ -1489,6 +1514,41 @@ mod tests {
14891514
assert_eq!(encode_address_header(""), "");
14901515
}
14911516

1517+
#[test]
1518+
fn test_encode_address_header_display_name_with_comma() {
1519+
// Corporate email: "Last, First (DEPT)" <email@corp.com>
1520+
let input = "\"Anderson, Rich (CORP)\" <richard.anderson@adp.com>";
1521+
let result = encode_address_header(input);
1522+
assert_eq!(
1523+
result,
1524+
"\"Anderson, Rich (CORP)\" <richard.anderson@adp.com>",
1525+
"Display name with comma and parens must be quoted: {result}"
1526+
);
1527+
}
1528+
1529+
#[test]
1530+
fn test_encode_address_header_display_name_with_parens() {
1531+
let input = "Rich (CORP) <rich@example.com>";
1532+
let result = encode_address_header(input);
1533+
assert_eq!(
1534+
result,
1535+
"\"Rich (CORP)\" <rich@example.com>",
1536+
"Display name with parentheses must be quoted: {result}"
1537+
);
1538+
}
1539+
1540+
#[test]
1541+
fn test_encode_address_header_display_name_with_escaped_quotes() {
1542+
// Display name with already-escaped quotes must not double-escape
1543+
let input = "\"Rich \\\"The Man\\\" Anderson\" <rich@example.com>";
1544+
let result = encode_address_header(input);
1545+
assert_eq!(
1546+
result,
1547+
"\"Rich \\\"The Man\\\" Anderson\" <rich@example.com>",
1548+
"Already-escaped quotes must not be double-escaped: {result}"
1549+
);
1550+
}
1551+
14921552
#[test]
14931553
fn test_message_builder_basic() {
14941554
let raw = MessageBuilder {

0 commit comments

Comments
 (0)