Skip to content

bug(gmail): parse_message_headers uses case-sensitive match, drops CC/headers with non-canonical casing #642

@gura105

Description

@gura105

Description

gws gmail +reply-all silently drops CC recipients when the original message's Cc header is stored with non-canonical casing (e.g., "CC" instead of "Cc").

Reproduction

  1. Have a Gmail thread where the latest message has CC recipients and the header name is "CC" (all uppercase — common with Microsoft Exchange / Outlook)
  2. Run:
    gws gmail +reply-all --message-id <ID> --body 'test' --draft
  3. Expected: Draft includes all original To + CC recipients
  4. Actual: Draft only includes To recipients + original sender. All CC recipients are silently dropped.

Root Cause

parse_message_headers in crates/google-workspace-cli/src/helpers/gmail/mod.rs (line 254) uses exact case-sensitive string matching:

match name {
    "From" => ...
    "Reply-To" => ...
    "To" => ...
    "Cc" => ...                                          // ← only matches "Cc"
    "Message-ID" | "Message-Id" => ...                   // ← already handles 2 variants
    _ => {}                                              // ← "CC" falls through silently
}

The Gmail API preserves original header casing from the sending MTA. Per RFC 5322 §1.2.2, header field names are case-insensitive, so "CC", "Cc", and "cc" are all valid.

Scope

This affects all headers in the match block, not just Cc. If any header arrives in non-canonical casing:

Header Impact if missed
From Hard error — function returns Err("Message is missing From header")
Message-ID Hard error — Err("Message is missing Message-ID header")
To +reply-all misses To recipients
CC +reply-all drops CC recipients (this bug)
Reply-To Reply goes to wrong recipient
References Threading breaks

Internal Inconsistency

The same file already uses case-insensitive matching in get_part_header:

fn get_part_header<'a>(part: &'a Value, name: &str) -> Option<&'a str> {
    ...
    .find(|h| n.eq_ignore_ascii_case(name))    // ← correct approach
    ...
}

Suggested Fix

Normalize name before matching:

match name.to_ascii_lowercase().as_str() {
    "from" => ...
    "reply-to" => ...
    "to" => ...
    "cc" => ...
    "subject" => ...
    "date" => ...
    "message-id" => ...
    "references" => ...
    _ => {}
}

This aligns with the existing get_part_header pattern and the extract_header function (which also uses eq_ignore_ascii_case).

Related

Environment

  • gws 0.22.3
  • macOS (arm64)
  • Original message sent from Microsoft Exchange (which emits "CC" in uppercase)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions