Skip to content

Commit 19e7a8b

Browse files
authored
fix: canonicalize multiple whitespaces in SigV4 (#512)
* fix: canonicalize multiple whitespaces Signed-off-by: Ion Koutsouris <15728914+ion-elgreco@users.noreply.github.com> * chore: clippy Signed-off-by: Ion Koutsouris <15728914+ion-elgreco@users.noreply.github.com> --------- Signed-off-by: Ion Koutsouris <15728914+ion-elgreco@users.noreply.github.com>
1 parent ad1d70f commit 19e7a8b

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

src/aws/credential.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,18 @@ fn canonicalize_query(url: &Url) -> String {
414414
encoded
415415
}
416416

417+
fn append_normalized_whitespace_value(headers: &'_ mut String, input: &str) {
418+
let mut iter = input.split_whitespace();
419+
420+
if let Some(first) = iter.next() {
421+
headers.push_str(first);
422+
for word in iter {
423+
headers.push(' ');
424+
headers.push_str(word);
425+
}
426+
}
427+
}
428+
417429
/// Canonicalizes headers into the AWS Canonical Form.
418430
///
419431
/// <https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html>
@@ -452,7 +464,7 @@ fn canonicalize_headers(header_map: &HeaderMap) -> (String, String) {
452464
if value_idx != 0 {
453465
canonical_headers.push(',');
454466
}
455-
canonical_headers.push_str(value.trim());
467+
append_normalized_whitespace_value(&mut canonical_headers, value.trim());
456468
}
457469
canonical_headers.push('\n');
458470
}
@@ -1293,4 +1305,60 @@ mod tests {
12931305
assert!(!debug_output.contains("super_secret"));
12941306
assert!(!debug_output.contains("temp_token"));
12951307
}
1308+
1309+
#[test]
1310+
fn test_normalize_whitespace() {
1311+
// test cases from minio: https://github.com/minio/minio/blob/05e569960ac584c8927a9af76755708d20f16129/cmd/signature-v4-utils_test.go#L307-L324
1312+
let test_cases = vec![
1313+
// input, expected
1314+
("本語", "本語"),
1315+
(" abc ", "abc"),
1316+
(" a b ", "a b"),
1317+
("a b ", "a b"),
1318+
("a b", "a b"),
1319+
("a b", "a b"),
1320+
(" a b c ", "a b c"),
1321+
("a \t b c ", "a b c"),
1322+
("\"a \t b c ", "\"a b c"),
1323+
(" \t\n\u{000b}\r\u{000c}a \t\n\u{000b}\r\u{000c} b \t\n\u{000b}\r\u{000c} c \t\n\u{000b}\r\u{000c}", "a b c"),
1324+
];
1325+
1326+
for (input, expected) in test_cases {
1327+
let mut headers = String::new();
1328+
1329+
append_normalized_whitespace_value(&mut headers, input);
1330+
assert_eq!(headers, expected);
1331+
}
1332+
}
1333+
1334+
#[test]
1335+
fn test_canonicalize_headers_whitespace_normalization() {
1336+
use http::header::HeaderMap;
1337+
1338+
let mut headers = HeaderMap::new();
1339+
headers.insert("x-amz-meta-example", " foo bar ".parse().unwrap());
1340+
headers.insert(
1341+
"x-amz-meta-another",
1342+
" multiple spaces here ".parse().unwrap(),
1343+
);
1344+
headers.insert(
1345+
"x-amz-meta-and-another-one",
1346+
"foo\t\t\t bar".parse().unwrap(),
1347+
);
1348+
// ignored headers
1349+
headers.insert("authorization", "SHOULD_BE_IGNORED".parse().unwrap());
1350+
headers.insert("content-length", "1337".parse().unwrap());
1351+
1352+
let (signed_headers, canonical_headers) = super::canonicalize_headers(&headers);
1353+
1354+
assert_eq!(
1355+
signed_headers,
1356+
"x-amz-meta-and-another-one;x-amz-meta-another;x-amz-meta-example"
1357+
);
1358+
1359+
let expected_canonical_headers = "x-amz-meta-and-another-one:foo bar\n\
1360+
x-amz-meta-another:multiple spaces here\n\
1361+
x-amz-meta-example:foo bar\n";
1362+
assert_eq!(canonical_headers, expected_canonical_headers);
1363+
}
12961364
}

src/integration.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,10 @@ pub async fn put_get_attributes(integration: &dyn ObjectStore) {
483483
(Attribute::ContentLanguage, "en-US"),
484484
(Attribute::ContentType, "text/html; charset=utf-8"),
485485
(Attribute::Metadata("test_key".into()), "test_value"),
486+
(
487+
Attribute::Metadata("key_with_spaces".into()),
488+
"hello world",
489+
),
486490
]);
487491

488492
let path = Path::from("attributes");

0 commit comments

Comments
 (0)