From: Austin Moore Date: Wed, 19 Mar 2025 03:58:56 +0000 (-0400) Subject: aws_sigv4: merge repeated headers in canonical request X-Git-Tag: curl-8_13_0~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3978bd449858b75db263aba8756d704eb98297cc;p=thirdparty%2Fcurl.git aws_sigv4: merge repeated headers in canonical request When multiple headers share the same name, AWS SigV4 expects them to be merged into a single header line, with values comma-delimited in the order they appeared. Add libtest 1978 to verify. Closes #16743 --- diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 4e262bfd6f..854cc8f4ef 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -154,6 +154,57 @@ static int compare_header_names(const char *a, const char *b) return cmp; } +/* Merge duplicate header definitions by comma delimiting their values + in the order defined the headers are defined, expecting headers to + be alpha-sorted and use ':' at this point */ +static CURLcode merge_duplicate_headers(struct curl_slist *head) +{ + struct curl_slist *curr = head; + CURLcode result = CURLE_OK; + + while(curr) { + struct curl_slist *next = curr->next; + if(!next) + break; + + if(compare_header_names(curr->data, next->data) == 0) { + struct dynbuf buf; + char *colon_next; + char *val_next; + + Curl_dyn_init(&buf, CURL_MAX_HTTP_HEADER); + + result = Curl_dyn_add(&buf, curr->data); + if(result) + return result; + + colon_next = strchr(next->data, ':'); + DEBUGASSERT(colon_next); + val_next = colon_next + 1; + + result = Curl_dyn_addn(&buf, ",", 1); + if(result) + return result; + + result = Curl_dyn_add(&buf, val_next); + if(result) + return result; + + free(curr->data); + curr->data = Curl_dyn_ptr(&buf); + + curr->next = next->next; + free(next->data); + free(next); + } + else { + curr = curr->next; + } + } + + return CURLE_OK; +} + /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ static CURLcode make_headers(struct Curl_easy *data, const char *hostname, @@ -299,6 +350,10 @@ static CURLcode make_headers(struct Curl_easy *data, } } while(again); + ret = merge_duplicate_headers(head); + if(ret) + goto fail; + for(l = head; l; l = l->next) { char *tmp; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 213f53db32..f17ad4be33 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -239,6 +239,7 @@ test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \ test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ test1955 test1956 test1957 test1958 test1959 test1960 test1964 \ test1970 test1971 test1972 test1973 test1974 test1975 test1976 test1977 \ +test1978 \ \ test2000 test2001 test2002 test2003 test2004 test2005 \ \ diff --git a/tests/data/test1978 b/tests/data/test1978 new file mode 100644 index 0000000000..fb17ac13e8 --- /dev/null +++ b/tests/data/test1978 @@ -0,0 +1,73 @@ + + + +HTTP +CURLOPT_AWS_SIGV4 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +SSL +Debug +crypto + + +HTTP AWS_SIGV4 canonical request duplicate header test + + +lib%TESTNUMBER + + +http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +^User-Agent:.* +^Content-Length:.* +^Accept:.* + + +PUT /%TESTNUMBER/testapi/test HTTP/1.1 +Host: 127.0.0.1:9000 +Authorization: AWS4-HMAC-SHA256 Credential=xxx/19700101/us-east-1/s3/aws4_request, SignedHeaders=curr-header-no-colon;duplicate-header;header-no-value;header-some-no-value;host;next-header-no-colon;some-other-header;x-amz-content-sha256;x-amz-date;x-amz-meta-blah;x-amz-meta-test;x-amz-meta-test2, Signature=b1e2ac88fd1307f0b9031140c3c99cc20d6263f3fe64b132dc433c95fe2c6316 +X-Amz-Date: 19700101T000000Z +x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +x-amz-meta-test: test2 +some-other-header: value +x-amz-meta-test: test1 +duplicate-header: duplicate +x-amz-meta-test: test3 +X-amz-meta-test2: test2 +x-amz-meta-blah: blah +x-Amz-meta-test2: test1 +x-amz-Meta-test2: test3 +curr-header-no-colon: value +next-header-no-colon: value +duplicate-header: duplicate +header-no-value: +header-no-value: +header-some-no-value: +header-some-no-value: value + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 3e64075c26..9a866306fa 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -72,7 +72,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq \ lib1933 lib1934 lib1935 lib1936 lib1937 lib1938 lib1939 lib1940 \ lib1945 lib1946 lib1947 lib1948 lib1955 lib1956 lib1957 lib1958 lib1959 \ lib1960 lib1964 \ - lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 \ + lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \ lib2301 lib2302 lib2304 lib2305 lib2306 lib2308 lib2309 lib2310 \ lib2311 \ lib2402 lib2404 lib2405 \ @@ -680,6 +680,9 @@ lib1975_LDADD = $(TESTUTIL_LIBS) lib1977_SOURCES = lib1977.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1977_LDADD = $(TESTUTIL_LIBS) +lib1978_SOURCES = lib1978.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib1978_LDADD = $(TESTUTIL_LIBS) + lib2301_SOURCES = lib2301.c $(SUPPORTFILES) lib2301_LDADD = $(TESTUTIL_LIBS) diff --git a/tests/libtest/lib1978.c b/tests/libtest/lib1978.c new file mode 100644 index 0000000000..07554547f4 --- /dev/null +++ b/tests/libtest/lib1978.c @@ -0,0 +1,105 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "test.h" + +#include "memdebug.h" + +CURLcode test(char *URL) +{ + CURL *curl; + CURLcode res = TEST_ERR_MAJOR_BAD; + struct curl_slist *connect_to = NULL; + struct curl_slist *list = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + fprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + fprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + test_setopt(curl, CURLOPT_UPLOAD, 1L); + test_setopt(curl, CURLOPT_INFILESIZE, 0L); + test_setopt(curl, CURLOPT_VERBOSE, 1L); + test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + test_setopt(curl, CURLOPT_USERPWD, "xxx"); + test_setopt(curl, CURLOPT_HEADER, 0L); + test_setopt(curl, CURLOPT_URL, URL); + + /* We want to test a couple assumptions here. + 1. the merging works with non-adjacent headers + 2. the merging works across multiple duplicate headers + 3. the merging works if a duplicate header has no colon + 4. the merging works if the headers are cased differently + 5. the merging works across multiple duplicate headers + 6. the merging works across multiple duplicate headers with the + same value + 7. merging works for headers all with no values + 8. merging works for headers some with no values + */ + + list = curl_slist_append(list, "x-amz-meta-test: test2"); + if(!list) + goto test_cleanup; + curl_slist_append(list, "some-other-header: value"); + curl_slist_append(list, "x-amz-meta-test: test1"); + curl_slist_append(list, "duplicate-header: duplicate"); + curl_slist_append(list, "header-no-value"); + curl_slist_append(list, "x-amz-meta-test: test3"); + curl_slist_append(list, "X-amz-meta-test2: test2"); + curl_slist_append(list, "x-amz-meta-blah: blah"); + curl_slist_append(list, "x-Amz-meta-test2: test1"); + curl_slist_append(list, "x-amz-Meta-test2: test3"); + curl_slist_append(list, "curr-header-no-colon"); + curl_slist_append(list, "curr-header-no-colon: value"); + curl_slist_append(list, "next-header-no-colon: value"); + curl_slist_append(list, "next-header-no-colon"); + curl_slist_append(list, "duplicate-header: duplicate"); + curl_slist_append(list, "header-no-value;"); + curl_slist_append(list, "header-no-value;"); + curl_slist_append(list, "header-some-no-value;"); + curl_slist_append(list, "header-some-no-value: value"); + + test_setopt(curl, CURLOPT_HTTPHEADER, list); + if(libtest_arg2) { + connect_to = curl_slist_append(connect_to, libtest_arg2); + } + test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + + res = curl_easy_perform(curl); + +test_cleanup: + + curl_slist_free_all(connect_to); + curl_slist_free_all(list); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return res; +}