{"record":{"id":"c6847fb4825c218f","repo":"rustfs/rustfs","slug":"failed-to-encode-query-parameters-reason","errorCode":null,"errorMessage":"failed to encode query parameters: {reason}","messagePattern":"failed to encode query parameters: (.+?)","errorType":"exception","errorClass":"SignV2Error","httpStatus":null,"severity":"warning","filePath":"crates/signer/src/request_signature_v2.rs","lineNumber":48,"sourceCode":"// SHA-1 is considered weak, but it's only used for HMAC (not signature collision).\n// Migration plan (not yet implemented):\n// Phase 1: Support both SHA-1 and SHA-256 (configurable)\n// Phase 2: Deprecation warnings in response headers\n// Phase 3: Default to SHA-256, SHA-1 becomes optional\n// See https://github.com/rustfs/backlog/issues/747 for discussion.\n\nconst _SIGN_V4_ALGORITHM: &str = \"AWS4-HMAC-SHA256\";\nconst SIGN_V2_ALGORITHM: &str = \"AWS\";\n\n#[derive(Debug, thiserror::Error)]\npub enum SignV2Error {\n    #[error(\"invalid UTF-8 header value for `{name}`\")]\n    InvalidHeaderValue { name: String },\n    #[error(\"failed to format signing timestamp: {reason}\")]\n    TimeFormat { reason: String },\n    #[error(\"failed to build signing timestamp: {reason}\")]\n    TimeComponent { reason: String },\n    #[error(\"failed to encode query parameters: {reason}\")]\n    QueryEncode { reason: String },\n    #[error(\"failed to parse uri: {reason}\")]\n    InvalidUri { reason: String },\n    #[error(\"failed to build uri from parts: {reason}\")]\n    InvalidUriParts { reason: String },\n    #[error(\"failed to convert canonical headers to UTF-8: {reason}\")]\n    CanonicalUtf8 { reason: String },\n    #[error(\"failed to parse header value for `{name}`: {reason}\")]\n    HeaderValueParse { name: String, reason: String },\n    #[error(\"failed to resolve host address: {0}\")]\n    HostAddr(#[from] HostAddrError),\n}\n\n#[derive(Debug)]\nstruct SignV2Failure {\n    request: request::Request<Body>,\n    error: SignV2Error,\n}","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/rustfs/rustfs/blob/35af688cd9d41b4346fbe27dcf7250ba72046c1f/crates/signer/src/request_signature_v2.rs#L30-L66","documentation":"SignV2Error::QueryEncode is returned by pre_sign_v2_inner at request_signature_v2.rs:133-136 when serde_urlencoded::to_string fails to serialize the query map (original query params plus AWSAccessKeyId/Expires, or GoogleAccessId for .storage.googleapis.com hosts). For a HashMap<String,String> the form-serializer percent-encodes every string, so a failure has no realistic input trigger. The variant is defensive plumbing that turns a serializer error into a typed failure.","triggerScenarios":"Calling try_pre_sign_v2/pre_sign_v2; the serializer would have to reject a String key or value, which form_urlencoded does not do for any &str. Only a modified signer or corrupted in-memory map (non-UTF-8 String contents constructed via unsafe) could reach it.","commonSituations":"Almost always seen while matching exhaustively on SignV2Error, not at runtime. If it does fire, suspect a stale or locally patched rustfs-signer version rather than your query string.","solutions":["Verify the rustfs-signer version in the build matches the source you are reading (cargo tree -p rustfs-signer).","Sanitize the incoming query string before presigning: drop pairs that fail UTF-8 or contain control characters.","Switch to try_pre_sign_v2 so the typed reason string from serde_urlencoded is surfaced instead of warn! (line 170) plus an unsigned request."],"exampleFix":"// before: non-UTF-8 bytes carried in from a raw query string\nlet query: HashMap<String, String> = serde_urlencoded::from_str(query_source).unwrap_or_default();\n\n// after: filter undecodable pairs before they reach the serializer\nlet query: HashMap<String, String> = serde_urlencoded::from_str(query_source)\n    .unwrap_or_default()\n    .into_iter()\n    .filter(|(k, v)| std::str::from_utf8(k.as_bytes()).is_ok() && std::str::from_utf8(v.as_bytes()).is_ok())\n    .collect();","handlingStrategy":"try-catch","validationCode":"let ok = serde_urlencoded::from_str::<HashMap<String, String>>(req.uri().query().unwrap_or(\"\"))\n    .map(|m| m.keys().all(|k| k.is_ascii()) && m.values().all(|v| v.is_ascii()))\n    .unwrap_or(true);","typeGuard":"fn is_query_encode(e: &SignV2Error) -> bool {\n    matches!(e, SignV2Error::QueryEncode { .. })\n}","tryCatchPattern":"match try_pre_sign_v2(req, ak, sk, 60, vhost) {\n    Ok(signed) => signed,\n    Err(SignV2Error::QueryEncode { reason }) => {\n        // serializer failure on String pairs is defensive: report, do not retry with edited params\n        return Err(anyhow!(\"query serialization failed: {reason}\"));\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Keep query keys/values as plain UTF-8 Strings end to end.","Prefer try_pre_sign_v2 so the serde reason string reaches your logs instead of a warn-only unsigned URL."],"tags":["rust","rustfs","s3","signing","sigv2","presign","query-string"],"backgroundTag":"query-string-encoding-failed","analyzedSha":"35af688cd9d41b4346fbe27dcf7250ba72046c1f","analyzedAt":"2026-08-20T21:57:04.799Z","contentChangedAt":"2026-08-20T21:57:04.799Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}