Coverage Report

Created: 2026-01-17 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/http_aws_sigv4.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS)
27
28
#include "urldata.h"
29
#include "strcase.h"
30
#include "strdup.h"
31
#include "http_aws_sigv4.h"
32
#include "curl_sha256.h"
33
#include "transfer.h"
34
#include "curl_trc.h"
35
#include "escape.h"
36
#include "curlx/strparse.h"
37
#include "slist.h"
38
39
#include <time.h>
40
41
#define HMAC_SHA256(k, kl, d, dl, o)                \
42
53.5k
  do {                                              \
43
53.5k
    result = Curl_hmacit(&Curl_HMAC_SHA256,         \
44
53.5k
                         (const unsigned char *)k,  \
45
53.5k
                         kl,                        \
46
53.5k
                         (const unsigned char *)d,  \
47
53.5k
                         dl, o);                    \
48
53.5k
    if(result) {                                    \
49
0
      goto fail;                                    \
50
0
    }                                               \
51
53.5k
  } while(0)
52
53
11.3k
#define TIMESTAMP_SIZE 17
54
55
/* hex-encoded with trailing null */
56
32.2k
#define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1)
57
58
38.3k
#define MAX_QUERY_COMPONENTS 128
59
60
struct pair {
61
  struct dynbuf key;
62
  struct dynbuf value;
63
};
64
65
static void sha256_to_hex(char *dst, unsigned char *sha)
66
32.1k
{
67
32.1k
  Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH,
68
32.1k
                 (unsigned char *)dst, SHA256_HEX_LENGTH);
69
32.1k
}
70
71
static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
72
10.7k
{
73
10.7k
  char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
74
75
10.7k
  if(tmp)
76
280
    return tmp;
77
10.5k
  return Curl_checkheaders(data, STRCONST("Date"));
78
10.7k
}
79
80
/* remove whitespace, and lowercase all headers */
81
static void trim_headers(struct curl_slist *head)
82
10.7k
{
83
10.7k
  struct curl_slist *l;
84
126k
  for(l = head; l; l = l->next) {
85
115k
    const char *value; /* to read from */
86
115k
    char *store;
87
115k
    size_t colon = strcspn(l->data, ":");
88
115k
    Curl_strntolower(l->data, l->data, colon);
89
90
115k
    value = &l->data[colon];
91
115k
    if(!*value)
92
0
      continue;
93
115k
    ++value;
94
115k
    store = (char *)CURL_UNCONST(value);
95
96
    /* skip leading whitespace */
97
115k
    curlx_str_passblanks(&value);
98
99
6.11M
    while(*value) {
100
6.00M
      int space = 0;
101
6.09M
      while(ISBLANK(*value)) {
102
89.8k
        value++;
103
89.8k
        space++;
104
89.8k
      }
105
6.00M
      if(space) {
106
        /* replace any number of consecutive whitespace with a single space,
107
           unless at the end of the string, then nothing */
108
32.5k
        if(*value)
109
24.2k
          *store++ = ' ';
110
32.5k
      }
111
5.96M
      else
112
5.96M
        *store++ = *value++;
113
6.00M
    }
114
115k
    *store = 0; /* null-terminate */
115
115k
  }
116
10.7k
}
117
118
/*
119
 * Frees all allocated strings in a dynbuf pair array, and the dynbuf itself
120
 */
121
static void pair_array_free(struct pair *pair_array, size_t num_elements)
122
4.26k
{
123
4.26k
  size_t index;
124
125
40.4k
  for(index = 0; index != num_elements; index++) {
126
36.2k
    curlx_dyn_free(&pair_array[index].key);
127
36.2k
    curlx_dyn_free(&pair_array[index].value);
128
36.2k
  }
129
4.26k
}
130
131
/*
132
 * Frees all allocated strings in a split dynbuf, and the dynbuf itself
133
 */
134
static void dyn_array_free(struct dynbuf *db, size_t num_elements)
135
4.35k
{
136
4.35k
  size_t index;
137
138
42.7k
  for(index = 0; index < num_elements; index++)
139
38.3k
    curlx_dyn_free((&db[index]));
140
4.35k
}
141
142
/*
143
 * Splits source string by SPLIT_BY, and creates an array of dynbuf in db.
144
 * db is initialized by this function.
145
 * Caller is responsible for freeing the array elements with dyn_array_free
146
 */
147
148
2.66M
#define SPLIT_BY '&'
149
150
static CURLcode split_to_dyn_array(const char *source,
151
                                   struct dynbuf db[MAX_QUERY_COMPONENTS],
152
                                   size_t *num_splits_out)
153
4.35k
{
154
4.35k
  CURLcode result = CURLE_OK;
155
4.35k
  size_t len = strlen(source);
156
4.35k
  size_t pos;         /* Position in result buffer */
157
4.35k
  size_t start = 0;   /* Start of current segment */
158
4.35k
  size_t segment_length = 0;
159
4.35k
  size_t index = 0;
160
4.35k
  size_t num_splits = 0;
161
162
  /* Split source_ptr on SPLIT_BY and store the segment offsets and length in
163
   * array */
164
2.67M
  for(pos = 0; pos < len; pos++) {
165
2.66M
    if(source[pos] == SPLIT_BY) {
166
62.4k
      if(segment_length) {
167
34.3k
        curlx_dyn_init(&db[index], segment_length + 1);
168
34.3k
        result = curlx_dyn_addn(&db[index], &source[start], segment_length);
169
34.3k
        if(result)
170
0
          goto fail;
171
172
34.3k
        segment_length = 0;
173
34.3k
        index++;
174
34.3k
        if(++num_splits == MAX_QUERY_COMPONENTS) {
175
9
          result = CURLE_TOO_LARGE;
176
9
          goto fail;
177
9
        }
178
34.3k
      }
179
62.3k
      start = pos + 1;
180
62.3k
    }
181
2.60M
    else {
182
2.60M
      segment_length++;
183
2.60M
    }
184
2.66M
  }
185
186
4.34k
  if(segment_length) {
187
4.01k
    curlx_dyn_init(&db[index], segment_length + 1);
188
4.01k
    result = curlx_dyn_addn(&db[index], &source[start], segment_length);
189
4.01k
    if(!result) {
190
4.01k
      if(++num_splits == MAX_QUERY_COMPONENTS)
191
8
        result = CURLE_TOO_LARGE;
192
4.01k
    }
193
4.01k
  }
194
4.35k
fail:
195
4.35k
  *num_splits_out = num_splits;
196
4.35k
  return result;
197
4.34k
}
198
199
static bool is_reserved_char(const char c)
200
4.87M
{
201
4.87M
  return (ISALNUM(c) || ISURLPUNTCS(c));
202
4.87M
}
203
204
static CURLcode uri_encode_path(struct Curl_str *original_path,
205
                                struct dynbuf *new_path)
206
10.5k
{
207
10.5k
  const char *p = curlx_str(original_path);
208
10.5k
  size_t i;
209
210
2.21M
  for(i = 0; i < curlx_strlen(original_path); i++) {
211
    /* Do not encode slashes or unreserved chars from RFC 3986 */
212
2.20M
    CURLcode result = CURLE_OK;
213
2.20M
    unsigned char c = p[i];
214
2.20M
    if(is_reserved_char(c) || c == '/')
215
1.44M
      result = curlx_dyn_addn(new_path, &c, 1);
216
755k
    else
217
755k
      result = curlx_dyn_addf(new_path, "%%%02X", c);
218
2.20M
    if(result)
219
5
      return result;
220
2.20M
  }
221
222
10.5k
  return CURLE_OK;
223
10.5k
}
224
225
static CURLcode encode_query_component(char *component, size_t len,
226
                                       struct dynbuf *db)
227
48.2k
{
228
48.2k
  size_t i;
229
2.72M
  for(i = 0; i < len; i++) {
230
2.67M
    CURLcode result = CURLE_OK;
231
2.67M
    unsigned char this_char = component[i];
232
233
2.67M
    if(is_reserved_char(this_char))
234
      /* Escape unreserved chars from RFC 3986 */
235
534k
      result = curlx_dyn_addn(db, &this_char, 1);
236
2.13M
    else if(this_char == '+')
237
      /* Encode '+' as space */
238
7.75k
      result = curlx_dyn_add(db, "%20");
239
2.13M
    else
240
2.13M
      result = curlx_dyn_addf(db, "%%%02X", this_char);
241
2.67M
    if(result)
242
0
      return result;
243
2.67M
  }
244
245
48.2k
  return CURLE_OK;
246
48.2k
}
247
248
/*
249
 * Populates a dynbuf containing url_encode(url_decode(in))
250
 */
251
static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
252
                                       struct dynbuf *out)
253
48.2k
{
254
48.2k
  char *out_s;
255
48.2k
  size_t out_s_len;
256
48.2k
  CURLcode result =
257
48.2k
    Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA);
258
259
48.2k
  if(!result) {
260
48.2k
    result = encode_query_component(out_s, out_s_len, out);
261
48.2k
    Curl_safefree(out_s);
262
48.2k
  }
263
48.2k
  return result;
264
48.2k
}
265
266
static bool should_urlencode(struct Curl_str *service_name)
267
10.7k
{
268
  /*
269
   * These services require unmodified (not additionally URL-encoded) URL
270
   * paths.
271
   * should_urlencode == true is equivalent to should_urlencode_uri_path
272
   * from the AWS SDK. Urls are already normalized by the curl URL parser
273
   */
274
275
10.7k
  if(curlx_str_cmp(service_name, "s3") ||
276
10.5k
     curlx_str_cmp(service_name, "s3-express") ||
277
10.5k
     curlx_str_cmp(service_name, "s3-outposts")) {
278
197
    return false;
279
197
  }
280
10.5k
  return true;
281
10.7k
}
282
283
/* maximum length for the aws sivg4 parts */
284
63.2k
#define MAX_SIGV4_LEN    64
285
21.5k
#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
286
287
/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
288
10.7k
#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
289
290
/* alphabetically compare two headers by their name, expecting
291
   headers to use ':' at this point */
292
static int compare_header_names(const char *a, const char *b)
293
48.0M
{
294
48.0M
  const char *colon_a;
295
48.0M
  const char *colon_b;
296
48.0M
  size_t len_a;
297
48.0M
  size_t len_b;
298
48.0M
  size_t min_len;
299
48.0M
  int cmp;
300
301
48.0M
  colon_a = strchr(a, ':');
302
48.0M
  colon_b = strchr(b, ':');
303
304
48.0M
  DEBUGASSERT(colon_a);
305
48.0M
  DEBUGASSERT(colon_b);
306
307
48.0M
  len_a = colon_a ? (size_t)(colon_a - a) : strlen(a);
308
48.0M
  len_b = colon_b ? (size_t)(colon_b - b) : strlen(b);
309
310
48.0M
  min_len = (len_a < len_b) ? len_a : len_b;
311
312
48.0M
  cmp = strncmp(a, b, min_len);
313
314
  /* return the shorter of the two if one is shorter */
315
48.0M
  if(!cmp)
316
36.6M
    return (int)(len_a - len_b);
317
318
11.4M
  return cmp;
319
48.0M
}
320
321
/* Merge duplicate header definitions by comma delimiting their values
322
   in the order defined the headers are defined, expecting headers to
323
   be alpha-sorted and use ':' at this point */
324
static CURLcode merge_duplicate_headers(struct curl_slist *head)
325
10.7k
{
326
10.7k
  struct curl_slist *curr = head;
327
10.7k
  CURLcode result = CURLE_OK;
328
329
125k
  while(curr) {
330
125k
    struct curl_slist *next = curr->next;
331
125k
    if(!next)
332
10.7k
      break;
333
334
114k
    if(compare_header_names(curr->data, next->data) == 0) {
335
87.2k
      struct dynbuf buf;
336
87.2k
      char *colon_next;
337
87.2k
      char *val_next;
338
339
87.2k
      curlx_dyn_init(&buf, CURL_MAX_HTTP_HEADER);
340
341
87.2k
      result = curlx_dyn_add(&buf, curr->data);
342
87.2k
      if(result)
343
0
        return result;
344
345
87.2k
      colon_next = strchr(next->data, ':');
346
87.2k
      DEBUGASSERT(colon_next);
347
87.2k
      val_next = colon_next + 1;
348
349
87.2k
      result = curlx_dyn_addn(&buf, ",", 1);
350
87.2k
      if(result)
351
0
        return result;
352
353
87.2k
      result = curlx_dyn_add(&buf, val_next);
354
87.2k
      if(result)
355
0
        return result;
356
357
87.2k
      curlx_free(curr->data);
358
87.2k
      curr->data = curlx_dyn_ptr(&buf);
359
360
87.2k
      curr->next = next->next;
361
87.2k
      curlx_free(next->data);
362
87.2k
      curlx_free(next);
363
87.2k
    }
364
27.6k
    else {
365
27.6k
      curr = curr->next;
366
27.6k
    }
367
114k
  }
368
369
10.7k
  return CURLE_OK;
370
10.7k
}
371
372
/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
373
static CURLcode make_headers(struct Curl_easy *data,
374
                             const char *hostname,
375
                             char *timestamp,
376
                             const char *provider1,
377
                             size_t plen, /* length of provider1 */
378
                             char **date_header,
379
                             char *content_sha256_header,
380
                             struct dynbuf *canonical_headers,
381
                             struct dynbuf *signed_headers)
382
10.7k
{
383
10.7k
  char date_hdr_key[DATE_HDR_KEY_LEN];
384
10.7k
  char date_full_hdr[DATE_FULL_HDR_LEN];
385
10.7k
  struct curl_slist *head = NULL;
386
10.7k
  struct curl_slist *tmp_head = NULL;
387
10.7k
  CURLcode ret = CURLE_OUT_OF_MEMORY;
388
10.7k
  struct curl_slist *l;
389
10.7k
  bool again = TRUE;
390
391
10.7k
  curl_msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%.*s-Date",
392
10.7k
                 (int)plen, provider1);
393
  /* provider1 ucfirst */
394
10.7k
  Curl_strntolower(&date_hdr_key[2], provider1, plen);
395
10.7k
  date_hdr_key[2] = Curl_raw_toupper(provider1[0]);
396
397
10.7k
  curl_msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
398
10.7k
                 "x-%.*s-date:%s", (int)plen, provider1, timestamp);
399
  /* provider1 lowercase */
400
10.7k
  Curl_strntolower(&date_full_hdr[2], provider1, plen);
401
402
10.7k
  if(!Curl_checkheaders(data, STRCONST("Host"))) {
403
10.5k
    char *fullhost;
404
405
10.5k
    if(data->state.aptr.host) {
406
      /* remove /r/n as the separator for canonical request must be '\n' */
407
2.52k
      size_t pos = strcspn(data->state.aptr.host, "\n\r");
408
2.52k
      fullhost = Curl_memdup0(data->state.aptr.host, pos);
409
2.52k
    }
410
8.04k
    else
411
8.04k
      fullhost = curl_maprintf("host:%s", hostname);
412
413
10.5k
    if(fullhost)
414
10.5k
      head = Curl_slist_append_nodup(NULL, fullhost);
415
10.5k
    if(!head) {
416
0
      curlx_free(fullhost);
417
0
      goto fail;
418
0
    }
419
10.5k
  }
420
421
10.7k
  if(*content_sha256_header) {
422
103
    tmp_head = curl_slist_append(head, content_sha256_header);
423
103
    if(!tmp_head)
424
0
      goto fail;
425
103
    head = tmp_head;
426
103
  }
427
428
  /* copy user headers to our header list. the logic is based on how http.c
429
     handles user headers.
430
431
     user headers in format 'name:' with no value are used to signal that an
432
     internal header of that name should be removed. those user headers are not
433
     added to this list.
434
435
     user headers in format 'name;' with no value are used to signal that a
436
     header of that name with no value should be sent. those user headers are
437
     added to this list but in the format that they will be sent, ie the
438
     semi-colon is changed to a colon for format 'name:'.
439
440
     user headers with a value of whitespace only, or without a colon or
441
     semi-colon, are not added to this list.
442
     */
443
132k
  for(l = data->set.headers; l; l = l->next) {
444
121k
    char *dupdata, *ptr;
445
121k
    char *sep = strchr(l->data, ':');
446
121k
    if(!sep)
447
73.8k
      sep = strchr(l->data, ';');
448
121k
    if(!sep || (*sep == ':' && !*(sep + 1)))
449
14.3k
      continue;
450
123k
    for(ptr = sep + 1; ISBLANK(*ptr); ++ptr)
451
16.1k
      ;
452
107k
    if(!*ptr && ptr != sep + 1) /* a value of whitespace only */
453
2.44k
      continue;
454
104k
    dupdata = curlx_strdup(l->data);
455
104k
    if(!dupdata)
456
0
      goto fail;
457
104k
    dupdata[sep - l->data] = ':';
458
104k
    tmp_head = Curl_slist_append_nodup(head, dupdata);
459
104k
    if(!tmp_head) {
460
0
      curlx_free(dupdata);
461
0
      goto fail;
462
0
    }
463
104k
    head = tmp_head;
464
104k
  }
465
466
10.7k
  trim_headers(head);
467
468
10.7k
  *date_header = find_date_hdr(data, date_hdr_key);
469
10.7k
  if(!*date_header) {
470
10.2k
    tmp_head = curl_slist_append(head, date_full_hdr);
471
10.2k
    if(!tmp_head)
472
0
      goto fail;
473
10.2k
    head = tmp_head;
474
10.2k
    *date_header = curl_maprintf("%s: %s\r\n", date_hdr_key, timestamp);
475
10.2k
    if(!*date_header)
476
0
      goto fail;
477
10.2k
  }
478
507
  else {
479
507
    const char *value;
480
507
    const char *endp;
481
507
    value = strchr(*date_header, ':');
482
507
    if(!value) {
483
37
      *date_header = NULL;
484
37
      goto fail;
485
37
    }
486
470
    ++value;
487
470
    curlx_str_passblanks(&value);
488
470
    endp = value;
489
8.26k
    while(*endp && ISALNUM(*endp))
490
7.79k
      ++endp;
491
    /* 16 bytes => "19700101T000000Z" */
492
470
    if((endp - value) == TIMESTAMP_SIZE - 1) {
493
33
      memcpy(timestamp, value, TIMESTAMP_SIZE - 1);
494
33
      timestamp[TIMESTAMP_SIZE - 1] = 0;
495
33
    }
496
437
    else
497
      /* bad timestamp length */
498
437
      timestamp[0] = 0;
499
470
    *date_header = NULL;
500
470
  }
501
502
  /* alpha-sort by header name in a case sensitive manner */
503
91.5k
  do {
504
91.5k
    again = FALSE;
505
48.1M
    for(l = head; l; l = l->next) {
506
48.0M
      struct curl_slist *next = l->next;
507
508
48.0M
      if(next && compare_header_names(l->data, next->data) > 0) {
509
9.75M
        char *tmp = l->data;
510
511
9.75M
        l->data = next->data;
512
9.75M
        next->data = tmp;
513
9.75M
        again = TRUE;
514
9.75M
      }
515
48.0M
    }
516
91.5k
  } while(again);
517
518
10.7k
  ret = merge_duplicate_headers(head);
519
10.7k
  if(ret)
520
0
    goto fail;
521
522
49.0k
  for(l = head; l; l = l->next) {
523
38.3k
    char *tmp;
524
525
38.3k
    if(curlx_dyn_add(canonical_headers, l->data))
526
24
      goto fail;
527
38.3k
    if(curlx_dyn_add(canonical_headers, "\n"))
528
2
      goto fail;
529
530
38.3k
    tmp = strchr(l->data, ':');
531
38.3k
    if(tmp)
532
38.3k
      *tmp = 0;
533
534
38.3k
    if(l != head) {
535
27.6k
      if(curlx_dyn_add(signed_headers, ";"))
536
0
        goto fail;
537
27.6k
    }
538
38.3k
    if(curlx_dyn_add(signed_headers, l->data))
539
0
      goto fail;
540
38.3k
  }
541
542
10.7k
  ret = CURLE_OK;
543
10.7k
fail:
544
10.7k
  curl_slist_free_all(head);
545
546
10.7k
  return ret;
547
10.7k
}
548
549
103
#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
550
/* add 2 for ": " between header name and value */
551
103
#define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + SHA256_HEX_LENGTH)
552
553
/* try to parse a payload hash from the content-sha256 header */
554
static const char *parse_content_sha_hdr(struct Curl_easy *data,
555
                                         const char *provider1,
556
                                         size_t plen,
557
                                         size_t *value_len)
558
10.7k
{
559
10.7k
  char key[CONTENT_SHA256_KEY_LEN];
560
10.7k
  size_t key_len;
561
10.7k
  const char *value;
562
10.7k
  size_t len;
563
564
10.7k
  key_len = curl_msnprintf(key, sizeof(key), "x-%.*s-content-sha256",
565
10.7k
                           (int)plen, provider1);
566
567
10.7k
  value = Curl_checkheaders(data, key, key_len);
568
10.7k
  if(!value)
569
10.7k
    return NULL;
570
571
54
  value = strchr(value, ':');
572
54
  if(!value)
573
8
    return NULL;
574
46
  ++value;
575
576
46
  curlx_str_passblanks(&value);
577
578
46
  len = strlen(value);
579
694
  while(len > 0 && ISBLANK(value[len - 1]))
580
648
    --len;
581
582
46
  *value_len = len;
583
46
  return value;
584
54
}
585
586
static CURLcode calc_payload_hash(struct Curl_easy *data,
587
                                  unsigned char *sha_hash, char *sha_hex)
588
10.7k
{
589
10.7k
  const char *post_data = data->set.postfields;
590
10.7k
  size_t post_data_len = 0;
591
10.7k
  CURLcode result;
592
593
10.7k
  if(post_data) {
594
253
    if(data->set.postfieldsize < 0)
595
253
      post_data_len = strlen(post_data);
596
0
    else
597
0
      post_data_len = (size_t)data->set.postfieldsize;
598
253
  }
599
10.7k
  result = Curl_sha256it(sha_hash, (const unsigned char *)post_data,
600
10.7k
                         post_data_len);
601
10.7k
  if(!result)
602
10.7k
    sha256_to_hex(sha_hex, sha_hash);
603
10.7k
  return result;
604
10.7k
}
605
606
56
#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
607
608
static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
609
                                     Curl_HttpReq httpreq,
610
                                     const char *provider1,
611
                                     size_t plen,
612
                                     unsigned char *sha_hash,
613
                                     char *sha_hex, char *header)
614
103
{
615
103
  bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
616
  /* The request method or filesize indicate no request payload */
617
103
  bool empty_payload = (empty_method || data->set.filesize == 0);
618
  /* The POST payload is in memory */
619
103
  bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
620
103
  CURLcode ret = CURLE_OUT_OF_MEMORY;
621
622
103
  if(empty_payload || post_payload) {
623
    /* Calculate a real hash when we know the request payload */
624
75
    ret = calc_payload_hash(data, sha_hash, sha_hex);
625
75
    if(ret)
626
0
      goto fail;
627
75
  }
628
28
  else {
629
    /* Fall back to s3's UNSIGNED-PAYLOAD */
630
28
    size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
631
28
    DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
632
28
    memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
633
28
    sha_hex[len] = 0;
634
28
  }
635
636
  /* format the required content-sha256 header */
637
103
  curl_msnprintf(header, CONTENT_SHA256_HDR_LEN,
638
103
                 "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex);
639
640
103
  ret = CURLE_OK;
641
103
fail:
642
103
  return ret;
643
103
}
644
645
static int compare_func(const void *a, const void *b)
646
136k
{
647
648
136k
  const struct pair *aa = a;
649
136k
  const struct pair *bb = b;
650
136k
  const size_t aa_key_len = curlx_dyn_len(&aa->key);
651
136k
  const size_t bb_key_len = curlx_dyn_len(&bb->key);
652
136k
  const size_t aa_value_len = curlx_dyn_len(&aa->value);
653
136k
  const size_t bb_value_len = curlx_dyn_len(&bb->value);
654
136k
  int compare;
655
656
  /* If one element is empty, the other is always sorted higher */
657
658
  /* Compare keys */
659
136k
  if((aa_key_len == 0) && (bb_key_len == 0))
660
0
    return 0;
661
136k
  if(aa_key_len == 0)
662
0
    return -1;
663
136k
  if(bb_key_len == 0)
664
0
    return 1;
665
136k
  compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key));
666
136k
  if(compare) {
667
104k
    return compare;
668
104k
  }
669
670
  /* Compare values */
671
32.4k
  if((aa_value_len == 0) && (bb_value_len == 0))
672
0
    return 0;
673
32.4k
  if(aa_value_len == 0)
674
0
    return -1;
675
32.4k
  if(bb_value_len == 0)
676
0
    return 1;
677
32.4k
  compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value));
678
679
32.4k
  return compare;
680
32.4k
}
681
682
UNITTEST CURLcode canon_path(const char *q, size_t len,
683
                             struct dynbuf *new_path,
684
                             bool do_uri_encode)
685
10.7k
{
686
10.7k
  CURLcode result = CURLE_OK;
687
688
10.7k
  struct Curl_str original_path;
689
690
10.7k
  curlx_str_assign(&original_path, q, len);
691
692
  /* Normalized path will be either the same or shorter than the original
693
   * path, plus trailing slash */
694
695
10.7k
  if(do_uri_encode)
696
10.5k
    result = uri_encode_path(&original_path, new_path);
697
197
  else
698
197
    result = curlx_dyn_addn(new_path, q, len);
699
700
10.7k
  if(!result) {
701
10.7k
    if(curlx_dyn_len(new_path) == 0)
702
0
      result = curlx_dyn_add(new_path, "/");
703
10.7k
  }
704
705
10.7k
  return result;
706
10.7k
}
707
708
UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
709
10.7k
{
710
10.7k
  CURLcode result = CURLE_OK;
711
712
10.7k
  struct dynbuf query_array[MAX_QUERY_COMPONENTS];
713
10.7k
  struct pair encoded_query_array[MAX_QUERY_COMPONENTS];
714
10.7k
  size_t num_query_components;
715
10.7k
  size_t counted_query_components = 0;
716
10.7k
  size_t index;
717
718
10.7k
  if(!query)
719
6.39k
    return result;
720
721
4.35k
  result = split_to_dyn_array(query, &query_array[0], &num_query_components);
722
4.35k
  if(result) {
723
17
    goto fail;
724
17
  }
725
726
  /* Create list of pairs, each pair containing an encoded query
727
   * component */
728
729
40.5k
  for(index = 0; index < num_query_components; index++) {
730
36.2k
    const char *in_key;
731
36.2k
    size_t in_key_len;
732
36.2k
    char *offset;
733
36.2k
    size_t query_part_len = curlx_dyn_len(&query_array[index]);
734
36.2k
    char *query_part = curlx_dyn_ptr(&query_array[index]);
735
736
36.2k
    in_key = query_part;
737
738
36.2k
    offset = strchr(query_part, '=');
739
    /* If there is no equals, this key has no value */
740
36.2k
    if(!offset) {
741
21.1k
      in_key_len = strlen(in_key);
742
21.1k
    }
743
15.0k
    else {
744
15.0k
      in_key_len = offset - in_key;
745
15.0k
    }
746
747
36.2k
    curlx_dyn_init(&encoded_query_array[index].key, query_part_len * 3 + 1);
748
36.2k
    curlx_dyn_init(&encoded_query_array[index].value, query_part_len * 3 + 1);
749
36.2k
    counted_query_components++;
750
751
    /* Decode/encode the key */
752
36.2k
    result = http_aws_decode_encode(in_key, in_key_len,
753
36.2k
                                    &encoded_query_array[index].key);
754
36.2k
    if(result) {
755
0
      goto fail;
756
0
    }
757
758
    /* Decode/encode the value if it exists */
759
36.2k
    if(offset && offset != (query_part + query_part_len - 1)) {
760
12.0k
      size_t in_value_len;
761
12.0k
      const char *in_value = offset + 1;
762
12.0k
      in_value_len = query_part + query_part_len - (offset + 1);
763
12.0k
      result = http_aws_decode_encode(in_value, in_value_len,
764
12.0k
                                      &encoded_query_array[index].value);
765
12.0k
      if(result) {
766
0
        goto fail;
767
0
      }
768
12.0k
    }
769
24.1k
    else {
770
      /* If there is no value, the value is an empty string */
771
24.1k
      curlx_dyn_init(&encoded_query_array[index].value, 2);
772
24.1k
      result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1);
773
24.1k
    }
774
775
36.2k
    if(result) {
776
0
      goto fail;
777
0
    }
778
36.2k
  }
779
780
  /* Sort the encoded query components by key and value */
781
4.33k
  qsort(&encoded_query_array, num_query_components,
782
4.33k
        sizeof(struct pair), compare_func);
783
784
  /* Append the query components together to make a full query string */
785
40.2k
  for(index = 0; index < num_query_components; index++) {
786
787
35.9k
    if(index)
788
31.6k
      result = curlx_dyn_addn(dq, "&", 1);
789
35.9k
    if(!result) {
790
35.8k
      char *key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key);
791
35.8k
      char *value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value);
792
35.8k
      size_t vlen = curlx_dyn_len(&encoded_query_array[index].value);
793
35.8k
      if(value_ptr && vlen) {
794
35.8k
        result = curlx_dyn_addf(dq, "%s=%s", key_ptr, value_ptr);
795
35.8k
      }
796
0
      else {
797
        /* Empty value is always encoded to key= */
798
0
        result = curlx_dyn_addf(dq, "%s=", key_ptr);
799
0
      }
800
35.8k
    }
801
35.9k
    if(result)
802
17
      break;
803
35.9k
  }
804
805
4.35k
fail:
806
4.35k
  if(counted_query_components)
807
    /* the encoded_query_array might not be initialized yet */
808
4.26k
    pair_array_free(&encoded_query_array[0], counted_query_components);
809
4.35k
  dyn_array_free(&query_array[0], num_query_components);
810
4.35k
  return result;
811
4.33k
}
812
813
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
814
11.0k
{
815
11.0k
  CURLcode result = CURLE_OUT_OF_MEMORY;
816
11.0k
  struct connectdata *conn = data->conn;
817
11.0k
  const char *line;
818
11.0k
  struct Curl_str provider0;
819
11.0k
  struct Curl_str provider1;
820
11.0k
  struct Curl_str region = { NULL, 0 };
821
11.0k
  struct Curl_str service = { NULL, 0 };
822
11.0k
  const char *hostname = conn->host.name;
823
11.0k
  time_t clock;
824
11.0k
  struct tm tm;
825
11.0k
  char timestamp[TIMESTAMP_SIZE];
826
11.0k
  char date[9];
827
11.0k
  struct dynbuf canonical_headers;
828
11.0k
  struct dynbuf signed_headers;
829
11.0k
  struct dynbuf canonical_query;
830
11.0k
  struct dynbuf canonical_path;
831
11.0k
  char *date_header = NULL;
832
11.0k
  Curl_HttpReq httpreq;
833
11.0k
  const char *method = NULL;
834
11.0k
  const char *payload_hash = NULL;
835
11.0k
  size_t payload_hash_len = 0;
836
11.0k
  unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
837
11.0k
  char sha_hex[SHA256_HEX_LENGTH];
838
11.0k
  char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
839
11.0k
  char *canonical_request = NULL;
840
11.0k
  char *request_type = NULL;
841
11.0k
  char *credential_scope = NULL;
842
11.0k
  char *str_to_sign = NULL;
843
11.0k
  const char *user = data->state.aptr.user ? data->state.aptr.user : "";
844
11.0k
  char *secret = NULL;
845
11.0k
  unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = { 0 };
846
11.0k
  unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = { 0 };
847
11.0k
  char *auth_headers = NULL;
848
849
11.0k
  if(data->set.path_as_is) {
850
13
    failf(data, "Cannot use sigv4 authentication with path-as-is flag");
851
13
    return CURLE_BAD_FUNCTION_ARGUMENT;
852
13
  }
853
854
10.9k
  if(Curl_checkheaders(data, STRCONST("Authorization"))) {
855
    /* Authorization already present, Bailing out */
856
18
    return CURLE_OK;
857
18
  }
858
859
  /* we init those buffers here, so goto fail will free initialized dynbuf */
860
10.9k
  curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
861
10.9k
  curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
862
10.9k
  curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
863
10.9k
  curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
864
865
  /*
866
   * Parameters parsing
867
   * Google and Outscale use the same OSC or GOOG,
868
   * but Amazon uses AWS and AMZ for header arguments.
869
   * AWS is the default because most of non-amazon providers
870
   * are still using aws:amz as a prefix.
871
   */
872
10.9k
  line = data->set.str[STRING_AWS_SIGV4];
873
10.9k
  if(!line || !*line)
874
7.42k
    line = "aws:amz";
875
876
  /* provider0[:provider1[:region[:service]]]
877
878
     No string can be longer than N bytes of non-whitespace
879
  */
880
10.9k
  if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) {
881
22
    failf(data, "first aws-sigv4 provider cannot be empty");
882
22
    result = CURLE_BAD_FUNCTION_ARGUMENT;
883
22
    goto fail;
884
22
  }
885
10.9k
  if(curlx_str_single(&line, ':') ||
886
8.69k
     curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) {
887
2.38k
    provider1 = provider0;
888
2.38k
  }
889
8.56k
  else if(curlx_str_single(&line, ':') ||
890
1.02k
          curlx_str_until(&line, &region, MAX_SIGV4_LEN, ':') ||
891
967
          curlx_str_single(&line, ':') ||
892
8.05k
          curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) {
893
    /* nothing to do */
894
8.05k
  }
895
896
10.9k
  if(!curlx_strlen(&service)) {
897
10.4k
    const char *p = hostname;
898
10.4k
    if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') ||
899
10.4k
       curlx_str_single(&p, '.')) {
900
105
      failf(data, "aws-sigv4: service missing in parameters and hostname");
901
105
      result = CURLE_URL_MALFORMAT;
902
105
      goto fail;
903
105
    }
904
905
10.3k
    infof(data, "aws_sigv4: picked service %.*s from host",
906
10.3k
          (int)curlx_strlen(&service), curlx_str(&service));
907
908
10.3k
    if(!curlx_strlen(&region)) {
909
9.87k
      if(curlx_str_until(&p, &region, MAX_SIGV4_LEN, '.') ||
910
9.84k
         curlx_str_single(&p, '.')) {
911
53
        failf(data, "aws-sigv4: region missing in parameters and hostname");
912
53
        result = CURLE_URL_MALFORMAT;
913
53
        goto fail;
914
53
      }
915
9.82k
      infof(data, "aws_sigv4: picked region %.*s from host",
916
9.82k
            (int)curlx_strlen(&region), curlx_str(&region));
917
9.82k
    }
918
10.3k
  }
919
920
10.7k
  Curl_http_method(data, &method, &httpreq);
921
922
10.7k
  payload_hash =
923
10.7k
    parse_content_sha_hdr(data, curlx_str(&provider1),
924
10.7k
                          curlx_strlen(&provider1), &payload_hash_len);
925
926
10.7k
  if(!payload_hash) {
927
    /* AWS S3 requires a x-amz-content-sha256 header, and supports special
928
     * values like UNSIGNED-PAYLOAD */
929
10.7k
    bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") &&
930
7.26k
                      curlx_str_casecompare(&service, "s3");
931
932
10.7k
    if(sign_as_s3)
933
103
      result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1),
934
103
                                    curlx_strlen(&provider1), sha_hash,
935
103
                                    sha_hex, content_sha256_hdr);
936
10.6k
    else
937
10.6k
      result = calc_payload_hash(data, sha_hash, sha_hex);
938
10.7k
    if(result)
939
0
      goto fail;
940
941
10.7k
    payload_hash = sha_hex;
942
    /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
943
10.7k
    payload_hash_len = strlen(sha_hex);
944
10.7k
  }
945
946
10.7k
#ifdef DEBUGBUILD
947
10.7k
  {
948
10.7k
    char *force_timestamp = getenv("CURL_FORCETIME");
949
10.7k
    if(force_timestamp)
950
0
      clock = 0;
951
10.7k
    else
952
10.7k
      clock = time(NULL);
953
10.7k
  }
954
#else
955
  clock = time(NULL);
956
#endif
957
10.7k
  result = curlx_gmtime(clock, &tm);
958
10.7k
  if(result) {
959
0
    goto fail;
960
0
  }
961
10.7k
  if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
962
0
    result = CURLE_OUT_OF_MEMORY;
963
0
    goto fail;
964
0
  }
965
966
10.7k
  result = make_headers(data, hostname, timestamp,
967
10.7k
                        curlx_str(&provider1), curlx_strlen(&provider1),
968
10.7k
                        &date_header, content_sha256_hdr,
969
10.7k
                        &canonical_headers, &signed_headers);
970
10.7k
  if(result)
971
37
    goto fail;
972
973
10.7k
  if(*content_sha256_hdr) {
974
    /* make_headers() needed this without the \r\n for canonicalization */
975
103
    size_t hdrlen = strlen(content_sha256_hdr);
976
103
    DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
977
103
    memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
978
103
  }
979
980
10.7k
  memcpy(date, timestamp, sizeof(date));
981
10.7k
  date[sizeof(date) - 1] = 0;
982
983
10.7k
  result = canon_query(data->state.up.query, &canonical_query);
984
10.7k
  if(result)
985
34
    goto fail;
986
987
10.7k
  result = canon_path(data->state.up.path, strlen(data->state.up.path),
988
10.7k
                        &canonical_path,
989
10.7k
                        should_urlencode(&service));
990
10.7k
  if(result)
991
5
    goto fail;
992
10.7k
  result = CURLE_OUT_OF_MEMORY;
993
994
10.7k
  canonical_request =
995
10.7k
    curl_maprintf("%s\n" /* HTTPRequestMethod */
996
10.7k
                  "%s\n" /* CanonicalURI */
997
10.7k
                  "%s\n" /* CanonicalQueryString */
998
10.7k
                  "%s\n" /* CanonicalHeaders */
999
10.7k
                  "%s\n" /* SignedHeaders */
1000
10.7k
                  "%.*s",  /* HashedRequestPayload in hex */
1001
10.7k
                  method,
1002
10.7k
                  curlx_dyn_ptr(&canonical_path),
1003
10.7k
                  curlx_dyn_ptr(&canonical_query) ?
1004
6.47k
                  curlx_dyn_ptr(&canonical_query) : "",
1005
10.7k
                  curlx_dyn_ptr(&canonical_headers),
1006
10.7k
                  curlx_dyn_ptr(&signed_headers),
1007
10.7k
                  (int)payload_hash_len, payload_hash);
1008
10.7k
  if(!canonical_request)
1009
0
    goto fail;
1010
1011
10.7k
  infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]",
1012
10.7k
        canonical_request);
1013
1014
10.7k
  request_type = curl_maprintf("%.*s4_request",
1015
10.7k
                               (int)curlx_strlen(&provider0),
1016
10.7k
                               curlx_str(&provider0));
1017
10.7k
  if(!request_type)
1018
0
    goto fail;
1019
1020
  /* provider0 is lowercased *after* curl_maprintf() so that the buffer
1021
     can be written to */
1022
10.7k
  Curl_strntolower(request_type, request_type, curlx_strlen(&provider0));
1023
1024
10.7k
  credential_scope = curl_maprintf("%s/%.*s/%.*s/%s", date,
1025
10.7k
                                   (int)curlx_strlen(&region),
1026
10.7k
                                   curlx_str(&region),
1027
10.7k
                                   (int)curlx_strlen(&service),
1028
10.7k
                                   curlx_str(&service),
1029
10.7k
                                   request_type);
1030
10.7k
  if(!credential_scope)
1031
0
    goto fail;
1032
1033
10.7k
  if(Curl_sha256it(sha_hash, (unsigned char *)canonical_request,
1034
10.7k
                   strlen(canonical_request)))
1035
0
    goto fail;
1036
1037
10.7k
  sha256_to_hex(sha_hex, sha_hash);
1038
1039
  /*
1040
   * Google allows using RSA key instead of HMAC, so this code might change
1041
   * in the future. For now we only support HMAC.
1042
   */
1043
10.7k
  str_to_sign = curl_maprintf("%.*s4-HMAC-SHA256\n" /* Algorithm */
1044
10.7k
                              "%s\n" /* RequestDateTime */
1045
10.7k
                              "%s\n" /* CredentialScope */
1046
10.7k
                              "%s",  /* HashedCanonicalRequest in hex */
1047
10.7k
                              (int)curlx_strlen(&provider0),
1048
10.7k
                              curlx_str(&provider0),
1049
10.7k
                              timestamp,
1050
10.7k
                              credential_scope,
1051
10.7k
                              sha_hex);
1052
10.7k
  if(!str_to_sign)
1053
0
    goto fail;
1054
1055
  /* make provider0 part done uppercase */
1056
10.7k
  Curl_strntoupper(str_to_sign, curlx_str(&provider0),
1057
10.7k
                   curlx_strlen(&provider0));
1058
1059
10.7k
  infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]",
1060
10.7k
        str_to_sign);
1061
1062
10.7k
  secret = curl_maprintf("%.*s4%s", (int)curlx_strlen(&provider0),
1063
10.7k
                         curlx_str(&provider0), data->state.aptr.passwd ?
1064
9.81k
                         data->state.aptr.passwd : "");
1065
10.7k
  if(!secret)
1066
0
    goto fail;
1067
  /* make provider0 part done uppercase */
1068
10.7k
  Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0));
1069
1070
10.7k
  HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
1071
10.7k
  HMAC_SHA256(sign0, sizeof(sign0),
1072
10.7k
              curlx_str(&region), curlx_strlen(&region), sign1);
1073
10.7k
  HMAC_SHA256(sign1, sizeof(sign1),
1074
10.7k
              curlx_str(&service), curlx_strlen(&service), sign0);
1075
10.7k
  HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
1076
10.7k
  HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
1077
1078
10.7k
  sha256_to_hex(sha_hex, sign0);
1079
1080
10.7k
  infof(data, "aws_sigv4: Signature - %s", sha_hex);
1081
1082
10.7k
  auth_headers = curl_maprintf("Authorization: %.*s4-HMAC-SHA256 "
1083
10.7k
                               "Credential=%s/%s, "
1084
10.7k
                               "SignedHeaders=%s, "
1085
10.7k
                               "Signature=%s\r\n"
1086
                               /*
1087
                                * date_header is added here, only if it was not
1088
                                * user-specified (using CURLOPT_HTTPHEADER).
1089
                                * date_header includes \r\n
1090
                                */
1091
10.7k
                               "%s"
1092
10.7k
                               "%s", /* optional sha256 header includes \r\n */
1093
10.7k
                               (int)curlx_strlen(&provider0),
1094
10.7k
                               curlx_str(&provider0),
1095
10.7k
                               user,
1096
10.7k
                               credential_scope,
1097
10.7k
                               curlx_dyn_ptr(&signed_headers),
1098
10.7k
                               sha_hex,
1099
10.7k
                               date_header ? date_header : "",
1100
10.7k
                               content_sha256_hdr);
1101
10.7k
  if(!auth_headers) {
1102
0
    goto fail;
1103
0
  }
1104
  /* provider 0 uppercase */
1105
10.7k
  Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1],
1106
10.7k
                   curlx_str(&provider0), curlx_strlen(&provider0));
1107
1108
10.7k
  curlx_free(data->state.aptr.userpwd);
1109
10.7k
  data->state.aptr.userpwd = auth_headers;
1110
10.7k
  data->state.authhost.done = TRUE;
1111
10.7k
  result = CURLE_OK;
1112
1113
10.9k
fail:
1114
10.9k
  curlx_dyn_free(&canonical_query);
1115
10.9k
  curlx_dyn_free(&canonical_path);
1116
10.9k
  curlx_dyn_free(&canonical_headers);
1117
10.9k
  curlx_dyn_free(&signed_headers);
1118
10.9k
  curlx_free(canonical_request);
1119
10.9k
  curlx_free(request_type);
1120
10.9k
  curlx_free(credential_scope);
1121
10.9k
  curlx_free(str_to_sign);
1122
10.9k
  curlx_free(secret);
1123
10.9k
  curlx_free(date_header);
1124
10.9k
  return result;
1125
10.7k
}
1126
1127
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_AWS */