From 1748cb78b84e74aa40efa1ad0d8e747b54abd20a Mon Sep 17 00:00:00 2001 From: rootkiller6788 <221446036+rootkiller6788@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:28:30 +0800 Subject: [PATCH 1/3] fix(gmail): match message header names case-insensitively parse_message_headers matched on the raw header name, so a message from an Exchange/Outlook sender (which emits "CC" and "MESSAGE-ID") had its Cc header dropped. get_part_header in the same file already does a case-insensitive lookup, so this was inconsistent too. --- .../src/helpers/gmail/mod.rs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/google-workspace-cli/src/helpers/gmail/mod.rs b/crates/google-workspace-cli/src/helpers/gmail/mod.rs index caeb8b6b0..075a22436 100644 --- a/crates/google-workspace-cli/src/helpers/gmail/mod.rs +++ b/crates/google-workspace-cli/src/helpers/gmail/mod.rs @@ -258,15 +258,18 @@ fn parse_message_headers(headers: &[Value]) -> ParsedMessageHeaders { let name = header.get("name").and_then(|v| v.as_str()).unwrap_or(""); let value = header.get("value").and_then(|v| v.as_str()).unwrap_or(""); - match name { - "From" => parsed.from = value.to_string(), - "Reply-To" => append_address_list_header_value(&mut parsed.reply_to, value), - "To" => append_address_list_header_value(&mut parsed.to, value), - "Cc" => append_address_list_header_value(&mut parsed.cc, value), - "Subject" => parsed.subject = value.to_string(), - "Date" => parsed.date = value.to_string(), - "Message-ID" | "Message-Id" => parsed.message_id = value.to_string(), - "References" => append_header_value(&mut parsed.references, value), + // Header names are case-insensitive, and the API hands through whatever casing + // the sending MTA used. Exchange/Outlook emit "CC"/"MESSAGE-ID", which used to + // fall through the match below and get dropped. + match name.to_ascii_lowercase().as_str() { + "from" => parsed.from = value.to_string(), + "reply-to" => append_address_list_header_value(&mut parsed.reply_to, value), + "to" => append_address_list_header_value(&mut parsed.to, value), + "cc" => append_address_list_header_value(&mut parsed.cc, value), + "subject" => parsed.subject = value.to_string(), + "date" => parsed.date = value.to_string(), + "message-id" => parsed.message_id = value.to_string(), + "references" => append_header_value(&mut parsed.references, value), _ => {} } } From 29f169a6981d5cb57bd63bfcd7fa48554bbf76b4 Mon Sep 17 00:00:00 2001 From: rootkiller6788 <221446036+rootkiller6788@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:28:34 +0800 Subject: [PATCH 2/3] test(gmail): cover non-canonical header casing in parse_original_message Fails on the previous commit with "Message is missing From header". --- .../src/helpers/gmail/mod.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/google-workspace-cli/src/helpers/gmail/mod.rs b/crates/google-workspace-cli/src/helpers/gmail/mod.rs index 075a22436..c001dd029 100644 --- a/crates/google-workspace-cli/src/helpers/gmail/mod.rs +++ b/crates/google-workspace-cli/src/helpers/gmail/mod.rs @@ -2430,6 +2430,50 @@ mod tests { assert_eq!(original.body_html.as_deref(), Some("

HTML only

")); } + #[test] + fn test_parse_original_message_accepts_non_canonical_header_casing() { + // Exchange/Outlook style casing: the old case-sensitive match dropped CC and + // blew up on the missing From header. + let msg = json!({ + "threadId": "thread-789", + "snippet": "", + "payload": { + "mimeType": "text/plain", + "headers": [ + { "name": "FROM", "value": "alice@example.com" }, + { "name": "TO", "value": "bob@example.com" }, + { "name": "cc", "value": "carol@example.com" }, + { "name": "CC", "value": "dave@example.com" }, + { "name": "Subject", "value": "Hello" }, + { "name": "DATE", "value": "Fri, 6 Mar 2026 12:00:00 +0000" }, + { "name": "message-id", "value": "" }, + { "name": "REPLY-TO", "value": "team@example.com" }, + { "name": "references", "value": "" } + ], + "body": { + "data": URL_SAFE.encode("hi") + } + } + }); + + let original = parse_original_message(&msg).unwrap(); + + assert_eq!(original.from.email, "alice@example.com"); + assert_eq!(original.to.len(), 1); + assert_eq!(original.to[0].email, "bob@example.com"); + let cc = original.cc.unwrap(); + assert_eq!(cc.len(), 2); + assert_eq!(cc[0].email, "carol@example.com"); + assert_eq!(cc[1].email, "dave@example.com"); + assert_eq!(original.reply_to.unwrap()[0].email, "team@example.com"); + assert_eq!(original.message_id, "msg@example.com"); + assert_eq!(original.references, vec!["ref-1@example.com"]); + assert_eq!( + original.date.as_deref(), + Some("Fri, 6 Mar 2026 12:00:00 +0000") + ); + } + #[test] fn test_parse_original_message_multipart_alternative() { let msg = json!({ From 0a916d83351290f499f293728aa103fbdb1458d3 Mon Sep 17 00:00:00 2001 From: rootkiller6788 <221446036+rootkiller6788@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:28:42 +0800 Subject: [PATCH 3/3] chore: changeset for the gmail header casing fix --- .changeset/gmail-header-case.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gmail-header-case.md diff --git a/.changeset/gmail-header-case.md b/.changeset/gmail-header-case.md new file mode 100644 index 000000000..e078d42b2 --- /dev/null +++ b/.changeset/gmail-header-case.md @@ -0,0 +1,5 @@ +--- +"@googleworkspace/cli": patch +--- + +Match Gmail message header names case-insensitively, so `CC` from Exchange/Outlook senders is no longer dropped by `gmail +reply-all` and `gmail +read`