googleworkspace/cli · error · GwsError
Failed to decode attachment data: {e}
Error message
Failed to decode attachment data: {e} What it means
`URL_SAFE.decode(data_str)` failed: the `data` field of the attachment response was not decodable as padded, URL-safe base64. Gmail historically returns base64url that may omit padding characters ('='), while the strict `URL_SAFE` engine in base64 0.22 requires canonical padding — unpadded input is the classic trigger. Corrupted or whitespace-laden payloads also fail here.
Source
Thrown at crates/google-workspace-cli/src/helpers/gmail/mod.rs:730
&err,
&format!("Failed to fetch attachment {attachment_id} from message {message_id}"),
));
}
let body: Value = resp
.json()
.await
.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse attachment JSON: {e}")))?;
let data_str = body.get("data").and_then(|v| v.as_str()).ok_or_else(|| {
GwsError::Other(anyhow::anyhow!(
"Attachment response missing 'data' field for {attachment_id}"
))
})?;
URL_SAFE
.decode(data_str)
.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to decode attachment data: {e}")))
}
/// Fetch binary data for selected original parts, converting them to `Attachment`s.
///
/// Performs a size preflight check using metadata before downloading, then fetches
/// parts sequentially. `existing_bytes` is the cumulative size of user-supplied
/// `--attach` files, counted against the combined size limit.
pub(super) async fn fetch_original_parts(
client: &reqwest::Client,
token: &str,
message_id: &str,
parts: &[OriginalPart],
existing_bytes: u64,
) -> Result<Vec<Attachment>, GwsError> {
// Size preflight: check metadata sizes before downloading anything
let total_metadata_size: u64 = parts.iter().map(|p| p.size).sum();
if existing_bytes + total_metadata_size > MAX_TOTAL_ATTACHMENT_BYTES {
return Err(GwsError::Validation(format!(View on GitHub (pinned to a3768d0e82)
Solutions
- If it fails only for some attachments, suspect padding: this is fixed by decoding with `base64::engine::general_purpose::URL_SAFE_NO_PAD` or by appending the needed '=' padding first (see example fix).
- Print the tail of `data_str` to check for missing '=' or '+/' characters.
- For stub servers, generate fixtures with the URL-safe alphabet.
- Update the CLI — engine selection lives in helpers/gmail/mod.rs and is patchable.
Example fix
// before: strict padded URL-safe decode fails on Gmail's unpadded base64url
URL_SAFE.decode(data_str).map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to decode attachment data: {e}")))
// after: accept both padded and unpadded base64url
fn decode_b64url_flexible(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
use base64::Engine;
let trimmed = s.trim_end_matches('=');
base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(trimmed)
}
decode_b64url_flexible(data_str).map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to decode attachment data: {e}"))) Defensive patterns
Strategy: validation
Validate before calling
// Normalize before decode: trim whitespace, re-pad if needed, reject other-alphabet chars
fn normalized_b64url(s: &str) -> Result<String, base64::DecodeError> {
let t = s.trim();
if t.chars().any(|c| c == '+' || c == '/' || c.is_whitespace()) {
return Err(base64::DecodeError::InvalidByte(0, b' '));
}
let pad = (4 - t.len() % 4) % 4;
Ok(format!("{t}{}", "=".repeat(pad)))
} Type guard
fn is_padded_b64url(s: &str) -> bool {
!s.is_empty() && s.len() % 4 == 0 && s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'=')
} Try / catch
// Decode leniently: try padded, fall back to NO_PAD after stripping '='
use base64::Engine;
let bytes = URL_SAFE
.decode(data_str)
.or_else(|_| base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(data_str.trim_end_matches('=')))
.map_err(|e| anyhow::anyhow!("attachment base64 undecodable: {e}"))?; Prevention
- Never assume Google base64url is padded — always decode with URL_SAFE_NO_PAD on the trimmed input or re-pad yourself.
- Generate test fixtures with the URL-safe alphabet (-, _), not standard base64 (+, /).
- If only some attachments fail, suspect a length-dependent padding issue, not corruption.
When it happens
Trigger: Gmail returning unpadded base64url (e.g. data length not a multiple of 4); JSON unescaping artifacts introducing stray characters; an interceptor corrupting the payload; using a stub that emits standard base64 ('+','/') instead of URL-safe ('-','_').
Common situations: Intermittent failures across many attachments — only those whose encoded length happens to need padding fail, making it look random; test fixtures generated with the wrong alphabet or without padding.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to fetch attachment: {e}
- Failed to parse attachment JSON: {e}
- Attachment response missing 'data' field for {attachment_id}
- Message is missing From header
- Message is missing Message-ID header
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/f0b7c3288cb52683.
Report an issue: GitHub.