Coverage Report

Created: 2026-07-01 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/htslib/hfile_s3.c
Line
Count
Source
1
/*  hfile_s3.c -- Amazon S3 backend for low-level file streams.
2
3
    Copyright (C) 2015-2017, 2019-2026 Genome Research Ltd.
4
5
    Author: John Marshall <jm18@sanger.ac.uk>
6
7
Permission is hereby granted, free of charge, to any person obtaining a copy
8
of this software and associated documentation files (the "Software"), to deal
9
in the Software without restriction, including without limitation the rights
10
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
copies of the Software, and to permit persons to whom the Software is
12
furnished to do so, subject to the following conditions:
13
14
The above copyright notice and this permission notice shall be included in
15
all copies or substantial portions of the Software.
16
17
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
DEALINGS IN THE SOFTWARE.  */
24
25
#define HTS_BUILDING_LIBRARY // Enables HTSLIB_EXPORT, see htslib/hts_defs.h
26
#include <config.h>
27
28
#include <stdarg.h>
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <string.h>
32
#include <strings.h>
33
#include <time.h>
34
35
#include <errno.h>
36
#include <pthread.h>
37
38
#include "hfile_internal.h"
39
#ifdef ENABLE_PLUGINS
40
#include "version.h"
41
#endif
42
#include "htslib/hts.h"  // for hts_version() and hts_verbose
43
#include "htslib/hts_alloc.h"
44
#include "htslib/kstring.h"
45
#include "hts_time_funcs.h"
46
47
#include <curl/curl.h>
48
49
typedef struct s3_auth_data {
50
    kstring_t id;
51
    kstring_t token;
52
    kstring_t secret;
53
    kstring_t region;
54
    kstring_t canonical_query_string;
55
    kstring_t user_query_string;
56
    kstring_t host;
57
    kstring_t profile;
58
    enum {s3_auto, s3_virtual, s3_path} url_style;
59
    time_t creds_expiry_time;
60
    char *bucket;
61
    time_t auth_time;
62
    char date[40];
63
    char date_long[17];
64
    char date_short[9];
65
    kstring_t date_html;
66
    char mode;
67
    int is_v4;
68
} s3_auth_data;
69
70
typedef struct {
71
    hFILE base;
72
    CURL *curl;
73
    CURLcode ret;
74
    s3_auth_data *au;
75
    kstring_t buffer;
76
    kstring_t url;
77
    long verbose;
78
    int write;
79
    int part_size; // size for reading or writing
80
81
    kstring_t content_hash;
82
    kstring_t authorisation;
83
    kstring_t content;
84
    kstring_t date;
85
    kstring_t token;
86
    kstring_t range;
87
88
    // write variables
89
    kstring_t upload_id;
90
    kstring_t completion_message;
91
    int part_no;
92
    int aborted;
93
    size_t index;
94
    int expand;
95
96
    // read variables
97
    size_t last_read;               // last read position (remote)
98
    size_t last_read_buffer;        // last read (local buffer)
99
    int64_t file_size;              // size of the file being read
100
    int keep_going;
101
102
} hFILE_s3;
103
104
0
#define AUTH_LIFETIME 60  // Regenerate auth headers if older than this
105
0
#define CREDENTIAL_LIFETIME 60 // Seconds before expiry to reread credentials
106
107
#if defined HAVE_COMMONCRYPTO
108
109
#include <CommonCrypto/CommonHMAC.h>
110
111
#define DIGEST_BUFSIZ CC_SHA1_DIGEST_LENGTH
112
#define SHA256_DIGEST_BUFSIZE CC_SHA256_DIGEST_LENGTH
113
#define HASH_LENGTH_SHA256 (SHA256_DIGEST_BUFSIZE * 2) + 1
114
115
static size_t
116
s3_sign(unsigned char *digest, kstring_t *key, kstring_t *message)
117
{
118
    CCHmac(kCCHmacAlgSHA1, key->s, key->l, message->s, message->l, digest);
119
    return CC_SHA1_DIGEST_LENGTH;
120
}
121
122
123
static void s3_sha256(const unsigned char *in, size_t length, unsigned char *out) {
124
    CC_SHA256(in, length, out);
125
}
126
127
128
static void s3_sign_sha256(const void *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len) {
129
    CCHmac(kCCHmacAlgSHA256, key, key_len, d, n, md);
130
    *md_len = CC_SHA256_DIGEST_LENGTH;
131
}
132
133
134
#elif defined HAVE_HMAC
135
136
#include <openssl/hmac.h>
137
#include <openssl/sha.h>
138
139
#define DIGEST_BUFSIZ EVP_MAX_MD_SIZE
140
0
#define SHA256_DIGEST_BUFSIZE SHA256_DIGEST_LENGTH
141
0
#define HASH_LENGTH_SHA256 (SHA256_DIGEST_BUFSIZE * 2) + 1
142
143
static size_t
144
s3_sign(unsigned char *digest, kstring_t *key, kstring_t *message)
145
0
{
146
0
    unsigned int len;
147
0
    HMAC(EVP_sha1(), key->s, key->l,
148
0
         (unsigned char *) message->s, message->l, digest, &len);
149
0
    return len;
150
0
}
151
152
153
0
static void s3_sha256(const unsigned char *in, size_t length, unsigned char *out) {
154
0
    SHA256(in, length, out);
155
0
}
156
157
158
0
static void s3_sign_sha256(const void *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len) {
159
0
    HMAC(EVP_sha256(), key, key_len, d, n, md, md_len);
160
0
}
161
162
#else
163
#error No HMAC() routine found by configure
164
#endif
165
166
static void
167
urldecode_kput(const char *s, int len, kstring_t *str)
168
0
{
169
0
    char buf[3];
170
0
    int i = 0;
171
172
0
    while (i < len)
173
0
        if (s[i] == '%' && i+2 < len) {
174
0
            buf[0] = s[i+1], buf[1] = s[i+2], buf[2] = '\0';
175
0
            kputc(strtol(buf, NULL, 16), str);
176
0
            i += 3;
177
0
        }
178
0
        else kputc(s[i++], str);
179
0
}
180
181
182
static void base64_kput(const unsigned char *data, size_t len, kstring_t *str)
183
0
{
184
0
    static const char base64[] =
185
0
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
186
187
0
    size_t i = 0;
188
0
    unsigned x = 0;
189
0
    int bits = 0, pad = 0;
190
191
0
    while (bits || i < len) {
192
0
        if (bits < 6) {
193
0
            x <<= 8, bits += 8;
194
0
            if (i < len) x |= data[i++];
195
0
            else pad++;
196
0
        }
197
198
0
        bits -= 6;
199
0
        kputc(base64[(x >> bits) & 63], str);
200
0
    }
201
202
0
    str->l -= pad;
203
0
    kputsn("==", pad, str);
204
0
}
205
206
207
static int is_dns_compliant(const char *s0, const char *slim, int is_https)
208
0
{
209
0
    int has_nondigit = 0, len = 0;
210
0
    const char *s;
211
212
0
    for (s = s0; s < slim; len++, s++)
213
0
        if (islower_c(*s))
214
0
            has_nondigit = 1;
215
0
        else if (*s == '-') {
216
0
            has_nondigit = 1;
217
0
            if (s == s0 || s+1 == slim) return 0;
218
0
        }
219
0
        else if (isdigit_c(*s))
220
0
            ;
221
0
        else if (*s == '.') {
222
0
            if (is_https) return 0;
223
0
            if (s == s0 || ! isalnum_c(s[-1])) return 0;
224
0
            if (s+1 == slim || ! isalnum_c(s[1])) return 0;
225
0
        }
226
0
        else return 0;
227
228
0
    return has_nondigit && len >= 3 && len <= 63;
229
0
}
230
231
232
static FILE *expand_tilde_open(const char *fname, const char *mode)
233
0
{
234
0
    FILE *fp;
235
236
0
    if (strncmp(fname, "~/", 2) == 0) {
237
0
        kstring_t full_fname = { 0, 0, NULL };
238
0
        const char *home = getenv("HOME");
239
0
        if (! home) return NULL;
240
241
0
        kputs(home, &full_fname);
242
0
        kputs(&fname[1], &full_fname);
243
244
0
        fp = fopen(full_fname.s, mode);
245
0
        free(full_fname.s);
246
0
    }
247
0
    else
248
0
        fp = fopen(fname, mode);
249
250
0
    return fp;
251
0
}
252
253
static void parse_ini(const char *fname, const char *section, ...)
254
0
{
255
0
    kstring_t line = { 0, 0, NULL };
256
0
    int active = 1;  // Start active, so global properties are accepted
257
0
    char *s;
258
259
0
    FILE *fp = expand_tilde_open(fname, "r");
260
0
    if (fp == NULL) return;
261
262
0
    while (line.l = 0, kfgetline(&line, fp) >= 0)
263
0
        if (line.s[0] == '[' && (s = strchr(line.s, ']')) != NULL) {
264
0
            *s = '\0';
265
0
            active = (strcmp(&line.s[1], section) == 0);
266
0
        }
267
0
        else if (active && (s = strpbrk(line.s, ":=")) != NULL) {
268
0
            const char *key = line.s, *value = &s[1], *akey;
269
0
            va_list args;
270
271
0
            while (isspace_c(*key)) key++;
272
0
            while (s > key && isspace_c(s[-1])) s--;
273
0
            *s = '\0';
274
275
0
            while (isspace_c(*value)) value++;
276
0
            while (line.l > 0 && isspace_c(line.s[line.l-1]))
277
0
                line.s[--line.l] = '\0';
278
279
0
            va_start(args, section);
280
0
            while ((akey = va_arg(args, const char *)) != NULL) {
281
0
                kstring_t *avar = va_arg(args, kstring_t *);
282
0
                if (strcmp(key, akey) == 0) {
283
0
                    avar->l = 0;
284
0
                    kputs(value, avar);
285
0
                    break; }
286
0
            }
287
0
            va_end(args);
288
0
        }
289
290
0
    fclose(fp);
291
0
    free(line.s);
292
0
}
293
294
295
static void parse_simple(const char *fname, kstring_t *id, kstring_t *secret)
296
0
{
297
0
    kstring_t text = { 0, 0, NULL };
298
0
    char *s;
299
0
    size_t len;
300
301
0
    FILE *fp = expand_tilde_open(fname, "r");
302
0
    if (fp == NULL) return;
303
304
0
    while (kfgetline(&text, fp) >= 0)
305
0
        kputc(' ', &text);
306
0
    fclose(fp);
307
308
0
    s = text.s;
309
0
    while (isspace_c(*s)) s++;
310
0
    kputsn(s, len = strcspn(s, " \t"), id);
311
312
0
    s += len;
313
0
    while (isspace_c(*s)) s++;
314
0
    kputsn(s, strcspn(s, " \t"), secret);
315
316
0
    free(text.s);
317
0
}
318
319
320
0
static void free_auth_data(s3_auth_data *ad) {
321
0
    free(ad->profile.s);
322
0
    free(ad->id.s);
323
0
    free(ad->token.s);
324
0
    free(ad->secret.s);
325
0
    free(ad->region.s);
326
0
    free(ad->canonical_query_string.s);
327
0
    free(ad->user_query_string.s);
328
0
    free(ad->host.s);
329
0
    free(ad->bucket);
330
0
    free(ad->date_html.s);
331
0
    free(ad);
332
0
}
333
334
static time_t parse_rfc3339_date(kstring_t *datetime)
335
0
{
336
0
    int offset = 0;
337
0
    time_t when;
338
0
    int num;
339
0
    char should_be_t = '\0', timezone[10] = { '\0' };
340
0
    unsigned int year, mon, day, hour, min, sec;
341
342
0
    if (!datetime->s)
343
0
        return 0;
344
345
    // It should be possible to do this with strptime(), but it seems
346
    // to not get on with our feature definitions.
347
0
    num = sscanf(datetime->s, "%4u-%2u-%2u%c%2u:%2u:%2u%9s",
348
0
                 &year, &mon, &day, &should_be_t, &hour, &min, &sec, timezone);
349
0
    if (num < 8)
350
0
        return 0;
351
0
    if (should_be_t != 'T' && should_be_t != 't' && should_be_t != ' ')
352
0
        return 0;
353
0
    struct tm parsed = { sec, min, hour, day, mon - 1, year - 1900, 0, 0, 0 };
354
355
0
    switch (timezone[0]) {
356
0
      case 'Z':
357
0
      case 'z':
358
0
      case '\0':
359
0
          break;
360
0
      case '+':
361
0
      case '-': {
362
0
          unsigned hr_off, min_off;
363
0
          if (sscanf(timezone + 1, "%2u:%2u", &hr_off, &min_off)) {
364
0
              if (hr_off < 24 && min_off <= 60) {
365
0
                  offset = ((hr_off * 60 + min_off)
366
0
                            * (timezone[0] == '+' ? -60 : 60));
367
0
              }
368
0
          }
369
0
          break;
370
0
      }
371
0
      default:
372
0
          return 0;
373
0
    }
374
375
0
    when = hts_time_gm(&parsed);
376
0
    return when >= 0 ? when + offset : 0;
377
0
}
378
379
0
static void refresh_auth_data(s3_auth_data *ad) {
380
    // Basically a copy of the AWS_SHARED_CREDENTIALS_FILE part of
381
    // setup_auth_data(), but this only reads the authorisation parts.
382
0
    const char *v = getenv("AWS_SHARED_CREDENTIALS_FILE");
383
0
    kstring_t expiry_time = KS_INITIALIZE;
384
0
    parse_ini(v? v : "~/.aws/credentials", ad->profile.s,
385
0
              "aws_access_key_id", &ad->id,
386
0
              "aws_secret_access_key", &ad->secret,
387
0
              "aws_session_token", &ad->token,
388
0
              "expiry_time", &expiry_time);
389
0
    if (expiry_time.l) {
390
0
        ad->creds_expiry_time = parse_rfc3339_date(&expiry_time);
391
0
    }
392
0
    ks_free(&expiry_time);
393
0
}
394
395
396
/* like a escape path but for query strings '=' and '&' are untouched */
397
0
static char *escape_query(const char *qs) {
398
0
    size_t i, j = 0, length, alloced;
399
0
    char *escaped;
400
401
0
    length = strlen(qs);
402
0
    alloced = hts_add_sat2(hts_prod_sat2(length, 3), 1);
403
0
    if ((escaped = hts_malloc(alloced)) == NULL) {
404
0
        return NULL;
405
0
    }
406
407
0
    for (i = 0; i < length; i++) {
408
0
        int c = qs[i];
409
410
0
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
411
0
             c == '_' || c == '-' || c == '~' || c == '.' || c == '/' || c == '=' || c == '&') {
412
0
            escaped[j++] = c;
413
0
        } else {
414
0
            snprintf(escaped + j, alloced - j, "%%%02X", c);
415
0
            j += 3;
416
0
        }
417
0
    }
418
419
0
    escaped[j] = '\0';
420
421
0
    return escaped;
422
0
}
423
424
425
0
static char *escape_path(const char *path) {
426
0
    size_t i, j = 0, length, alloced;
427
0
    char *escaped;
428
429
0
    length = strlen(path);
430
0
    alloced = hts_add_sat2(hts_prod_sat2(length, 3), 1);
431
432
0
    if ((escaped = hts_malloc(alloced)) == NULL) {
433
0
        return NULL;
434
0
    }
435
436
0
    for (i = 0; i < length; i++) {
437
0
        int c = path[i];
438
439
0
        if (c == '?') break; // don't escape ? or beyond
440
441
0
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
442
0
             c == '_' || c == '-' || c == '~' || c == '.' || c == '/') {
443
0
            escaped[j++] = c;
444
0
        } else {
445
0
            snprintf(escaped + j, alloced - j, "%%%02X", c);
446
0
            j += 3;
447
0
        }
448
0
    }
449
450
0
    if (i != length) {
451
        // in the case of a '?' copy the rest of the path across unchanged
452
0
        strcpy(escaped + j, path + i);
453
0
    } else {
454
0
        escaped[j] = '\0';
455
0
    }
456
457
0
    return escaped;
458
0
}
459
460
461
0
static int is_escaped(const char *str) {
462
0
    const char *c = str;
463
0
    int escaped = 0;
464
0
    int needs_escape = 0;
465
466
0
    while (*c != '\0') {
467
0
        if (*c == '%' && c[1] != '\0' && c[2] != '\0') {
468
0
            if (isxdigit_c(c[1]) && isxdigit_c(c[2])) {
469
0
                escaped = 1;
470
0
                c += 3;
471
0
                continue;
472
0
            } else {
473
                // only escaped if all % signs are escaped
474
0
                escaped = 0;
475
0
            }
476
0
        }
477
0
        if (!((*c >= '0' && *c <= '9') || (*c >= 'A' && *c <= 'Z')
478
0
              || (*c >= 'a' && *c <= 'z') ||
479
0
              *c == '_' || *c == '-' || *c == '~' || *c == '.' || *c == '/')) {
480
0
            needs_escape = 1;
481
0
        }
482
0
        c++;
483
0
    }
484
485
0
    return escaped || !needs_escape;
486
0
}
487
488
489
0
static int redirect_endpoint(hFILE_s3 *fp, kstring_t *header) {
490
0
    s3_auth_data *ad = fp->au;
491
0
    kstring_t *url = &fp->url;
492
0
    char *new_region;
493
0
    char *end;
494
0
    int ret = -1;
495
496
    // get the new region from the reply header
497
0
    if ((new_region = strstr(header->s, "x-amz-bucket-region: "))) {
498
499
0
        new_region += strlen("x-amz-bucket-region: ");
500
0
        end = new_region;
501
502
0
        while (isalnum_c(*end) || ispunct_c(*end)) end++;
503
504
0
        *end = 0;
505
506
0
        if (strstr(ad->host.s, "amazonaws.com")) {
507
0
            ad->region.l = 0;
508
0
            kputs(new_region, &ad->region);
509
510
0
            ad->host.l = 0;
511
512
0
            if (ad->url_style == s3_path) {
513
                // Path style https://s3.{region-code}.amazonaws.com/{bucket-name}/{key-name}
514
0
                ksprintf(&ad->host, "s3.%s.amazonaws.com", new_region);
515
0
            } else {
516
                // Virtual https://{bucket-name}.s3.{region-code}.amazonaws.com/{key-name}
517
                // Extract the {bucket-name} from {ad->host} to include in subdomain
518
0
                kstring_t url_prefix = KS_INITIALIZE;
519
0
                kputsn(ad->host.s, strcspn(ad->host.s, "."), &url_prefix);
520
521
0
                ksprintf(&ad->host, "%s.s3.%s.amazonaws.com", url_prefix.s, new_region);
522
0
                free(url_prefix.s);
523
0
            }
524
0
            if (ad->region.l && ad->host.l) {
525
0
               int e = 0;
526
0
               url->l = 0;
527
0
               e |= kputs("https://", url) < 0;
528
0
               e |= kputs(ad->host.s, url) < 0;
529
0
               e |= kputsn(ad->bucket, strlen(ad->bucket), url) < 0;
530
531
0
               if (!e)
532
0
                   ret = 0;
533
0
            }
534
0
            if (ad->user_query_string.l) {
535
0
                kputc('?', url);
536
0
                kputsn(ad->user_query_string.s, ad->user_query_string.l, url);
537
0
            }
538
0
        }
539
0
    }
540
541
0
    if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: redirect_endpoint: return %d\n", ret);
542
543
0
    return ret;
544
0
}
545
546
static s3_auth_data * setup_auth_data(const char *s3url, const char *mode,
547
                                      int sigver, kstring_t *url)
548
0
{
549
0
    s3_auth_data *ad = calloc(1, sizeof(*ad));
550
0
    const char *bucket, *path;
551
0
    char *escaped = NULL;
552
0
    size_t url_path_pos;
553
0
    ptrdiff_t bucket_len;
554
0
    int is_https = 1, dns_compliant;
555
0
    char *query_start;
556
557
0
    if (!ad)
558
0
        return NULL;
559
0
    ad->mode = strchr(mode, 'r') ? 'r' : 'w';
560
0
    ad->url_style = s3_auto;
561
562
    // Our S3 URL format is s3[+SCHEME]://[ID[:SECRET[:TOKEN]]@]BUCKET/PATH
563
564
0
    if (s3url[2] == '+') {
565
0
        bucket = strchr(s3url, ':') + 1;
566
0
        if (bucket == NULL) {
567
0
            free(ad);
568
0
            return NULL;
569
0
        }
570
0
        kputsn(&s3url[3], bucket - &s3url[3], url);
571
0
        is_https = strncmp(url->s, "https:", 6) == 0;
572
0
    }
573
0
    else {
574
0
        kputs("https:", url);
575
0
        bucket = &s3url[3];
576
0
    }
577
0
    while (*bucket == '/') kputc(*bucket++, url);
578
579
0
    path = bucket + strcspn(bucket, "/?#@");
580
581
0
    if (*path == '@') {
582
0
        const char *colon = strpbrk(bucket, ":@");
583
0
        if (*colon != ':') {
584
0
            urldecode_kput(bucket, colon - bucket, &ad->profile);
585
0
        }
586
0
        else {
587
0
            const char *colon2 = strpbrk(&colon[1], ":@");
588
0
            urldecode_kput(bucket, colon - bucket, &ad->id);
589
0
            urldecode_kput(&colon[1], colon2 - &colon[1], &ad->secret);
590
0
            if (*colon2 == ':')
591
0
                urldecode_kput(&colon2[1], path - &colon2[1], &ad->token);
592
0
        }
593
594
0
        bucket = &path[1];
595
0
        path = bucket + strcspn(bucket, "/?#");
596
0
    }
597
0
    else {
598
        // If the URL has no ID[:SECRET]@, consider environment variables.
599
0
        const char *v;
600
0
        if ((v = getenv("AWS_ACCESS_KEY_ID")) != NULL) kputs(v, &ad->id);
601
0
        if ((v = getenv("AWS_SECRET_ACCESS_KEY")) != NULL) kputs(v, &ad->secret);
602
0
        if ((v = getenv("AWS_SESSION_TOKEN")) != NULL) kputs(v, &ad->token);
603
0
        if ((v = getenv("AWS_DEFAULT_REGION")) != NULL) kputs(v, &ad->region);
604
0
        if ((v = getenv("HTS_S3_HOST")) != NULL) kputs(v, &ad->host);
605
606
0
        if ((v = getenv("AWS_DEFAULT_PROFILE")) != NULL) kputs(v, &ad->profile);
607
0
        else if ((v = getenv("AWS_PROFILE")) != NULL) kputs(v, &ad->profile);
608
0
        else kputs("default", &ad->profile);
609
610
0
        if ((v = getenv("HTS_S3_ADDRESS_STYLE")) != NULL) {
611
0
            if (strcasecmp(v, "virtual") == 0) {
612
0
                ad->url_style = s3_virtual;
613
0
            } else if (strcasecmp(v, "path") == 0) {
614
0
                ad->url_style = s3_path;
615
0
            }
616
0
        }
617
0
    }
618
619
0
    if (ad->id.l == 0) {
620
0
        kstring_t url_style = KS_INITIALIZE;
621
0
        kstring_t expiry_time = KS_INITIALIZE;
622
0
        const char *v = getenv("AWS_SHARED_CREDENTIALS_FILE");
623
0
        parse_ini(v? v : "~/.aws/credentials", ad->profile.s,
624
0
                  "aws_access_key_id", &ad->id,
625
0
                  "aws_secret_access_key", &ad->secret,
626
0
                  "aws_session_token", &ad->token,
627
0
                  "region", &ad->region,
628
0
                  "addressing_style", &url_style,
629
0
                  "expiry_time", &expiry_time,
630
0
                  NULL);
631
632
0
        if (url_style.l) {
633
0
            if (strcmp(url_style.s, "virtual") == 0) {
634
0
                ad->url_style = s3_virtual;
635
0
            } else if (strcmp(url_style.s, "path") == 0) {
636
0
                ad->url_style = s3_path;
637
0
            } else {
638
0
                ad->url_style = s3_auto;
639
0
            }
640
0
        }
641
0
        if (expiry_time.l) {
642
            // Not a real part of the AWS configuration file, but it allows
643
            // support for short-term credentials like those for the IAM
644
            // service.  The botocore library uses the key "expiry_time"
645
            // internally for this purpose.
646
            // See https://github.com/boto/botocore/blob/develop/botocore/credentials.py
647
0
            ad->creds_expiry_time = parse_rfc3339_date(&expiry_time);
648
0
        }
649
650
0
        ks_free(&url_style);
651
0
        ks_free(&expiry_time);
652
0
    }
653
654
0
    if (ad->id.l == 0) {
655
0
        kstring_t url_style = KS_INITIALIZE;
656
0
        const char *v = getenv("HTS_S3_S3CFG");
657
0
        parse_ini(v? v : "~/.s3cfg", ad->profile.s, "access_key", &ad->id,
658
0
                  "secret_key", &ad->secret, "access_token", &ad->token,
659
0
                  "host_base", &ad->host,
660
0
                  "bucket_location", &ad->region,
661
0
                  "host_bucket", &url_style,
662
0
                  NULL);
663
664
0
        if (url_style.l) {
665
            // Conforming to s3cmd's GitHub PR#416, host_bucket without the "%(bucket)s" string
666
            // indicates use of path style adressing.
667
0
            if (strstr(url_style.s, "%(bucket)s") == NULL) {
668
0
                ad->url_style = s3_path;
669
0
            } else {
670
0
                ad->url_style = s3_auto;
671
0
            }
672
0
        }
673
674
0
        ks_free(&url_style);
675
0
    }
676
677
0
    if (ad->id.l == 0)
678
0
        parse_simple("~/.awssecret", &ad->id, &ad->secret);
679
680
681
    // if address_style is set, force the dns_compliant setting
682
0
    if (ad->url_style == s3_virtual) {
683
0
        dns_compliant = 1;
684
0
    } else if (ad->url_style == s3_path) {
685
0
        dns_compliant = 0;
686
0
    } else {
687
0
        dns_compliant = is_dns_compliant(bucket, path, is_https);
688
0
    }
689
690
0
    if (ad->host.l == 0)
691
0
        kputs("s3.amazonaws.com", &ad->host);
692
693
0
    if (!dns_compliant && ad->region.l > 0
694
0
        && strcmp(ad->host.s, "s3.amazonaws.com") == 0) {
695
        // Can avoid a redirection by including the region in the host name
696
        // (assuming the right one has been specified)
697
0
        ad->host.l = 0;
698
0
        ksprintf(&ad->host, "s3.%s.amazonaws.com", ad->region.s);
699
0
    }
700
701
0
    if (ad->region.l == 0)
702
0
        kputs("us-east-1", &ad->region);
703
704
0
    if (!is_escaped(path)) {
705
0
        escaped = escape_path(path);
706
0
        if (escaped == NULL) {
707
0
            goto error;
708
0
        }
709
0
    }
710
711
0
    bucket_len = path - bucket;
712
713
    // Use virtual hosted-style access if possible, otherwise path-style.
714
0
    if (dns_compliant) {
715
0
        size_t url_host_pos = url->l;
716
        // Append "bucket.host" to url
717
0
        kputsn_(bucket, bucket_len, url);
718
0
        kputc('.', url);
719
0
        kputsn(ad->host.s, ad->host.l, url);
720
0
        url_path_pos = url->l;
721
722
0
        if (sigver == 4) {
723
            // Copy back to ad->host to use when making the signature
724
0
            ad->host.l = 0;
725
0
            kputsn(url->s + url_host_pos, url->l - url_host_pos, &ad->host);
726
0
        }
727
0
    }
728
0
    else {
729
        // Append "host/bucket" to url
730
0
        kputsn(ad->host.s, ad->host.l, url);
731
0
        url_path_pos = url->l;
732
0
        kputc('/', url);
733
0
        kputsn(bucket, bucket_len, url);
734
0
    }
735
736
0
    kputs(escaped == NULL ? path : escaped, url);
737
738
0
    if (sigver == 4 || !dns_compliant) {
739
0
        ad->bucket = malloc(url->l - url_path_pos + 1);
740
0
        if (ad->bucket == NULL) {
741
0
            goto error;
742
0
        }
743
0
        memcpy(ad->bucket, url->s + url_path_pos, url->l - url_path_pos + 1);
744
0
        ad->is_v4 = 1;
745
0
    }
746
0
    else {
747
0
        ad->bucket = malloc(url->l - url_path_pos + bucket_len + 2);
748
0
        if (ad->bucket == NULL) {
749
0
            goto error;
750
0
        }
751
0
        ad->bucket[0] = '/';
752
0
        memcpy(ad->bucket + 1, bucket, bucket_len);
753
0
        memcpy(ad->bucket + bucket_len + 1,
754
0
               url->s + url_path_pos, url->l - url_path_pos + 1);
755
0
        ad->is_v4 = 0;
756
0
    }
757
758
    // write any query strings to its own place to use later
759
0
    if ((query_start = strchr(ad->bucket, '?'))) {
760
0
        kputs(query_start + 1, &ad->user_query_string);
761
0
        *query_start = 0;
762
0
    }
763
764
0
    free(escaped);
765
766
0
    return ad;
767
768
0
 error:
769
0
    free(escaped);
770
0
    free_auth_data(ad);
771
0
    return NULL;
772
0
}
773
774
775
0
static int v2_authorisation(hFILE_s3 *fp, char *request) {
776
0
    s3_auth_data *ad = fp->au;
777
0
    time_t now = time(NULL);
778
779
0
#ifdef HAVE_GMTIME_R
780
0
    struct tm tm_buffer;
781
0
    struct tm *tm = gmtime_r(&now, &tm_buffer);
782
#else
783
    struct tm *tm = gmtime(&now);
784
#endif
785
786
0
    kstring_t message = KS_INITIALIZE;
787
0
    unsigned char digest[DIGEST_BUFSIZ];
788
0
    size_t digest_len;
789
790
0
    if (ad->creds_expiry_time > 0
791
0
        && ad->creds_expiry_time - now < CREDENTIAL_LIFETIME) {
792
0
        refresh_auth_data(ad);
793
0
    }
794
795
    // date format between v2 and v4 is different.
796
797
0
    strftime(ad->date, sizeof(ad->date), "Date: %a, %d %b %Y %H:%M:%S GMT", tm);
798
799
0
    kputs(ad->date, &fp->date);
800
801
0
    if (!ad->id.l || !ad->secret.l) {
802
0
        ad->auth_time = now;
803
0
        return 0;
804
0
    }
805
806
0
    if (ksprintf(&message, "%s\n\n\n%s\n%s%s%s%s",
807
0
                 request, ad->date + 6,
808
0
                 ad->token.l ? "x-amz-security-token:" : "",
809
0
                 ad->token.l ? ad->token.s : "",
810
0
                 ad->token.l ? "\n" : "",
811
0
                 ad->bucket) < 0) {
812
0
        return -1;
813
0
    }
814
815
0
    digest_len = s3_sign(digest, &ad->secret, &message);
816
817
0
    if (ksprintf(&fp->authorisation, "Authorization: AWS %s:", ad->id.s) < 0)
818
0
        goto fail;
819
820
0
    base64_kput(digest, digest_len, &fp->authorisation);
821
822
0
    free(message.s);
823
0
    ad->auth_time = now;
824
0
    return 0;
825
826
0
 fail:
827
0
    free(message.s);
828
0
    return -1;
829
0
}
830
831
/***************************************************************
832
833
AWS S3 sig version 4 writing code
834
835
****************************************************************/
836
837
0
static void hash_string(char *in, size_t length, char *out, size_t out_len) {
838
0
    unsigned char hashed[SHA256_DIGEST_BUFSIZE];
839
0
    int i, j;
840
841
0
    s3_sha256((const unsigned char *)in, length, hashed);
842
843
0
    for (i = 0, j = 0; i < SHA256_DIGEST_BUFSIZE; i++, j+= 2) {
844
0
        snprintf(out + j, out_len - j, "%02x", hashed[i]);
845
0
    }
846
0
}
847
848
849
0
static int make_signature(s3_auth_data *ad, kstring_t *string_to_sign, char *signature_string, size_t sig_string_len) {
850
0
    unsigned char date_key[SHA256_DIGEST_BUFSIZE];
851
0
    unsigned char date_region_key[SHA256_DIGEST_BUFSIZE];
852
0
    unsigned char date_region_service_key[SHA256_DIGEST_BUFSIZE];
853
0
    unsigned char signing_key[SHA256_DIGEST_BUFSIZE];
854
0
    unsigned char signature[SHA256_DIGEST_BUFSIZE];
855
856
0
    const unsigned char service[] = "s3";
857
0
    const unsigned char request[] = "aws4_request";
858
859
0
    kstring_t secret_access_key = KS_INITIALIZE;
860
0
    unsigned int len;
861
0
    unsigned int i, j;
862
863
0
    ksprintf(&secret_access_key, "AWS4%s", ad->secret.s);
864
865
0
    if (secret_access_key.l == 0) {
866
0
        return -1;
867
0
    }
868
869
0
    s3_sign_sha256(secret_access_key.s, secret_access_key.l, (const unsigned char *)ad->date_short, strlen(ad->date_short), date_key, &len);
870
0
    s3_sign_sha256(date_key, len, (const unsigned char *)ad->region.s, ad->region.l, date_region_key, &len);
871
0
    s3_sign_sha256(date_region_key, len, service, 2, date_region_service_key, &len);
872
0
    s3_sign_sha256(date_region_service_key, len, request, 12, signing_key, &len);
873
0
    s3_sign_sha256(signing_key, len, (const unsigned char *)string_to_sign->s, string_to_sign->l, signature, &len);
874
875
0
    for (i = 0, j = 0; i < len; i++, j+= 2) {
876
0
        snprintf(signature_string + j, sig_string_len - j, "%02x", signature[i]);
877
0
    }
878
879
0
    ks_free(&secret_access_key);
880
881
0
    return 0;
882
0
}
883
884
885
0
static int make_authorisation(s3_auth_data *ad, char *http_request, char *content, kstring_t *auth) {
886
0
    kstring_t signed_headers = KS_INITIALIZE;
887
0
    kstring_t canonical_headers = KS_INITIALIZE;
888
0
    kstring_t canonical_request = KS_INITIALIZE;
889
0
    kstring_t scope = KS_INITIALIZE;
890
0
    kstring_t string_to_sign = KS_INITIALIZE;
891
0
    char cr_hash[HASH_LENGTH_SHA256];
892
0
    char signature_string[HASH_LENGTH_SHA256];
893
0
    int ret = -1;
894
895
0
    if (!ad->id.l || !ad->secret.l) {
896
0
        return 0;
897
0
    }
898
899
0
    if (!ad->token.l) {
900
0
        kputs("host;x-amz-content-sha256;x-amz-date", &signed_headers);
901
0
    } else {
902
0
        kputs("host;x-amz-content-sha256;x-amz-date;x-amz-security-token", &signed_headers);
903
0
    }
904
905
0
    if (signed_headers.l == 0) {
906
0
        return -1;
907
0
    }
908
909
910
0
    if (!ad->token.l) {
911
0
        ksprintf(&canonical_headers, "host:%s\nx-amz-content-sha256:%s\nx-amz-date:%s\n",
912
0
        ad->host.s, content, ad->date_long);
913
0
    } else {
914
0
        ksprintf(&canonical_headers, "host:%s\nx-amz-content-sha256:%s\nx-amz-date:%s\nx-amz-security-token:%s\n",
915
0
        ad->host.s, content, ad->date_long, ad->token.s);
916
0
    }
917
918
0
    if (canonical_headers.l == 0) {
919
0
        goto cleanup;
920
0
    }
921
922
    // bucket == canonical_uri
923
0
    ksprintf(&canonical_request, "%s\n%s\n%s\n%s\n%s\n%s",
924
0
        http_request, ad->bucket, ad->canonical_query_string.s,
925
0
        canonical_headers.s, signed_headers.s, content);
926
927
0
    if (canonical_request.l == 0) {
928
0
        goto cleanup;
929
0
    }
930
931
0
    hash_string(canonical_request.s, canonical_request.l, cr_hash, sizeof(cr_hash));
932
933
0
    ksprintf(&scope, "%s/%s/s3/aws4_request", ad->date_short, ad->region.s);
934
935
0
    if (scope.l == 0) {
936
0
        goto cleanup;
937
0
    }
938
939
0
    ksprintf(&string_to_sign, "AWS4-HMAC-SHA256\n%s\n%s\n%s", ad->date_long, scope.s, cr_hash);
940
941
0
    if (string_to_sign.l == 0) {
942
0
        goto cleanup;
943
0
    }
944
945
0
    if (make_signature(ad, &string_to_sign, signature_string, sizeof(signature_string))) {
946
0
        goto cleanup;
947
0
    }
948
949
0
    ksprintf(auth, "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request,SignedHeaders=%s,Signature=%s",
950
0
                ad->id.s, ad->date_short, ad->region.s, signed_headers.s, signature_string);
951
952
0
    if (auth->l == 0) {
953
0
        goto cleanup;
954
0
    }
955
956
0
    ret = 0;
957
958
0
 cleanup:
959
0
    ks_free(&signed_headers);
960
0
    ks_free(&canonical_headers);
961
0
    ks_free(&canonical_request);
962
0
    ks_free(&scope);
963
0
    ks_free(&string_to_sign);
964
965
0
    return ret;
966
0
}
967
968
969
0
static int update_time(s3_auth_data *ad, time_t now) {
970
0
    int ret = -1;
971
0
#ifdef HAVE_GMTIME_R
972
0
    struct tm tm_buffer;
973
0
    struct tm *tm = gmtime_r(&now, &tm_buffer);
974
#else
975
    struct tm *tm = gmtime(&now);
976
#endif
977
978
0
    if (now - ad->auth_time > AUTH_LIFETIME) {
979
        // update timestamp
980
0
        ad->auth_time = now;
981
982
0
        if (strftime(ad->date_long, 17, "%Y%m%dT%H%M%SZ", tm) != 16) {
983
0
            return -1;
984
0
        }
985
986
0
        if (strftime(ad->date_short, 9, "%Y%m%d", tm) != 8) {
987
0
            return -1;
988
0
        }
989
990
0
        ad->date_html.l = 0;
991
0
        ksprintf(&ad->date_html, "x-amz-date: %s", ad->date_long);
992
0
    }
993
994
0
    if (ad->date_html.l) ret = 0;
995
996
0
    return ret;
997
0
}
998
999
1000
0
static int query_cmp(const void *p1, const void *p2) {
1001
0
    char **q1 = (char **)p1;
1002
0
    char **q2 = (char **)p2;
1003
1004
0
    return strcmp(*q1, *q2);
1005
0
}
1006
1007
1008
/* Query strings must be in alphabetical order for authorisation */
1009
1010
0
static int order_query_string(kstring_t *qs) {
1011
0
    int *query_offset = NULL;
1012
0
    int num_queries, i;
1013
0
    char **queries = NULL;
1014
0
    kstring_t ordered = KS_INITIALIZE;
1015
0
    char *escaped = NULL;
1016
0
    int ret = -1;
1017
1018
0
    if ((query_offset = ksplit(qs, '&', &num_queries)) == NULL) {
1019
0
        return -1;
1020
0
    }
1021
1022
0
    if ((queries = hts_malloc_p(sizeof(char*), num_queries)) == NULL)
1023
0
        goto err;
1024
1025
0
    for (i = 0; i < num_queries; i++) {
1026
0
        queries[i] = qs->s + query_offset[i];
1027
0
    }
1028
1029
0
    qsort(queries, num_queries, sizeof(char *), query_cmp);
1030
1031
0
    for (i = 0; i < num_queries; i++) {
1032
0
        if (i) {
1033
0
            kputs("&", &ordered);
1034
0
        }
1035
1036
0
        kputs(queries[i], &ordered);
1037
0
    }
1038
1039
0
    if ((escaped = escape_query(ordered.s)) == NULL)
1040
0
        goto err;
1041
1042
0
    qs->l = 0;
1043
0
    kputs(escaped, qs);
1044
1045
0
    ret = 0;
1046
0
 err:
1047
0
    free(ordered.s);
1048
0
    free(queries);
1049
0
    free(query_offset);
1050
0
    free(escaped);
1051
1052
0
    return ret;
1053
0
}
1054
1055
1056
0
static int v4_authorisation(hFILE_s3 *fp, char *request, kstring_t *content, char *cqs, int uqs) {
1057
0
    s3_auth_data *ad = fp->au;
1058
0
    char content_hash[HASH_LENGTH_SHA256];
1059
0
    time_t now;
1060
1061
0
    now = time(NULL);
1062
1063
0
    if (update_time(ad, now)) {
1064
0
        return -1;
1065
0
    }
1066
1067
0
    if (ad->creds_expiry_time > 0
1068
0
        && ad->creds_expiry_time - now < CREDENTIAL_LIFETIME) {
1069
0
        refresh_auth_data(ad);
1070
0
    }
1071
1072
0
    if (content) {
1073
0
        hash_string(content->s, content->l, content_hash, sizeof(content_hash));
1074
0
    } else {
1075
        // empty hash
1076
0
        hash_string("", 0, content_hash, sizeof(content_hash));
1077
0
    }
1078
1079
0
    ad->canonical_query_string.l = 0;
1080
1081
0
    if (cqs) {
1082
0
        kputs(cqs, &ad->canonical_query_string);
1083
1084
        /* add a user provided query string, normally only useful on upload initiation */
1085
0
        if (uqs) {
1086
0
            kputs("&", &ad->canonical_query_string);
1087
0
            kputs(ad->user_query_string.s, &ad->canonical_query_string);
1088
1089
0
            if (order_query_string(&ad->canonical_query_string)) {
1090
0
                return -1;
1091
0
            }
1092
0
        }
1093
0
    }
1094
1095
0
    if (make_authorisation(ad, request, content_hash, &fp->authorisation)) {
1096
0
        return -1;
1097
0
    }
1098
1099
0
    kputs(ad->date_html.s, &fp->date);
1100
0
    kputsn(content_hash, HASH_LENGTH_SHA256, &fp->content_hash);
1101
1102
0
    if (fp->date.l == 0 || fp->content_hash.l == 0) {
1103
0
        return -1;
1104
0
    }
1105
1106
0
    if (ad->token.l) {
1107
0
        ksprintf(&fp->token, "x-amz-security-token: %s", ad->token.s);
1108
0
    }
1109
1110
0
    return 0;
1111
0
}
1112
1113
0
static int set_region(s3_auth_data *ad, kstring_t *region) {
1114
0
    ad->region.l = 0;
1115
0
    return kputsn(region->s, region->l, &ad->region) < 0;
1116
0
}
1117
1118
//
1119
// Writing and reading handling
1120
//
1121
1122
// Some common code
1123
1124
0
#define S3_MOVED_PERMANENTLY 301
1125
0
#define S3_TEMPORARY_REDIRECT 307
1126
0
#define S3_BAD_REQUEST 400
1127
1128
static struct {
1129
    kstring_t useragent;
1130
    CURLSH *share;
1131
    pthread_mutex_t share_lock;
1132
} curl = { { 0, 0, NULL }, NULL, PTHREAD_MUTEX_INITIALIZER };
1133
1134
static void share_lock(CURL *handle, curl_lock_data data,
1135
1
                       curl_lock_access access, void *userptr) {
1136
1
    pthread_mutex_lock(&curl.share_lock);
1137
1
}
1138
1139
1
static void share_unlock(CURL *handle, curl_lock_data data, void *userptr) {
1140
1
    pthread_mutex_unlock(&curl.share_lock);
1141
1
}
1142
1143
1144
0
static void initialise_authorisation_values(hFILE_s3 *fp) {
1145
0
    ks_initialize(&fp->content_hash);
1146
0
    ks_initialize(&fp->authorisation);
1147
0
    ks_initialize(&fp->content);
1148
0
    ks_initialize(&fp->date);
1149
0
    ks_initialize(&fp->token);
1150
0
    ks_initialize(&fp->range);
1151
0
}
1152
1153
1154
0
static void clear_authorisation_values(hFILE_s3 *fp) {
1155
0
    ks_clear(&fp->content_hash);
1156
0
    ks_clear(&fp->authorisation);
1157
0
    ks_clear(&fp->content);
1158
0
    ks_clear(&fp->date);
1159
0
    ks_clear(&fp->token);
1160
0
    ks_clear(&fp->range);
1161
0
}
1162
1163
1164
0
static void free_authorisation_values(hFILE_s3 *fp) {
1165
0
    ks_free(&fp->content_hash);
1166
0
    ks_free(&fp->authorisation);
1167
0
    ks_free(&fp->content);
1168
0
    ks_free(&fp->date);
1169
0
    ks_free(&fp->token);
1170
0
    ks_free(&fp->range);
1171
0
}
1172
1173
/* As the response text is case insensitive we need a version of strstr that
1174
   is also case insensitive.  The response is small so no need to get too
1175
   complicated on the string search.
1176
*/
1177
0
static char *stristr(char *haystack, char *needle) {
1178
1179
0
    while (*haystack) {
1180
0
        char *h = haystack;
1181
0
        char *n = needle;
1182
1183
0
        while (toupper_c(*h) == toupper_c(*n)) {
1184
0
            h++, n++;
1185
0
            if (!*h || !*n) break;
1186
0
        }
1187
1188
0
        if (!*n) break;
1189
1190
0
        haystack++;
1191
0
    }
1192
1193
0
    if (!*haystack) return NULL;
1194
1195
0
    return haystack;
1196
0
}
1197
1198
1199
0
static int get_entry(char *in, char *start_tag, char *end_tag, kstring_t *out) {
1200
0
    char *start;
1201
0
    char *end;
1202
1203
0
    if (!in) {
1204
0
        return EOF;
1205
0
    }
1206
1207
0
    start = stristr(in, start_tag);
1208
0
    if (!start) return EOF;
1209
1210
0
    start += strlen(start_tag);
1211
0
    end = stristr(start, end_tag);
1212
1213
0
    if (!end) return EOF;
1214
1215
0
    return kputsn(start, end - start, out);
1216
0
}
1217
1218
1219
0
static int report_s3_error(kstring_t *body, long resp_code) {
1220
0
    kstring_t entry = KS_INITIALIZE;
1221
1222
0
    if (get_entry(body->s, "<Code>", "</Code>", &entry) == EOF) {
1223
0
        return -1;
1224
0
    }
1225
1226
0
    fprintf(stderr, "hfile_s3: S3 error %ld: %s\n", resp_code, entry.s);
1227
1228
0
    ks_clear(&entry);
1229
1230
0
    if (get_entry(body->s, "<Message>", "</Message>", &entry) == EOF) {
1231
0
        return -1;
1232
0
    }
1233
1234
0
    if (entry.l)
1235
0
        fprintf(stderr, "%s\n", entry.s);
1236
1237
0
    ks_free(&entry);
1238
1239
0
    return 0;
1240
0
}
1241
1242
1243
static int http_status_errno(int status)
1244
0
{
1245
0
    if (hts_verbose >= HTS_LOG_INFO)
1246
0
        fprintf(stderr, "hfile_s3: setting errno from HTTP status code %d\n", status);
1247
1248
0
    if (status >= 500)
1249
0
        switch (status) {
1250
0
        case 501: return ENOSYS;
1251
0
        case 503: return EBUSY;
1252
0
        case 504: return ETIMEDOUT;
1253
0
        default:  return EIO;
1254
0
        }
1255
0
    else if (status >= 400)
1256
0
        switch (status) {
1257
0
        case 401: return EPERM;
1258
0
        case 403: return EACCES;
1259
0
        case 404: return ENOENT;
1260
0
        case 405: return EROFS;
1261
0
        case 407: return EPERM;
1262
0
        case 408: return ETIMEDOUT;
1263
0
        case 410: return ENOENT;
1264
0
        default:  return EINVAL;
1265
0
        }
1266
0
    else if (status >= 300)
1267
0
        return EIO;
1268
0
    else return 0;
1269
0
}
1270
1271
1272
static int easy_errno(CURL *curl, CURLcode err)
1273
0
{
1274
0
    long lval;
1275
1276
0
    if (hts_verbose >= HTS_LOG_INFO)
1277
0
        fprintf(stderr, "hfile_s3: setting errno from libcurl error code %d (%s)\n",
1278
0
                (int) err, curl_easy_strerror(err));
1279
1280
0
    switch (err) {
1281
0
    case CURLE_OK:
1282
0
        return 0;
1283
1284
0
    case CURLE_UNSUPPORTED_PROTOCOL:
1285
0
    case CURLE_URL_MALFORMAT:
1286
0
        return EINVAL;
1287
1288
0
#if LIBCURL_VERSION_NUM >= 0x071505
1289
0
    case CURLE_NOT_BUILT_IN:
1290
0
        return ENOSYS;
1291
0
#endif
1292
1293
0
    case CURLE_COULDNT_RESOLVE_PROXY:
1294
0
    case CURLE_COULDNT_RESOLVE_HOST:
1295
0
    case CURLE_FTP_CANT_GET_HOST:
1296
0
        return EDESTADDRREQ; // Lookup failure
1297
1298
0
    case CURLE_COULDNT_CONNECT:
1299
0
    case CURLE_SEND_ERROR:
1300
0
    case CURLE_RECV_ERROR:
1301
0
        if (curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &lval) == CURLE_OK)
1302
0
            return lval;
1303
0
        else
1304
0
            return ECONNABORTED;
1305
1306
0
    case CURLE_REMOTE_ACCESS_DENIED:
1307
0
    case CURLE_LOGIN_DENIED:
1308
0
    case CURLE_TFTP_PERM:
1309
0
        return EACCES;
1310
1311
0
    case CURLE_PARTIAL_FILE:
1312
0
        return EPIPE;
1313
1314
0
    case CURLE_HTTP_RETURNED_ERROR:
1315
0
        if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &lval) == CURLE_OK)
1316
0
            return http_status_errno(lval);
1317
0
        else
1318
0
            return EIO;
1319
1320
0
    case CURLE_OUT_OF_MEMORY:
1321
0
        return ENOMEM;
1322
1323
0
    case CURLE_OPERATION_TIMEDOUT:
1324
0
        return ETIMEDOUT;
1325
1326
0
    case CURLE_RANGE_ERROR:
1327
0
        return ESPIPE;
1328
1329
0
    case CURLE_SSL_CONNECT_ERROR:
1330
0
        return ECONNABORTED;
1331
1332
0
    case CURLE_FILE_COULDNT_READ_FILE:
1333
0
    case CURLE_TFTP_NOTFOUND:
1334
0
        return ENOENT;
1335
1336
0
    case CURLE_TOO_MANY_REDIRECTS:
1337
0
        return ELOOP;
1338
1339
0
    case CURLE_FILESIZE_EXCEEDED:
1340
0
        return EFBIG;
1341
1342
0
    case CURLE_REMOTE_DISK_FULL:
1343
0
        return ENOSPC;
1344
1345
0
    case CURLE_REMOTE_FILE_EXISTS:
1346
0
        return EEXIST;
1347
1348
0
    default:
1349
0
        return EIO;
1350
0
    }
1351
0
}
1352
1353
1354
0
static void initialise_local(hFILE_s3 *fp) {
1355
0
    ks_initialize(&fp->buffer);
1356
0
    ks_initialize(&fp->url);
1357
0
    ks_initialize(&fp->upload_id);           // write only
1358
0
    ks_initialize(&fp->completion_message);  // write only
1359
0
}
1360
1361
1362
0
static void cleanup_local(hFILE_s3 *fp) {
1363
0
    ks_free(&fp->buffer);
1364
0
    ks_free(&fp->url);
1365
0
    ks_free(&fp->upload_id);
1366
0
    ks_free(&fp->completion_message);
1367
0
    curl_easy_cleanup(fp->curl);
1368
0
    free_authorisation_values(fp);
1369
0
}
1370
1371
1372
0
static void cleanup(hFILE_s3 *fp) {
1373
    // free up authorisation data
1374
0
    free_auth_data(fp->au);
1375
0
    cleanup_local(fp);
1376
0
}
1377
1378
0
static size_t response_callback(void *contents, size_t size, size_t nmemb, void *userp) {
1379
0
    size_t realsize = size * nmemb;
1380
0
    kstring_t *resp = (kstring_t *)userp;
1381
1382
0
    if (kputsn((const char *)contents, realsize, resp) == EOF) {
1383
0
        return 0;
1384
0
    }
1385
1386
0
    return realsize;
1387
0
}
1388
1389
1390
0
static int add_header(struct curl_slist **head, char *value) {
1391
0
    int err = 0;
1392
0
    struct curl_slist *tmp;
1393
1394
0
    if ((tmp = curl_slist_append(*head, value)) == NULL) {
1395
0
        err = 1;
1396
0
    } else {
1397
0
        *head = tmp;
1398
0
    }
1399
1400
0
    return err;
1401
0
}
1402
1403
1404
static struct curl_slist *set_html_headers(hFILE_s3 *fp, kstring_t *auth, kstring_t *date,
1405
0
                 kstring_t *content, kstring_t *token, kstring_t *range) {
1406
0
    struct curl_slist *headers = NULL;
1407
0
    CURLcode cret;
1408
0
    int err = 0;
1409
1410
    /* The next two lines have the effect of preventing curl from
1411
       adding these headers.  If they exist it can lead to conflicts
1412
       in the signature calculations (not present in all S3 systems).
1413
    */
1414
0
    err = add_header(&headers, "Content-Type:");
1415
0
    err |= add_header(&headers, "Expect:");
1416
1417
0
    if (err) goto error;
1418
1419
0
    if (auth->l)
1420
0
        if ((err = add_header(&headers, auth->s)))
1421
0
            goto error;
1422
1423
0
    if ((err = add_header(&headers, date->s)))
1424
0
        goto error;
1425
1426
0
    if (content->l)
1427
0
        if ((err = add_header(&headers, content->s)))
1428
0
            goto error;
1429
1430
0
    if (range)
1431
0
        if ((err = add_header(&headers, range->s)))
1432
0
            goto error;
1433
1434
0
    if (token->l)
1435
0
        if ((err = add_header(&headers, token->s)))
1436
0
            goto error;
1437
1438
0
    cret = curl_easy_setopt(fp->curl, CURLOPT_HTTPHEADER, headers);
1439
0
    if (cret != CURLE_OK) {
1440
0
        err = 1;
1441
0
        errno = easy_errno(fp->curl, cret);
1442
0
        goto error;
1443
0
    }
1444
1445
0
error:
1446
1447
0
    if (err) {
1448
0
        curl_slist_free_all(headers);
1449
0
        headers = NULL;
1450
0
    }
1451
1452
0
    return headers;
1453
0
}
1454
1455
1456
/*
1457
1458
S3 Multipart Upload
1459
-------------------
1460
1461
There are several steps in the Mulitipart upload.
1462
1463
1464
1) Initiate Upload
1465
------------------
1466
1467
Initiate the upload and get an upload ID.  This ID is used in all other steps.
1468
1469
1470
2) Upload Part
1471
--------------
1472
1473
Upload a part of the data.  5Mb minimum part size (except for the last part).
1474
Each part is numbered and a successful upload returns an Etag header value that
1475
needs to used for the completion step.
1476
1477
Step repeated till all data is uploaded.
1478
1479
1480
3) Completion
1481
-------------
1482
1483
Complete the upload by sending all the part numbers along with their associated
1484
Etag values.
1485
1486
1487
Optional - Abort
1488
----------------
1489
1490
If something goes wrong this instructs the server to delete all the partial
1491
uploads and abandon the upload process.
1492
*/
1493
1494
/*
1495
   This is the writing code.
1496
*/
1497
1498
0
#define MINIMUM_S3_WRITE_SIZE 5242880
1499
1500
// Lets the part memory size grow to about 1Gb giving a 2.5Tb max file size.
1501
// Max. parts allowed by AWS is 10000, so use ceil(10000.0/9.0)
1502
0
#define EXPAND_ON 1112
1503
1504
1505
1506
/*
1507
    The partially uploaded file will hang around unless the delete command is sent.
1508
*/
1509
0
static int abort_upload(hFILE_s3 *fp) {
1510
0
    kstring_t url = KS_INITIALIZE;
1511
0
    kstring_t canonical_query_string = KS_INITIALIZE;
1512
0
    int ret = -1, save_errno;
1513
0
    struct curl_slist *headers = NULL;
1514
0
    char http_request[] = "DELETE";
1515
0
    CURLcode err;
1516
1517
0
    save_errno = errno; // keep the errno that caused the need to abort
1518
1519
0
    clear_authorisation_values(fp);
1520
1521
0
    if (ksprintf(&canonical_query_string, "uploadId=%s", fp->upload_id.s) < 0) {
1522
0
        goto out;
1523
0
    }
1524
1525
0
    if (v4_authorisation(fp,  http_request, NULL, canonical_query_string.s, 0) != 0) {
1526
0
        goto out;
1527
0
    }
1528
1529
0
    if (ksprintf(&url, "%s?%s", fp->url.s, canonical_query_string.s) < 0) {
1530
0
        goto out;
1531
0
    }
1532
1533
0
    if (ksprintf(&fp->content, "x-amz-content-sha256: %s", fp->content_hash.s) < 0) {
1534
0
        goto out;
1535
0
    }
1536
1537
0
    curl_easy_reset(fp->curl);
1538
1539
0
    err = curl_easy_setopt(fp->curl, CURLOPT_CUSTOMREQUEST, http_request);
1540
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_USERAGENT, curl.useragent.s);
1541
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_URL, url.s);
1542
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_VERBOSE, fp->verbose);
1543
1544
0
    if (err != CURLE_OK) {
1545
0
        errno = EINVAL;
1546
0
        goto out;
1547
0
    }
1548
1549
0
    headers = set_html_headers(fp, &fp->authorisation, &fp->date, &fp->content, &fp->token, NULL);
1550
1551
0
    if (!headers)
1552
0
        goto out;
1553
1554
0
    fp->ret = curl_easy_perform(fp->curl);
1555
1556
0
    if (fp->ret == CURLE_OK) {
1557
0
        ret = 0;
1558
0
    } else {
1559
0
        errno = easy_errno(fp->curl, fp->ret);
1560
0
    }
1561
1562
0
 out:
1563
0
    ks_free(&url);
1564
0
    ks_free(&canonical_query_string);
1565
0
    curl_slist_free_all(headers);
1566
1567
0
    fp->aborted = 1;
1568
0
    cleanup(fp);
1569
1570
0
    errno = save_errno;
1571
0
    return ret;
1572
0
}
1573
1574
1575
0
static int complete_upload(hFILE_s3 *fp, kstring_t *resp) {
1576
0
    kstring_t url = KS_INITIALIZE;
1577
0
    kstring_t canonical_query_string = KS_INITIALIZE;
1578
0
    int ret = -1;
1579
0
    struct curl_slist *headers = NULL;
1580
0
    char http_request[] = "POST";
1581
0
    CURLcode err;
1582
1583
0
    clear_authorisation_values(fp);
1584
1585
0
    if (ksprintf(&canonical_query_string, "uploadId=%s", fp->upload_id.s) < 0) {
1586
0
        return -1;
1587
0
    }
1588
1589
    // finish off the completion reply
1590
0
    if (kputs("</CompleteMultipartUpload>\n", &fp->completion_message) < 0) {
1591
0
        goto out;
1592
0
    }
1593
1594
0
    if (v4_authorisation(fp,  http_request, &fp->completion_message, canonical_query_string.s, 0) != 0) {
1595
0
        goto out;
1596
0
    }
1597
1598
0
    if (ksprintf(&url, "%s?%s", fp->url.s, canonical_query_string.s) < 0) {
1599
0
        goto out;
1600
0
    }
1601
1602
0
    if (ksprintf(&fp->content, "x-amz-content-sha256: %s", fp->content_hash.s) < 0) {
1603
0
        goto out;
1604
0
    }
1605
1606
0
    curl_easy_reset(fp->curl);
1607
1608
0
    err = curl_easy_setopt(fp->curl, CURLOPT_POST, 1L);
1609
1610
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_POSTFIELDS, fp->completion_message.s);
1611
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_POSTFIELDSIZE, (long) fp->completion_message.l);
1612
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEFUNCTION, response_callback);
1613
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEDATA, (void *)resp);
1614
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_URL, url.s);
1615
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_USERAGENT, curl.useragent.s);
1616
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_VERBOSE, fp->verbose);
1617
1618
0
    if (err != CURLE_OK) {
1619
0
        errno = EINVAL;
1620
0
        goto out;
1621
0
    }
1622
1623
0
    headers = set_html_headers(fp, &fp->authorisation, &fp->date, &fp->content, &fp->token, NULL);
1624
1625
0
    if (!headers)
1626
0
        goto out;
1627
1628
0
    fp->ret = curl_easy_perform(fp->curl);
1629
1630
0
    if (fp->ret == CURLE_OK) {
1631
0
        ret = 0;
1632
0
    } else {
1633
0
        errno = easy_errno(fp->curl, fp->ret);
1634
0
    }
1635
1636
0
 out:
1637
0
    ks_free(&url);
1638
0
    ks_free(&canonical_query_string);
1639
0
    curl_slist_free_all(headers);
1640
1641
0
    return ret;
1642
0
}
1643
1644
1645
0
static size_t upload_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
1646
0
    size_t realsize = size * nmemb;
1647
0
    hFILE_s3 *fp = (hFILE_s3 *)stream;
1648
0
    size_t read_length;
1649
1650
0
    if (realsize > (fp->buffer.l - fp->index)) {
1651
0
        read_length = fp->buffer.l - fp->index;
1652
0
    } else {
1653
0
        read_length = realsize;
1654
0
    }
1655
1656
0
    memcpy(ptr, fp->buffer.s + fp->index, read_length);
1657
0
    fp->index += read_length;
1658
1659
0
    return read_length;
1660
0
}
1661
1662
1663
0
static int upload_part(hFILE_s3 *fp, kstring_t *resp) {
1664
0
    kstring_t url = KS_INITIALIZE;
1665
0
    kstring_t canonical_query_string = KS_INITIALIZE;
1666
0
    int ret = -1;
1667
0
    struct curl_slist *headers = NULL;
1668
0
    char http_request[] = "PUT";
1669
0
    CURLcode err;
1670
1671
0
    clear_authorisation_values(fp);
1672
1673
0
    if (ksprintf(&canonical_query_string, "partNumber=%d&uploadId=%s", fp->part_no, fp->upload_id.s) < 0) {
1674
0
        return -1;
1675
0
    }
1676
1677
0
    if (v4_authorisation(fp, http_request, &fp->buffer, canonical_query_string.s, 0) != 0) {
1678
0
        goto out;
1679
0
    }
1680
1681
0
    if (ksprintf(&url, "%s?%s", fp->url.s, canonical_query_string.s) < 0) {
1682
0
        goto out;
1683
0
    }
1684
1685
0
    fp->index = 0;
1686
0
    if (ksprintf(&fp->content, "x-amz-content-sha256: %s", fp->content_hash.s) < 0) {
1687
0
        goto out;
1688
0
    }
1689
1690
0
    curl_easy_reset(fp->curl);
1691
1692
0
    err = curl_easy_setopt(fp->curl, CURLOPT_UPLOAD, 1L);
1693
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_READFUNCTION, upload_callback);
1694
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_READDATA, fp);
1695
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fp->buffer.l);
1696
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERFUNCTION, response_callback);
1697
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERDATA, (void *)resp);
1698
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_URL, url.s);
1699
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_USERAGENT, curl.useragent.s);
1700
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_VERBOSE, fp->verbose);
1701
1702
0
    if (err != CURLE_OK) {
1703
0
        errno = EINVAL;
1704
0
        goto out;
1705
0
    }
1706
1707
0
    headers = set_html_headers(fp, &fp->authorisation, &fp->date, &fp->content, &fp->token, NULL);
1708
1709
0
    if (!headers)
1710
0
        goto out;
1711
1712
0
    fp->ret = curl_easy_perform(fp->curl);
1713
1714
0
    if (fp->ret == CURLE_OK) {
1715
0
        ret = 0;
1716
0
    } else {
1717
0
        errno = easy_errno(fp->curl, fp->ret);
1718
0
    }
1719
1720
0
 out:
1721
0
    ks_free(&url);
1722
0
    ks_free(&canonical_query_string);
1723
0
    curl_slist_free_all(headers);
1724
1725
0
    return ret;
1726
0
}
1727
1728
1729
0
static ssize_t s3_write(hFILE *fpv, const void *bufferv, size_t nbytes) {
1730
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
1731
0
    const char *buffer  = (const char *)bufferv;
1732
0
    CURLcode cret;
1733
1734
0
    if (kputsn(buffer, nbytes, &fp->buffer) == EOF) {
1735
0
        return -1;
1736
0
    }
1737
1738
0
    if (fp->buffer.l > fp->part_size) {
1739
        // time to write out our data
1740
0
        kstring_t response = {0, 0, NULL};
1741
0
        int ret;
1742
1743
0
        ret = upload_part(fp, &response);
1744
1745
0
        if (!ret) {
1746
0
            long response_code;
1747
0
            kstring_t etag = {0, 0, NULL};
1748
1749
0
            cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
1750
1751
0
            if (cret != CURLE_OK) {
1752
0
                errno = easy_errno(fp->curl, cret);
1753
0
                ret = -1;
1754
0
            } else if (response_code > 200) {
1755
0
                errno = http_status_errno(response_code);
1756
0
                ret = -1;
1757
0
            } else {
1758
0
                if (get_entry(response.s, "Etag: \"", "\"", &etag) == EOF) {
1759
0
                    fprintf(stderr, "hfile_s3: Failed to read Etag\n");
1760
0
                    ret = -1;
1761
0
                } else {
1762
0
                    ksprintf(&fp->completion_message, "\t<Part>\n\t\t<PartNumber>%d</PartNumber>\n\t\t<ETag>%s</ETag>\n\t</Part>\n",
1763
0
                        fp->part_no, etag.s);
1764
1765
0
                    ks_free(&etag);
1766
0
                }
1767
0
            }
1768
0
        }
1769
1770
0
        ks_free(&response);
1771
1772
0
        if (ret) {
1773
0
            abort_upload(fp);
1774
0
            return -1;
1775
0
        }
1776
1777
0
        fp->part_no++;
1778
0
        fp->buffer.l = 0;
1779
1780
0
        if (fp->expand && (fp->part_no % EXPAND_ON == 0)) {
1781
0
            fp->part_size *= 2;
1782
0
        }
1783
0
    }
1784
1785
0
    return nbytes;
1786
0
}
1787
1788
1789
0
static int s3_write_close(hFILE *fpv) {
1790
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
1791
0
    kstring_t response = {0, 0, NULL};
1792
0
    int ret = 0;
1793
0
    CURLcode cret;
1794
0
    long response_code;
1795
1796
0
    if (!fp->aborted) {
1797
1798
0
        if (fp->buffer.l) {
1799
            // write the last part
1800
1801
0
            ret = upload_part(fp, &response);
1802
1803
0
            if (!ret) {
1804
0
                kstring_t etag = {0, 0, NULL};
1805
1806
0
                cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
1807
1808
0
                if (cret != CURLE_OK) {
1809
0
                    errno = easy_errno(fp->curl, cret);
1810
0
                    ret = -1;
1811
0
                } else if (response_code > 200) {
1812
0
                    errno = http_status_errno(response_code);
1813
0
                    ret = -1;
1814
0
                } else {
1815
0
                    if (get_entry(response.s, "ETag: \"", "\"", &etag) == EOF) {
1816
0
                        ret = -1;
1817
0
                    } else {
1818
0
                        ksprintf(&fp->completion_message, "\t<Part>\n\t\t<PartNumber>%d</PartNumber>\n\t\t<ETag>%s</ETag>\n\t</Part>\n",
1819
0
                            fp->part_no, etag.s);
1820
1821
0
                        ks_free(&etag);
1822
0
                    }
1823
0
                }
1824
0
            }
1825
1826
0
            ks_free(&response);
1827
1828
0
            if (ret) {
1829
0
                abort_upload(fp);
1830
0
                return -1;
1831
0
            }
1832
1833
0
            fp->part_no++;
1834
0
        }
1835
1836
0
        if (fp->part_no > 1) {
1837
0
            ret = complete_upload(fp, &response);
1838
1839
0
            if (!ret) {
1840
0
                if (strstr(response.s, "CompleteMultipartUploadResult") == NULL) {
1841
0
                    ret = -1;
1842
0
                    cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
1843
1844
0
                    if (cret == CURLE_OK) {
1845
0
                        if (hts_verbose >= HTS_LOG_INFO) {
1846
0
                            if (report_s3_error(&response, response_code)) {
1847
0
                                fprintf(stderr, "hfile_s3: warning, unable to report full S3 error status.\n");
1848
0
                            }
1849
0
                        }
1850
1851
0
                        errno = http_status_errno(response_code);
1852
0
                    } else {
1853
0
                        errno = easy_errno(fp->curl, cret);
1854
0
                    }
1855
0
                }
1856
0
            }
1857
0
        } else {
1858
0
            ret = -1;
1859
0
        }
1860
1861
0
        if (ret) {
1862
0
            abort_upload(fp);
1863
0
        } else {
1864
0
            cleanup(fp);
1865
0
        }
1866
0
    }
1867
1868
0
    ks_free(&response);
1869
1870
0
    return ret;
1871
0
}
1872
1873
1874
0
static int handle_bad_request(hFILE_s3 *fp, kstring_t *resp) {
1875
0
    kstring_t region = {0, 0, NULL};
1876
0
    int ret = -1;
1877
1878
0
    if (get_entry(resp->s, "<Region>", "</Region>", &region) == EOF) {
1879
0
        return -1;
1880
0
    }
1881
1882
0
    ret = set_region(fp->au, &region);
1883
1884
0
    ks_free(&region);
1885
1886
0
    if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: handle_bad_request: return %d\n", ret);
1887
1888
0
    return ret;
1889
0
}
1890
1891
0
static int initialise_upload(hFILE_s3 *fp, kstring_t *head, kstring_t *resp, int user_query) {
1892
0
    kstring_t url = KS_INITIALIZE;
1893
0
    int ret = -1;
1894
0
    struct curl_slist *headers = NULL;
1895
0
    char http_request[] = "POST";
1896
0
    char delimiter = '?';
1897
0
    CURLcode err;
1898
1899
0
    clear_authorisation_values(fp);
1900
1901
0
    if (user_query) {
1902
0
        delimiter = '&';
1903
0
    }
1904
1905
0
    if (v4_authorisation(fp, http_request, NULL, "uploads=", user_query) != 0) {
1906
0
        goto out;
1907
0
    }
1908
1909
0
    if (ksprintf(&url, "%s%cuploads", fp->url.s, delimiter) < 0) {
1910
0
        goto out;
1911
0
    }
1912
1913
0
    if (ksprintf(&fp->content, "x-amz-content-sha256: %s", fp->content_hash.s) < 0) {
1914
0
        goto out;
1915
0
    }
1916
1917
0
    err = curl_easy_setopt(fp->curl, CURLOPT_URL, url.s);
1918
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_POST, 1L);
1919
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_POSTFIELDS, "");  // send no data
1920
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEFUNCTION, response_callback);
1921
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEDATA, (void *)resp);
1922
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERFUNCTION, response_callback);
1923
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERDATA, (void *)head);
1924
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_USERAGENT, curl.useragent.s);
1925
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_VERBOSE, fp->verbose);
1926
1927
0
    if (err != CURLE_OK) {
1928
0
        errno = EINVAL;
1929
0
        goto out;
1930
0
    }
1931
1932
0
    headers = set_html_headers(fp, &fp->authorisation, &fp->date, &fp->content, &fp->token, NULL);
1933
1934
0
    if (!headers)
1935
0
        goto out;
1936
1937
0
    fp->ret = curl_easy_perform(fp->curl);
1938
1939
0
    if (fp->ret == CURLE_OK) {
1940
0
        ret = 0;
1941
0
    } else {
1942
0
        errno = easy_errno(fp->curl, fp->ret);
1943
0
    }
1944
1945
0
 out:
1946
0
    curl_slist_free_all(headers);
1947
0
    ks_free(&url);
1948
1949
0
    return ret;
1950
0
}
1951
1952
1953
0
static int get_upload_id(hFILE_s3 *fp, kstring_t *resp) {
1954
0
    int ret = 0;
1955
1956
0
    if (get_entry(resp->s, "<UploadId>", "</UploadId>", &fp->upload_id) == EOF) {
1957
0
        ret = -1;
1958
0
    }
1959
1960
0
    return ret;
1961
0
}
1962
1963
1964
/*
1965
    Now for the reading code
1966
*/
1967
1968
0
#define READ_PART_SIZE 1048576
1969
1970
0
static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *fpv) {
1971
0
    hFILE_s3 *fp = (hFILE_s3 *) fpv;
1972
0
    size_t n = size * nmemb;
1973
1974
0
    if (n) {
1975
0
        if (kputsn(ptr, n, &fp->buffer) == EOF) {
1976
0
            fprintf(stderr, "hfile_s3: error: unable to allocate memory to read data.\n");
1977
0
            return 0;
1978
0
        }
1979
0
    }
1980
1981
0
    return n;
1982
0
}
1983
1984
1985
0
static int s3_read_close(hFILE *fpv) {
1986
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
1987
1988
0
    cleanup(fp);
1989
1990
0
    return 0;
1991
0
}
1992
1993
1994
0
static int get_part(hFILE_s3 *fp, kstring_t *resp) {
1995
0
    struct curl_slist *headers = NULL;
1996
0
    int ret = -1;
1997
0
    char http_request[] = "GET";
1998
0
    CURLcode err;
1999
2000
0
    ks_clear(&fp->buffer); // reset storage buffer
2001
0
    clear_authorisation_values(fp);
2002
2003
0
    if (fp->au->is_v4) {
2004
0
        if (v4_authorisation(fp, http_request, NULL, "", 0) != 0) {
2005
0
            goto out;
2006
0
        }
2007
2008
0
        if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: get_part: v4 auth done\n");
2009
2010
0
        if (ksprintf(&fp->content, "x-amz-content-sha256: %s", fp->content_hash.s) < 0) {
2011
0
            goto out;
2012
0
        }
2013
0
    } else {
2014
0
        if (v2_authorisation(fp, http_request) != 0) {
2015
0
            goto out;
2016
0
        }
2017
2018
0
        if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: get_part v2 auth done\n");
2019
0
    }
2020
2021
0
    if (ksprintf(&fp->range, "Range: bytes=%zu-%zu", fp->last_read, fp->last_read + fp->part_size - 1) < 0) {
2022
0
        goto out;
2023
0
    }
2024
2025
0
    if (hts_verbose >= HTS_LOG_INFO) {
2026
0
        fprintf(stderr, "hfile_s3: get_part: range set %s\n", fp->range.s);
2027
0
        fprintf(stderr, "hfile_s3: url %s\n", fp->url.s);
2028
0
    }
2029
2030
0
    curl_easy_reset(fp->curl);
2031
2032
0
    err = curl_easy_setopt(fp->curl, CURLOPT_URL, fp->url.s);
2033
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEFUNCTION, recv_callback);
2034
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_WRITEDATA, (void *)fp);
2035
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_USERAGENT, curl.useragent.s);
2036
0
    err |= curl_easy_setopt(fp->curl, CURLOPT_VERBOSE, fp->verbose);
2037
2038
0
    if (resp) {
2039
0
        err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERFUNCTION, response_callback);
2040
0
        err |= curl_easy_setopt(fp->curl, CURLOPT_HEADERDATA, (void *)resp);
2041
0
    }
2042
2043
0
    if (err != CURLE_OK) {
2044
0
        errno = EINVAL;
2045
0
        goto out;
2046
0
    }
2047
2048
0
    headers = set_html_headers(fp, &fp->authorisation, &fp->date, &fp->content, &fp->token, &fp->range);
2049
2050
0
    if (!headers)
2051
0
        goto out;
2052
2053
0
    fp->ret = curl_easy_perform(fp->curl);
2054
2055
0
    if (fp->ret == CURLE_OK) {
2056
0
        ret = 0;
2057
0
    } else {
2058
0
        errno = easy_errno(fp->curl, fp->ret);
2059
0
    }
2060
2061
0
out:
2062
0
    if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: get_part: ret %d\n", ret);
2063
0
    curl_slist_free_all(headers);
2064
2065
0
    return ret;
2066
0
}
2067
2068
2069
0
static ssize_t s3_read(hFILE *fpv, void *bufferv, size_t nbytes) {
2070
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
2071
0
    char *buffer = (char *)bufferv;
2072
0
    size_t got = 0;
2073
2074
    /* Transfer data from the fp->buffer to the calling buffer.
2075
       If there is no data left in the fp->buffer, grab another chunk of
2076
       data from s3.
2077
    */
2078
0
    while (fp->keep_going && got < nbytes) {
2079
2080
0
        if (fp->buffer.l && fp->last_read_buffer < fp->buffer.l) {
2081
            // copy data across
2082
0
            size_t to_copy;
2083
0
            size_t remaining = fp->buffer.l - fp->last_read_buffer;
2084
0
            size_t bytes_left = nbytes - got;
2085
2086
0
            if (hts_verbose >  HTS_LOG_INFO) fprintf(stderr, "hfile_s3: read - remaining %zu read %zu bytes_left %zu, nbytes %zu\n", remaining, got, bytes_left, nbytes);
2087
2088
0
            if (bytes_left < remaining) {
2089
0
                to_copy = bytes_left;
2090
0
            } else {
2091
0
                to_copy = remaining;
2092
0
            }
2093
2094
0
            memcpy(buffer + got, fp->buffer.s + fp->last_read_buffer, to_copy);
2095
0
            got += to_copy;
2096
0
            fp->last_read_buffer += to_copy;
2097
2098
0
            if ((fp->buffer.l < fp->part_size) && (fp->last_read_buffer == fp->buffer.l)) {
2099
0
                fp->keep_going = 0;
2100
0
            }
2101
0
        } else {
2102
0
            int ret;
2103
2104
0
            ret = get_part(fp, NULL);
2105
2106
0
            if (!ret) {
2107
0
                long response_code;
2108
0
                CURLcode cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
2109
2110
0
                if (cret != CURLE_OK) {
2111
0
                    errno = easy_errno(fp->curl, cret);
2112
0
                    ret = -1;
2113
0
                } else if (response_code > 300) {
2114
0
                    errno = http_status_errno(response_code);
2115
0
                    ret = -1;
2116
0
                }
2117
0
            }
2118
2119
0
            if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: read - read error %d\n", ret);
2120
2121
0
            if (ret < 0)
2122
0
                return ret;
2123
2124
0
            if (fp->buffer.l == 0) {
2125
0
                fp->keep_going = 0;
2126
0
                break;
2127
0
            }
2128
2129
0
            fp->last_read_buffer = 0;
2130
0
            fp->last_read = fp->last_read + fp->buffer.l;
2131
0
        }
2132
0
    }
2133
2134
0
    return got;
2135
0
}
2136
2137
2138
0
static off_t s3_seek(hFILE *fpv, off_t offset, int whence) {
2139
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
2140
0
    off_t origin;
2141
2142
0
    if (fp->write) {
2143
        // lets not try and seek while writing
2144
0
        errno = ESPIPE;
2145
0
        return -1;
2146
0
    }
2147
2148
    // I am not sure we handle any seek other than one from the beginning
2149
0
    switch (whence) {
2150
0
        case SEEK_SET:
2151
0
            origin = 0;
2152
0
            break;
2153
0
        case SEEK_CUR:
2154
            // hseek() should convert this to SEEK_SET
2155
0
            errno = ENOSYS;
2156
0
            return -1;
2157
0
        case SEEK_END:
2158
0
            if (fp->file_size < 0) {
2159
0
                errno = ESPIPE;
2160
0
                return -1;
2161
0
            }
2162
2163
0
            origin = fp->file_size;
2164
0
            break;
2165
0
        default:
2166
0
            errno = EINVAL;
2167
0
            return -1;
2168
0
    }
2169
2170
    // Check 0 <= origin+offset < fp->file_size carefully, avoiding overflow
2171
0
    if ((offset < 0)? origin + offset < 0
2172
0
                : (fp->file_size >= 0 && offset > fp->file_size - origin)) {
2173
0
        errno = EINVAL;
2174
0
        return -1;
2175
0
    }
2176
2177
0
    fp->keep_going = 1;
2178
2179
0
    size_t pos = origin + offset; // origin is really only useful if we can make the other modes work
2180
2181
0
    if (pos <= fp->last_read && pos > (fp->last_read - fp->buffer.l)) {
2182
        // within the current local buffer
2183
0
        fp->last_read_buffer = pos - (fp->last_read - fp->buffer.l);
2184
0
    } else {
2185
0
        fp->last_read = pos;
2186
0
        ks_clear(&fp->buffer); // resetting fp->buffer triggers a new remote read
2187
0
    }
2188
2189
0
    return (off_t) pos;
2190
0
}
2191
2192
2193
/*
2194
    Unlike upload, download does not really need an initialisation.  Here we use it to
2195
    get the size of the wanted files and as a test for redirects.
2196
*/
2197
0
static int initialise_download(hFILE_s3 *fp, kstring_t *resp) {
2198
2199
0
    fp->last_read = 0;
2200
0
    ks_clear(resp);
2201
2202
0
    return get_part(fp, resp);
2203
0
}
2204
2205
2206
0
static int s3_close(hFILE *fpv) {
2207
0
    hFILE_s3 *fp = (hFILE_s3 *)fpv;
2208
0
    int ret;
2209
2210
0
    if (!fp->write) {
2211
0
        ret = s3_read_close(fpv);
2212
0
    } else {
2213
0
        ret = s3_write_close(fpv);
2214
0
    }
2215
2216
0
    return ret;
2217
0
}
2218
2219
2220
static const struct hFILE_backend s3_backend = {
2221
    s3_read, s3_write, s3_seek, NULL, s3_close
2222
};
2223
2224
/* Read and write open here, need to be after the s3_backend declaration. */
2225
0
static hFILE *s3_write_open(const char *url, s3_auth_data *auth) {
2226
0
    hFILE_s3 *fp;
2227
0
    kstring_t response = {0, 0, NULL};
2228
0
    kstring_t header   = {0, 0, NULL};
2229
0
    int has_user_query = 0;
2230
0
    char *query_start;
2231
0
    const char *env;
2232
0
    CURLcode cret;
2233
0
    long response_code;
2234
0
    int save_errno;
2235
2236
0
    fp = (hFILE_s3 *)hfile_init(sizeof(hFILE_s3), "w", 0);
2237
2238
0
    if (fp == NULL) {
2239
0
        return NULL;
2240
0
    }
2241
2242
0
    if ((fp->curl = curl_easy_init()) == NULL) {
2243
0
        errno = ENOMEM;
2244
0
        goto error;
2245
0
    }
2246
2247
0
    fp->au = auth;
2248
2249
0
    initialise_local(fp);
2250
0
    initialise_authorisation_values(fp);
2251
0
    fp->aborted = 0;
2252
0
    fp->part_size = MINIMUM_S3_WRITE_SIZE;
2253
0
    fp->expand = 1;
2254
0
    fp->write = 1;
2255
2256
0
    if ((env = getenv("HTS_S3_PART_SIZE")) != NULL) {
2257
0
        int part_size = atoi(env) * 1024 * 1024;
2258
2259
0
        if (part_size > fp->part_size)
2260
0
            fp->part_size = part_size;
2261
2262
0
        fp->expand = 0;
2263
0
    }
2264
2265
0
    if (hts_verbose >= 8) {
2266
0
        fp->verbose = 1L;
2267
0
    } else {
2268
0
        fp->verbose = 0L;
2269
0
    }
2270
2271
0
    kputs(url, &fp->url);
2272
2273
0
    if ((query_start = strchr(fp->url.s, '?'))) {
2274
0
        has_user_query = 1;
2275
0
    }
2276
2277
0
    if (initialise_upload(fp, &header, &response, has_user_query))
2278
0
        goto error;
2279
2280
0
    cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
2281
2282
0
    if (cret == CURLE_OK) {
2283
0
        if (response_code == S3_MOVED_PERMANENTLY || response_code == S3_TEMPORARY_REDIRECT) {
2284
0
            if (redirect_endpoint(fp, &header) == 0) {
2285
0
                ks_clear(&response);
2286
0
                ks_clear(&header);
2287
2288
0
                if (initialise_upload(fp, &header, &response, has_user_query))
2289
0
                    goto error;
2290
0
            }
2291
0
        } else if (response_code == S3_BAD_REQUEST) {
2292
0
            if (handle_bad_request(fp, &response) == 0) {
2293
0
                ks_clear(&response);
2294
0
                ks_clear(&header);
2295
2296
0
                if (initialise_upload(fp, &header, &response, has_user_query))
2297
0
                    goto error;
2298
0
            }
2299
0
        }
2300
2301
        // reget the response code (may not have changed)
2302
0
        cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
2303
0
    } else {
2304
        // unable to get a response code from curl
2305
0
        errno = easy_errno(fp->curl, cret);
2306
0
        goto error;
2307
0
    }
2308
2309
0
    if (response_code >= 300) {
2310
        // something went wrong with the initialisation
2311
2312
0
        if (cret == CURLE_OK) {
2313
0
            if (hts_verbose >= HTS_LOG_INFO) {
2314
0
                if (report_s3_error(&response, response_code)) {
2315
0
                    fprintf(stderr, "hfile_s3: warning, unable to report full S3 error status.\n");
2316
0
                }
2317
0
            }
2318
2319
0
            errno = http_status_errno(response_code);
2320
0
        } else {
2321
0
            errno = easy_errno(fp->curl, cret);
2322
0
        }
2323
2324
0
        goto error;
2325
0
    }
2326
2327
0
    if (get_upload_id(fp, &response)) goto error;
2328
2329
    // start the completion message (a formatted list of parts)
2330
0
    if (kputs("<CompleteMultipartUpload>\n", &fp->completion_message) == EOF) {
2331
0
        goto error;
2332
0
    }
2333
2334
0
    fp->part_no = 1;
2335
2336
    // user query string no longer a useful part of the URL
2337
0
    if (query_start)
2338
0
         *query_start = '\0';
2339
2340
0
    fp->base.backend = &s3_backend;
2341
0
    ks_free(&response);
2342
0
    ks_free(&header);
2343
2344
0
    return &fp->base;
2345
2346
0
error:
2347
0
    save_errno = errno;
2348
0
    ks_free(&response);
2349
0
    ks_free(&header);
2350
0
    cleanup_local(fp);
2351
0
    free_authorisation_values(fp);
2352
0
    hfile_destroy((hFILE *)fp);
2353
0
    errno = save_errno;
2354
0
    return NULL;
2355
0
}
2356
2357
2358
0
static hFILE *s3_read_open(const char *url, s3_auth_data *auth) {
2359
0
    hFILE_s3 *fp;
2360
0
    const char *env;
2361
0
    kstring_t response   = {0, 0, NULL};
2362
0
    kstring_t file_range = {0, 0, NULL};
2363
0
    CURLcode cret;
2364
0
    long response_code = 0;
2365
0
    int save_errno;
2366
2367
0
    fp = (hFILE_s3 *)hfile_init(sizeof(hFILE_s3), "r", 0);
2368
2369
0
    if (fp == NULL) {
2370
0
        return NULL;
2371
0
    }
2372
2373
0
    if ((fp->curl = curl_easy_init()) == NULL) {
2374
0
        errno = ENOMEM;
2375
0
        goto error;
2376
0
    }
2377
2378
0
    fp->au = auth;
2379
2380
0
    initialise_local(fp);
2381
0
    initialise_authorisation_values(fp);
2382
2383
0
    fp->last_read = 0; // ranges start at 0
2384
0
    fp->write = 0;
2385
2386
0
    if ((env = getenv("HTS_S3_READ_PART_SIZE")) != NULL) {
2387
0
        fp->part_size = atoi(env) * 1024 * 1024;
2388
0
    } else {
2389
0
        fp->part_size = READ_PART_SIZE;
2390
0
    }
2391
2392
0
    if (hts_verbose >= 8) {
2393
0
        fp->verbose = 1L;
2394
0
    } else {
2395
0
        fp->verbose = 0L;
2396
0
    }
2397
2398
0
    kputs(url, &fp->url);
2399
2400
0
    if (initialise_download(fp, &response))
2401
0
        goto error;
2402
2403
0
    cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
2404
2405
0
    if (cret == CURLE_OK) {
2406
0
        if (response_code == S3_MOVED_PERMANENTLY || response_code == S3_TEMPORARY_REDIRECT) {
2407
0
            ks_clear(&response);
2408
2409
0
            if (redirect_endpoint(fp, &response) == 0) {
2410
0
                if (initialise_download(fp, &response))
2411
0
                    goto error;
2412
0
            }
2413
0
        } else if (response_code == S3_BAD_REQUEST) {
2414
0
            ks_clear(&response);
2415
2416
0
            if (handle_bad_request(fp, &fp->buffer) == 0) {
2417
0
                if (initialise_download(fp, &response))
2418
0
                    goto error;
2419
0
            }
2420
0
        }
2421
2422
        // reget the response code (may not have changed)
2423
0
        cret = curl_easy_getinfo(fp->curl, CURLINFO_RESPONSE_CODE, &response_code);
2424
0
    } else {
2425
        // unable to get a response code from curl
2426
0
        errno = easy_errno(fp->curl, cret);
2427
0
        goto error;
2428
0
    }
2429
2430
0
    if (response_code >= 300) {
2431
        // something went wrong with the initialisation
2432
2433
0
        if (cret == CURLE_OK) {
2434
0
            if (hts_verbose >= HTS_LOG_INFO) {
2435
0
                if (report_s3_error(&fp->buffer, response_code)) {
2436
0
                    fprintf(stderr, "hfile_s3: warning, unable to report full S3 error status.\n");
2437
0
                }
2438
0
            }
2439
2440
0
            errno = http_status_errno(response_code);
2441
0
        } else {
2442
0
            errno = easy_errno(fp->curl, cret);
2443
0
        }
2444
2445
0
        goto error;
2446
0
    }
2447
2448
0
    if (get_entry(response.s, "content-range: bytes ", "\n", &file_range) == EOF) {
2449
0
        fprintf(stderr, "hfile_s3: warning: failed to read file size.\n");
2450
0
        fp->file_size = -1;
2451
0
    } else {
2452
0
        char *s;
2453
0
        if ((s = strchr(file_range.s, '/'))) {
2454
0
            fp->file_size = strtoll(s + 1, NULL, 10);
2455
0
        } else {
2456
0
            fp->file_size = -1;
2457
0
        }
2458
0
    }
2459
2460
0
    fp->last_read_buffer = 0;
2461
0
    fp->last_read = fp->last_read + fp->buffer.l;
2462
0
    fp->base.backend = &s3_backend;
2463
0
    fp->keep_going = 1;
2464
2465
0
    ks_free(&response);
2466
0
    ks_free(&file_range);
2467
0
    return &fp->base;
2468
2469
0
 error:
2470
0
    save_errno = errno;
2471
0
    ks_free(&response);
2472
0
    ks_free(&file_range);
2473
0
    cleanup_local(fp);
2474
0
    free_authorisation_values(fp);
2475
0
    hfile_destroy((hFILE *)fp);
2476
0
    errno = save_errno;
2477
0
    return NULL;
2478
0
}
2479
2480
2481
0
static hFILE *s3_open_v4(const char *s3url, const char *mode, va_list *argsp) {
2482
0
    kstring_t url = { 0, 0, NULL };
2483
2484
0
    s3_auth_data *ad = setup_auth_data(s3url, mode, 4, &url);
2485
0
    hFILE *fp = NULL;
2486
2487
0
    if (ad == NULL) {
2488
0
        return NULL;
2489
0
    }
2490
2491
0
    if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: s3_open_v4 url %s\n", url.s);
2492
2493
0
    if (*mode == 'r') {
2494
0
        fp  = s3_read_open(url.s, ad);
2495
0
    } else {
2496
0
        fp =  s3_write_open(url.s, ad);
2497
0
    }
2498
2499
0
    ks_free(&url);
2500
0
    if (!fp)
2501
0
        free_auth_data(ad);
2502
2503
0
    return fp;
2504
0
}
2505
2506
2507
0
static hFILE *s3_open_v2(const char *s3url, const char *mode, va_list *argsp) {
2508
0
    kstring_t url = { 0, 0, NULL };
2509
2510
0
    s3_auth_data *ad = setup_auth_data(s3url, mode, 2, &url);
2511
0
    hFILE *fp = NULL;
2512
2513
0
    if (ad == NULL) {
2514
0
        return NULL;
2515
0
    }
2516
2517
0
    if (hts_verbose >= HTS_LOG_INFO) fprintf(stderr, "hfile_s3: s3_open_v2 url %s\n", url.s);
2518
2519
0
    if (*mode == 'r') {
2520
0
        fp  = s3_read_open(url.s, ad);
2521
0
    } else {
2522
0
        fprintf(stderr, "hfile_s3: error - signature v2 not handled for writing.\n.");
2523
0
    }
2524
2525
0
    ks_free(&url);
2526
0
    if (!fp)
2527
0
        free_auth_data(ad);
2528
2529
0
    return fp;
2530
0
}
2531
2532
2533
static hFILE *hopen_s3(const char *url, const char *mode)
2534
0
{
2535
0
    hFILE *fp;
2536
2537
0
    if (getenv("HTS_S3_V2") == NULL) {
2538
0
        fp = s3_open_v4(url, mode, NULL);
2539
0
    } else {
2540
0
        fp = s3_open_v2(url, mode, NULL);
2541
0
    }
2542
2543
0
    return fp;
2544
0
}
2545
2546
2547
static hFILE *vhopen_s3(const char *url, const char *mode, va_list args0)
2548
0
{
2549
0
    hFILE *fp;
2550
2551
    // This should handle to vargs case.  Not sure what vargs we want
2552
    // to handle
2553
0
    fp = hopen_s3(url, mode);
2554
2555
0
    return fp;
2556
0
}
2557
2558
2559
1
static void s3_exit(void) {
2560
1
    if (curl_share_cleanup(curl.share) == CURLSHE_OK)
2561
1
        curl.share = NULL;
2562
2563
1
    free(curl.useragent.s);
2564
1
    curl.useragent.l = curl.useragent.m = 0; curl.useragent.s = NULL;
2565
1
    curl_global_cleanup();
2566
1
}
2567
2568
2569
1
int PLUGIN_GLOBAL(hfile_plugin_init,_s3)(struct hFILE_plugin *self) {
2570
2571
1
    static const struct hFILE_scheme_handler handler =
2572
1
        { hopen_s3, hfile_always_remote, "Amazon S3",
2573
1
          2000 + 50, vhopen_s3
2574
1
        };
2575
2576
#ifdef ENABLE_PLUGINS
2577
    // Embed version string for examination via strings(1) or what(1)
2578
    static const char id[] =
2579
        "@(#)hfile_s3 plugin (htslib)\t" HTS_VERSION_TEXT;
2580
    const char *version = strchr(id, '\t') + 1;
2581
2582
    if (hts_verbose >= 9)
2583
        fprintf(stderr, "[M::hfile_s3.init] version %s\n",
2584
                version);
2585
#else
2586
1
    const char *version = hts_version();
2587
1
#endif
2588
2589
1
    const curl_version_info_data *info;
2590
1
    CURLcode err;
2591
1
    CURLSHcode errsh;
2592
2593
1
    err = curl_global_init(CURL_GLOBAL_ALL);
2594
2595
1
    if (err != CURLE_OK) {
2596
        // Set a suitably catastrophic error code
2597
0
        errno = ENETDOWN;
2598
0
        return -1;
2599
0
    }
2600
2601
1
    curl.share = curl_share_init();
2602
2603
1
    if (curl.share == NULL) {
2604
0
        curl_global_cleanup();
2605
0
        errno = EIO;
2606
0
        return -1;
2607
0
    }
2608
2609
1
    errsh  = curl_share_setopt(curl.share, CURLSHOPT_LOCKFUNC, share_lock);
2610
1
    errsh |= curl_share_setopt(curl.share, CURLSHOPT_UNLOCKFUNC, share_unlock);
2611
1
    errsh |= curl_share_setopt(curl.share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
2612
2613
1
    if (errsh != 0) {
2614
0
        curl_share_cleanup(curl.share);
2615
0
        curl_global_cleanup();
2616
0
        errno = EIO;
2617
0
        return -1;
2618
0
    }
2619
2620
1
    info = curl_version_info(CURLVERSION_NOW);
2621
1
    ksprintf(&curl.useragent, "htslib/%s libcurl/%s", version, info->version);
2622
2623
1
    self->name = "Amazon S3";
2624
1
    self->destroy = s3_exit;
2625
2626
1
    hfile_add_scheme_handler("s3",       &handler);
2627
1
    hfile_add_scheme_handler("s3+http",  &handler);
2628
1
    hfile_add_scheme_handler("s3+https", &handler);
2629
2630
1
    return 0;
2631
1
}
2632