@@ -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}
0 commit comments