]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http_aws_sigv4: improve sigv4 url encoding and canonicalization
authorNigel Brittain <nbaws@amazon.com>
Sun, 27 Apr 2025 00:22:23 +0000 (00:22 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 21 May 2025 06:07:18 +0000 (08:07 +0200)
Closes #17129

docs/KNOWN_BUGS
lib/http_aws_sigv4.c
lib/http_aws_sigv4.h
tests/data/Makefile.am
tests/data/test1979 [new file with mode: 0644]
tests/data/test1980 [new file with mode: 0644]
tests/data/test439
tests/data/test472
tests/unit/Makefile.inc
tests/unit/unit1979.c [new file with mode: 0644]
tests/unit/unit1980.c [new file with mode: 0644]

index 191d47a979a98b0c75ce82f84b0ceff794c034ea..7ed5ee39dbef0cdf9b43c7a007fa183329c5b58b 100644 (file)
@@ -99,8 +99,6 @@ problems may have been fixed or changed somewhat since this was written.
 
  16. aws-sigv4
  16.2 aws-sigv4 does not handle multipart/form-data correctly
- 16.3 aws-sigv4 has problems with particular URLs
- 16.6 aws-sigv4 does not behave well with AWS VPC Lattice
 
  17. HTTP/2
  17.1 HTTP/2 prior knowledge over proxy
@@ -607,14 +605,6 @@ problems may have been fixed or changed somewhat since this was written.
 
  https://github.com/curl/curl/issues/13351
 
-16.3 aws-sigv4 has problems with particular URLs
-
- https://github.com/curl/curl/issues/13058
-
-16.6 aws-sigv4 does not behave well with AWS VPC Lattice
-
- https://github.com/curl/curl/issues/11007
-
 17. HTTP/2
 
 17.1 HTTP/2 prior knowledge over proxy
index 1b8354fdff91a04fbaf6942694bf867e5ac276e0..4d09013851b86d7a571b4cc3dda4334eb78be05e 100644 (file)
 /* hex-encoded with trailing null */
 #define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1)
 
+#define MAX_QUERY_COMPONENTS 128
+
+struct pair {
+  struct dynbuf key;
+  struct dynbuf value;
+};
+
+static void dyn_array_free(struct dynbuf *db, size_t num_elements);
+static void pair_array_free(struct pair *pair_array, size_t num_elements);
+static CURLcode split_to_dyn_array(const char *source, char split_by,
+    struct dynbuf db[MAX_QUERY_COMPONENTS], size_t *num_splits);
+static bool is_reserved_char(const char c);
+static CURLcode uri_encode_path(struct Curl_str *original_path,
+    struct dynbuf *new_path);
+static CURLcode encode_query_component(char *component, size_t len,
+  struct dynbuf *db);
+static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
+  struct dynbuf *out);
+static bool should_urlencode(struct Curl_str *service_name);
+
 static void sha256_to_hex(char *dst, unsigned char *sha)
 {
   Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH,
@@ -390,8 +410,7 @@ fail:
 static const char *parse_content_sha_hdr(struct Curl_easy *data,
                                          const char *provider1,
                                          size_t plen,
-                                         size_t *value_len)
-{
+                                         size_t *value_len) {
   char key[CONTENT_SHA256_KEY_LEN];
   size_t key_len;
   const char *value;
@@ -478,150 +497,187 @@ fail:
   return ret;
 }
 
-struct pair {
-  const char *p;
-  size_t len;
-};
-
 static int compare_func(const void *a, const void *b)
 {
+
   const struct pair *aa = a;
   const struct pair *bb = b;
+  const size_t aa_key_len = curlx_dyn_len(&aa->key);
+  const size_t bb_key_len = curlx_dyn_len(&bb->key);
+  const size_t aa_value_len = curlx_dyn_len(&aa->value);
+  const size_t bb_value_len = curlx_dyn_len(&bb->value);
+  int compare;
+
   /* If one element is empty, the other is always sorted higher */
-  if(aa->len == 0 && bb->len == 0)
+
+  /* Compare keys */
+  if((aa_key_len == 0) && (bb_key_len == 0))
     return 0;
-  if(aa->len == 0)
+  if(aa_key_len == 0)
     return -1;
-  if(bb->len == 0)
+  if(bb_key_len == 0)
     return 1;
-  return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len);
-}
+  compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key));
+  if(compare) {
+    return compare;
+  }
 
-#define MAX_QUERYPAIRS 64
+  /* Compare values */
+  if((aa_value_len == 0) && (bb_value_len == 0))
+    return 0;
+  if(aa_value_len == 0)
+    return -1;
+  if(bb_value_len == 0)
+    return 1;
+  compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value));
+
+  return compare;
 
-/**
- * found_equals have a double meaning,
- * detect if an equal have been found when called from canon_query,
- * and mark that this function is called to compute the path,
- * if found_equals is NULL.
- */
-static CURLcode canon_string(const char *q, size_t len,
-                             struct dynbuf *dq, bool *found_equals)
+}
+
+UNITTEST CURLcode canon_path(const char *q, size_t len,
+                              struct dynbuf *new_path,
+                              bool do_uri_encode)
 {
   CURLcode result = CURLE_OK;
 
-  for(; len && !result; q++, len--) {
-    if(ISALNUM(*q))
-      result = curlx_dyn_addn(dq, q, 1);
-    else {
-      switch(*q) {
-      case '-':
-      case '.':
-      case '_':
-      case '~':
-        /* allowed as-is */
-        result = curlx_dyn_addn(dq, q, 1);
-        break;
-      case '%':
-        /* uppercase the following if hexadecimal */
-        if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) {
-          char tmp[3]="%";
-          tmp[1] = Curl_raw_toupper(q[1]);
-          tmp[2] = Curl_raw_toupper(q[2]);
-          result = curlx_dyn_addn(dq, tmp, 3);
-          q += 2;
-          len -= 2;
-        }
-        else
-          /* '%' without a following two-digit hex, encode it */
-          result = curlx_dyn_addn(dq, "%25", 3);
-        break;
-      default: {
-        unsigned char out[3]={'%'};
-
-        if(!found_equals) {
-          /* if found_equals is NULL assuming, been in path */
-          if(*q == '/') {
-            /* allowed as if */
-            result = curlx_dyn_addn(dq, q, 1);
-            break;
-          }
-        }
-        else {
-          /* allowed as-is */
-          if(*q == '=') {
-            result = curlx_dyn_addn(dq, q, 1);
-            *found_equals = TRUE;
-            break;
-          }
-        }
-        /* URL encode */
-        Curl_hexbyte(&out[1], *q, FALSE);
-        result = curlx_dyn_addn(dq, out, 3);
-        break;
-      }
-      }
+  struct Curl_str original_path;
+
+  curlx_str_assign(&original_path, q, len);
+
+  /* Normalized path will be either the same or shorter than the original
+   * path, plus trailing slash */
+
+  if(do_uri_encode) {
+    result = uri_encode_path(&original_path, new_path);
+    if(result) {
+      goto fail;
     }
   }
+  else {
+    result = curlx_dyn_addn(new_path, q, len);
+    if(result) {
+      goto fail;
+    }
+  }
+
+  if(curlx_dyn_len(new_path) == 0) {
+    result = curlx_dyn_add(new_path, "/");
+  }
+fail:
   return result;
 }
 
-
-static CURLcode canon_query(struct Curl_easy *data,
-                            const char *query, struct dynbuf *dq)
+UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
 {
   CURLcode result = CURLE_OK;
-  int entry = 0;
-  int i;
-  const char *p = query;
-  struct pair array[MAX_QUERYPAIRS];
-  struct pair *ap = &array[0];
+
+  struct dynbuf query_array[MAX_QUERY_COMPONENTS];
+  struct pair encoded_query_array[MAX_QUERY_COMPONENTS];
+  size_t num_query_components;
+  size_t counted_query_components = 0;
+  size_t index;
+  size_t in_key_len;
+  size_t in_value_len;
+  size_t query_part_len;
+  const char *in_key;
+  char *in_value;
+  char *offset;
+  char *key_ptr;
+  char *value_ptr;
+  const char *query_part;
+
   if(!query)
     return result;
 
-  /* sort the name=value pairs first */
-  do {
-    char *amp;
-    entry++;
-    ap->p = p;
-    amp = strchr(p, '&');
-    if(amp)
-      ap->len = amp - p; /* excluding the ampersand */
+  result = split_to_dyn_array(query, '&', &query_array[0],
+    &num_query_components);
+  if(result) {
+    goto fail;
+  }
+
+  /* Create list of pairs, each pair containing an encoded query
+    * component */
+
+  for(index = 0; index < num_query_components;
+    index++) {
+
+    query_part_len = curlx_dyn_len(&query_array[index]);
+    query_part = curlx_dyn_ptr(&query_array[index]);
+
+    in_key = query_part;
+
+    offset = strchr(query_part, '=');
+    /* If there is no equals, this key has no value */
+    if(!offset) {
+      in_key_len = strlen(in_key);
+    }
     else {
-      ap->len = strlen(p);
-      break;
+      in_key_len = offset - in_key;
+    }
+
+    curlx_dyn_init(&encoded_query_array[index].key, query_part_len*3 + 1);
+    curlx_dyn_init(&encoded_query_array[index].value, query_part_len*3 + 1);
+    counted_query_components++;
+
+    /* Decode/encode the key */
+    result = http_aws_decode_encode(in_key, in_key_len,
+      &encoded_query_array[index].key);
+    if(result) {
+      goto fail;
+    }
+
+    /* Decode/encode the value if it exists */
+    if(offset && offset != (query_part + query_part_len - 1)) {
+      in_value = offset + 1;
+      in_value_len = query_part + query_part_len - (offset + 1);
+      result = http_aws_decode_encode(in_value, in_value_len,
+        &encoded_query_array[index].value);
+      if(result) {
+        goto fail;
+      }
+    }
+    else {
+      /* If there is no value, the value is an empty string */
+      curlx_dyn_init(&encoded_query_array[index].value, 2);
+      result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1);
+    }
+
+    if(result) {
+      goto fail;
     }
-    ap++;
-    p = amp + 1;
-  } while(entry < MAX_QUERYPAIRS);
-  if(entry == MAX_QUERYPAIRS) {
-    /* too many query pairs for us */
-    failf(data, "aws-sigv4: too many query pairs in URL");
-    return CURLE_URL_MALFORMAT;
   }
 
-  qsort(&array[0], entry, sizeof(struct pair), compare_func);
+  /* Sort the encoded query components by key and value */
+  qsort(&encoded_query_array, num_query_components,
+    sizeof(struct pair), compare_func);
 
-  ap = &array[0];
-  for(i = 0; !result && (i < entry); i++, ap++) {
-    const char *q = ap->p;
-    bool found_equals = FALSE;
-    if(!ap->len)
-      continue;
-    result = canon_string(q, ap->len, dq, &found_equals);
-    if(!result && !found_equals) {
-      /* queries without value still need an equals */
-      result = curlx_dyn_addn(dq, "=", 1);
+  /* Append the query components together to make a full query string */
+  for(index = 0; index < num_query_components; index++) {
+
+    key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key);
+    value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value);
+
+    if(value_ptr && strlen(value_ptr)) {
+      result = curlx_dyn_addf(dq, "%s=%s&", key_ptr, value_ptr);
     }
-    if(!result && i < entry - 1) {
-      /* insert ampersands between query pairs */
-      result = curlx_dyn_addn(dq, "&", 1);
+    else {
+      /* Empty value is always encoded to key= */
+      result = curlx_dyn_addf(dq, "%s=&", key_ptr);
+    }
+    if(result) {
+      goto fail;
     }
   }
+  /* Remove trailing & */
+  result = curlx_dyn_setlen(dq, curlx_dyn_len(dq)-1);
+
+fail:
+  pair_array_free(&encoded_query_array[0], counted_query_components);
+  dyn_array_free(&query_array[0], num_query_components);
   return result;
 }
 
-
 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
 {
   CURLcode result = CURLE_OUT_OF_MEMORY;
@@ -658,6 +714,11 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
   unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0};
   char *auth_headers = NULL;
 
+  if(data->set.path_as_is) {
+    failf(data, "Cannot use sigv4 authentication with path-as-is flag");
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  }
+
   if(Curl_checkheaders(data, STRCONST("Authorization"))) {
     /* Authorization already present, Bailing out */
     return CURLE_OK;
@@ -787,12 +848,13 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
   memcpy(date, timestamp, sizeof(date));
   date[sizeof(date) - 1] = 0;
 
-  result = canon_query(data, data->state.up.query, &canonical_query);
+  result = canon_query(data->state.up.query, &canonical_query);
   if(result)
     goto fail;
 
-  result = canon_string(data->state.up.path, strlen(data->state.up.path),
-                        &canonical_path, NULL);
+  result = canon_path(data->state.up.path, strlen(data->state.up.path),
+                        &canonical_path,
+                        should_urlencode(&service));
   if(result)
     goto fail;
   result = CURLE_OUT_OF_MEMORY;
@@ -925,4 +987,200 @@ fail:
   return result;
 }
 
+/*
+* Frees all allocated strings in a dynbuf pair array, and the dynbuf itself
+*/
+
+static void pair_array_free(struct pair *pair_array, size_t num_elements)
+{
+  size_t index;
+
+  for(index = 0; index != num_elements; index++) {
+    curlx_dyn_free(&pair_array[index].key);
+    curlx_dyn_free(&pair_array[index].value);
+  }
+
+}
+
+/*
+* Frees all allocated strings in a split dynbuf, and the dynbuf itself
+*/
+
+static void dyn_array_free(struct dynbuf *db, size_t num_elements)
+{
+  size_t index;
+
+  for(index = 0; index < num_elements; index++) {
+    curlx_dyn_free((&db[index]));
+  }
+
+}
+
+/*
+* Splits source string by split_by, and creates an array of dynbuf in db
+* db is initialized by this function
+* Caller is responsible for freeing the array elements with dyn_array_free
+*/
+
+static CURLcode split_to_dyn_array(const char *source, char split_by,
+  struct dynbuf db[MAX_QUERY_COMPONENTS], size_t *num_splits_out)
+{
+
+  CURLcode result = CURLE_OK;
+
+  size_t len = strlen(source);
+
+  size_t pos = 0;     /* Position in result buffer */
+  size_t start = 0;   /* Start of current segment */
+  size_t segment_length = 0;
+  size_t index = 0;
+  size_t num_splits;
+
+  /* Split source_ptr on split_by and store the segment offsets and
+   * length in array */
+  num_splits = 0;
+  for(pos = 0; pos < len; pos++) {
+    if(source[pos] == split_by) {
+      if(segment_length) {
+        curlx_dyn_init(&db[index], segment_length + 1);
+        result = curlx_dyn_addn(&db[index], &source[start],
+          segment_length);
+        if(result) {
+          goto fail;
+        }
+        segment_length = 0;
+        index++;
+        if(++num_splits == MAX_QUERY_COMPONENTS) {
+          goto fail;
+        }
+      }
+      start = pos + 1;
+    }
+    else {
+      segment_length++;
+    }
+  }
+
+  if(segment_length) {
+    curlx_dyn_init(&db[index], segment_length + 1);
+    result = curlx_dyn_addn(&db[index], &source[start],
+                           segment_length);
+    if(result) {
+      goto fail;
+    }
+    if(++num_splits == MAX_QUERY_COMPONENTS) {
+      goto fail;
+    }
+}
+fail:
+  *num_splits_out = num_splits;
+  return result;
+}
+
+
+static bool is_reserved_char(const char c)
+{
+  return (ISALNUM(c) || ISURLPUNTCS(c));
+}
+
+static CURLcode uri_encode_path(struct Curl_str *original_path,
+struct dynbuf *new_path)
+{
+
+  const char *p = curlx_str(original_path);
+  CURLcode result = CURLE_OK;
+  size_t index;
+
+  for(index = 0; index < curlx_strlen(original_path); index++) {
+    /* Do not encode slashes or unreserved chars from RFC 3986 */
+    unsigned char c = p[index];
+    if(is_reserved_char(c) || c == '/') {
+      result = curlx_dyn_addn(new_path, &c, 1);
+      if(result) {
+        goto fail;
+      }
+    }
+    else {
+      result = curlx_dyn_addf(new_path, "%%%02X", c);
+      if(result) {
+        goto fail;
+      }
+    }
+  }
+fail:
+  return result;
+}
+
+
+static CURLcode encode_query_component(char *component, size_t len,
+  struct dynbuf *db)
+{
+
+  size_t index;
+  CURLcode result = CURLE_OK;
+  unsigned char this_char;
+
+  for(index = 0; index < len; index++) {
+
+    this_char = component[index];
+
+    if(is_reserved_char(this_char)) {
+      /* Escape unreserved chars from RFC 3986 */
+      result = curlx_dyn_addn(db, &this_char, 1);
+    }
+    else if(this_char == '+') {
+      /* Encode '+' as space */
+      result = curlx_dyn_add(db, "%20");
+    }
+    else {
+      result = curlx_dyn_addf(db, "%%%02X", this_char);
+    }
+    if(result) {
+      goto fail;
+    }
+
+  }
+fail:
+  return result;
+}
+
+/*
+* Populates a dynbuf containing url_encode(url_decode(in))
+*/
+
+static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
+struct dynbuf *out)
+{
+  CURLcode result = CURLE_OK;
+  char *out_s;
+  size_t out_s_len;
+
+  result = Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA);
+
+  if(result) {
+    goto fail;
+  }
+  result = encode_query_component(out_s, out_s_len, out);
+  Curl_safefree(out_s);
+fail:
+  return result;
+}
+
+static bool should_urlencode(struct Curl_str *service_name)
+{
+  /*
+   * These services require unmodified (not additionally url encoded) URL
+   * paths.
+   * should_urlencode == true is equivalent to should_urlencode_uri_path
+   * from the AWS SDK. Urls are already normalized by the curl url parser
+   */
+
+  if(curlx_str_cmp(service_name, "s3") ||
+     curlx_str_cmp(service_name, "s3-express") ||
+     curlx_str_cmp(service_name, "s3-outposts")) {
+    return false;
+  }
+  return true;
+}
+
 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
index 8928f4f717c3c0b4b09dee0c343cc5d78007471c..7d3a3d94f4c68a4fe4304e8396378927ef2d0f7a 100644 (file)
  *
  ***************************************************************************/
 #include "curl_setup.h"
+#include "curlx/dynbuf.h"
+#include "urldata.h"
+#include "curlx/strparse.h"
 
 /* this is for creating aws_sigv4 header output */
 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data);
 
+#ifdef UNITTESTS
+UNITTEST CURLcode canon_path(const char *q, size_t len,
+    struct dynbuf *new_path,
+    bool normalize);
+UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq);
+#endif
+
 #endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */
index c718b4461518db0d9acd8946e6291b7ae3d63157..e8f9e12be71e87b7624890f88216959d3f132582 100644 (file)
@@ -240,7 +240,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 \
+test1978 test1979 test1980 \
 \
 test2000 test2001 test2002 test2003 test2004 test2005 \
 \
diff --git a/tests/data/test1979 b/tests/data/test1979
new file mode 100644 (file)
index 0000000..d89b097
--- /dev/null
@@ -0,0 +1,22 @@
+<testcase>
+<info>
+<keywords>
+unittest
+canon_string
+</keywords>
+</info>
+
+#
+# Client-side
+<client>
+<server>
+none
+</server>
+<features>
+unittest
+</features>
+<name>
+sigv4 canon_string unit tests
+</name>
+</client>
+</testcase>
diff --git a/tests/data/test1980 b/tests/data/test1980
new file mode 100644 (file)
index 0000000..6e801fe
--- /dev/null
@@ -0,0 +1,22 @@
+<testcase>
+<info>
+<keywords>
+unittest
+canon_query
+</keywords>
+</info>
+
+#
+# Client-side
+<client>
+<server>
+none
+</server>
+<features>
+unittest
+</features>
+<name>
+sigv4 canon_query unit tests
+</name>
+</client>
+</testcase>
index 7bf6c6520b08a5ffda91b7b7eb64f16f826eea0b..dc1ae43f9e013b65d026d4147bf8feca1f19ec04 100644 (file)
@@ -39,7 +39,7 @@ aws
 aws-sigv4 with query
 </name>
 <command>
-"http://fake.fake.fake:8000/%TESTNUMBER/?name=me%&noval&aim=b%aad&&&weirdo=*.//-" -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT
+"http://fake.fake.fake:8000/%TESTNUMBER/?name=me&noval&aim=b%aad&&&weirdo=*.//-" -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -47,9 +47,9 @@ aws-sigv4 with query
 # Verify data after the test has been "shot"
 <verify>
 <protocol crlf="yes">
-GET /439/?name=me%&noval&aim=b%aad&&&weirdo=*.//- HTTP/1.1
+GET /439/?name=me&noval&aim=b%aad&&&weirdo=*.//- HTTP/1.1
 Host: fake.fake.fake:8000
-Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=cbbf4a72764e27e396730f5e56cea046d4ce862a2d91db4856fb086b92f49270
+Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=9dd8592929306832a6673d10063491391e486e5f50de4647ea7c2c797277e0a6
 X-Amz-Date: 19700101T000000Z
 User-Agent: curl/%VERSION
 Accept: */*
index 88b8fba39d9c634ecd0e1eb5aef970ca67e3d3d2..3b4cf74764e135d60bcecb87f35705241c289a99 100644 (file)
@@ -50,7 +50,7 @@ aws-sigv4 with query
 <protocol crlf="yes">
 GET /472/a=%e3%81%82 HTTP/1.1
 Host: fake.fake.fake:8000
-Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=c63315c199922f7ee00141869a250389405d19e205057249fb74726d940b1fc3
+Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=d2f4797c813fc51d729ac555a23ac682be908fdbfae2042ba98d214c9298201b
 X-Amz-Date: 19700101T000000Z
 User-Agent: curl/%VERSION
 Accept: */*
index b44b1c55897205fbbf6fab638b26ebf1c222c29d..fb68911941b912613610a96b82131f7c245cbaf6 100644 (file)
@@ -41,6 +41,7 @@ UNITPROGS = unit1300          unit1302 unit1303 unit1304 unit1305 unit1307 \
  unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 unit1656 unit1657 \
  unit1658 \
  unit1660 unit1661 unit1663 unit1664 \
+ unit1979 unit1980 \
  unit2600 unit2601 unit2602 unit2603 unit2604 \
  unit3200 \
  unit3205 \
@@ -132,6 +133,10 @@ unit1663_SOURCES = unit1663.c $(UNITFILES)
 
 unit1664_SOURCES = unit1664.c $(UNITFILES)
 
+unit1979_SOURCES = unit1979.c $(UNITFILES)
+
+unit1980_SOURCES = unit1980.c $(UNITFILES)
+
 unit2600_SOURCES = unit2600.c $(UNITFILES)
 
 unit2601_SOURCES = unit2601.c $(UNITFILES)
diff --git a/tests/unit/unit1979.c b/tests/unit/unit1979.c
new file mode 100644 (file)
index 0000000..e5f044d
--- /dev/null
@@ -0,0 +1,145 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.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 "curlcheck.h"
+
+
+#include "http_aws_sigv4.h"
+#include "dynbuf.h"
+
+static CURLcode unit_setup(void)
+{
+  return CURLE_OK;
+}
+
+static void unit_stop(void)
+{
+}
+
+UNITTEST_START
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS)
+
+struct testcase
+{
+  const char *testname;
+  const bool normalize;
+  const char *url_part;
+  const char *canonical_url;
+};
+
+static const struct testcase testcases[] = {
+  {
+    "test-equals-encode",
+    true,
+    "/a=b",
+    "/a%3Db",
+  },
+  {
+    "test-equals-noencode",
+    false,
+    "/a=b",
+    "/a=b",
+  },
+  {
+    "test-s3-tables",
+    true,
+    "/tables/arn%3Aaws%3As3tables%3Aus-east-1%3A022954301426%3Abucket%2Fja"
+    "soehartablebucket/jasoeharnamespace/jasoehartable/encryption",
+    "/tables/arn%253Aaws%253As3tables%253Aus-east-1%253A022954301426%253Ab"
+    "ucket%252Fjasoehartablebucket/jasoeharnamespace/jasoehartable/encrypt"
+    "ion",
+  },
+  {
+    "get-vanilla",
+    true,
+    "/",
+    "/",
+  },
+  {
+    "get-unreserved",
+    true,
+    "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+    "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+  },
+  {
+    "get-slashes-unnormalized",
+    false,
+    "//example//",
+    "//example//",
+  },
+  {
+    "get-space-normalized",
+    true,
+    "/example space/",
+    "/example%20space/",
+  },
+  {
+    "get-slash-dot-slash-unnormalized",
+    false,
+    "/./",
+    "/./",
+  },
+  {
+    "get-slash-unnormalized",
+    false,
+    "//",
+    "//",
+  },
+  {
+    "get-relative-relative-unnormalized",
+    false,
+    "/example1/example2/../..",
+    "/example1/example2/../..",
+  }
+};
+
+  struct dynbuf canonical_path;
+
+  char buffer[1024];
+  char *canonical_path_string;
+  size_t i;
+  int result;
+  int msnprintf_result;
+
+  for(i = 0; i < CURL_ARRAYSIZE(testcases); i++) {
+    curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
+
+    result = canon_path(testcases[i].url_part, strlen(testcases[i].url_part),
+      &canonical_path,
+      testcases[i].normalize);
+    canonical_path_string = curlx_dyn_ptr(&canonical_path);
+    msnprintf_result = curl_msnprintf(buffer, sizeof(buffer),
+    "%s: Received \"%s\" and should be \"%s\", normalize (%d)",
+      testcases[i].testname, curlx_dyn_ptr(&canonical_path),
+      testcases[i].canonical_url, testcases[i].normalize);
+    fail_unless(msnprintf_result >= 0, "curl_msnprintf fails");
+    fail_unless(!result && canonical_path_string &&
+      !strcmp(canonical_path_string,
+      testcases[i].canonical_url), buffer);
+    curlx_dyn_free(&canonical_path);
+  }
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
+
+UNITTEST_STOP
diff --git a/tests/unit/unit1980.c b/tests/unit/unit1980.c
new file mode 100644 (file)
index 0000000..69cd2dd
--- /dev/null
@@ -0,0 +1,114 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.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 "curlcheck.h"
+
+
+#include "http_aws_sigv4.h"
+#include "dynbuf.h"
+
+
+static CURLcode unit_setup(void)
+{
+  return CURLE_OK;
+}
+
+static void unit_stop(void)
+{
+}
+
+UNITTEST_START
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS)
+
+struct testcase {
+  const char *testname;
+  const char *query_part;
+  const char *canonical_query;
+};
+
+static const struct testcase testcases[] = {
+      {
+          "no-value",
+          "Param1=",
+          "Param1="
+      },
+      {
+        "test-439",
+        "name=me&noval&aim=b%aad&weirdo=*.//-",
+        "aim=b%AAd&name=me&noval=&weirdo=%2A.%2F%2F-"
+    },
+    {
+        "blank-query-params",
+        "hello=a&b&c=&d",
+        "b=&c=&d=&hello=a"
+      },
+      {
+          "get-vanilla-query-order-key-case",
+          "Param2=value2&Param1=value1",
+          "Param1=value1&Param2=value2"
+      },
+      {
+          "get-vanilla-query-unreserved",
+          "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz="
+          "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+          "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz="
+          "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+      },
+      {
+          "get-vanilla-empty-query-key",
+          "Param1=value1",
+          "Param1=value1"
+      },
+      {
+          "get-vanilla-query-order-encoded",
+          "Param-3=Value3&Param=Value2&%E1%88%B4=Value1",
+          "%E1%88%B4=Value1&Param=Value2&Param-3=Value3"
+      },
+};
+
+  struct dynbuf canonical_query;
+
+  char buffer[1024];
+  char *canonical_query_ptr;
+  size_t i;
+  int result;
+  int msnprintf_result;
+
+  for(i = 0;  i < CURL_ARRAYSIZE(testcases); i++) {
+    curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
+
+    result = canon_query(testcases[i].query_part, &canonical_query);
+    canonical_query_ptr = curlx_dyn_ptr(&canonical_query);
+    msnprintf_result = curl_msnprintf(buffer, sizeof(buffer),
+      "%s: Received \"%s\" and should be \"%s\"",
+      testcases[i].testname, canonical_query_ptr,
+      testcases[i].canonical_query);
+    fail_unless(msnprintf_result >= 0, "curl_msnprintf fails");
+    fail_unless(!result && canonical_query_ptr && !strcmp(canonical_query_ptr,
+      testcases[i].canonical_query),
+    buffer);
+    curlx_dyn_free(&canonical_query);
+ }
+
+ #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
+ UNITTEST_STOP