Coverage Report

Created: 2025-07-18 07:19

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