7df6e18265341f87a69fba808aa1f92f8ebca841
markd
  Wed Apr 15 13:39:42 2026 -0700
move copy of htslib

diff --git src/htslib/hfile_libcurl.c src/htslib/hfile_libcurl.c
deleted file mode 100644
index fc449b91f3c..00000000000
--- src/htslib/hfile_libcurl.c
+++ /dev/null
@@ -1,919 +0,0 @@
-/*  hfile_libcurl.c -- libcurl backend for low-level file streams.
-
-    Copyright (C) 2015 Genome Research Ltd.
-
-    Author: John Marshall <jm18@sanger.ac.uk>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.  */
-
-#include <config.h>
-
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <errno.h>
-#include <sys/select.h>
-
-#include "hfile_internal.h"
-#include "htslib/hts.h"  // for hts_version() and hts_verbose
-#include "htslib/kstring.h"
-
-#include <curl/curl.h>
-
-typedef struct {
-    hFILE base;
-    CURL *easy;
-    struct curl_slist *headers;
-    off_t file_size;
-    struct {
-        union { char *rd; const char *wr; } ptr;
-        size_t len;
-    } buffer;
-    CURLcode final_result;  // easy result code for finished transfers
-    // Flags for communicating with libcurl callbacks:
-    unsigned paused : 1;    // callback tells us that it has paused transfer
-    unsigned closing : 1;   // informs callback that hclose() has been invoked
-    unsigned finished : 1;  // wait_perform() tells us transfer is complete
-} hFILE_libcurl;
-
-static int http_status_errno(int status)
-{
-    if (status >= 500)
-        switch (status) {
-        case 501: return ENOSYS;
-        case 503: return EBUSY;
-        case 504: return ETIMEDOUT;
-        default:  return EIO;
-        }
-    else if (status >= 400)
-        switch (status) {
-        case 401: return EPERM;
-        case 403: return EACCES;
-        case 404: return ENOENT;
-        case 405: return EROFS;
-        case 407: return EPERM;
-        case 408: return ETIMEDOUT;
-        case 410: return ENOENT;
-        default:  return EINVAL;
-        }
-    else return 0;
-}
-
-static int easy_errno(CURL *easy, CURLcode err)
-{
-    long lval;
-
-    switch (err) {
-    case CURLE_OK:
-        return 0;
-
-    case CURLE_UNSUPPORTED_PROTOCOL:
-    case CURLE_URL_MALFORMAT:
-        return EINVAL;
-
-    case CURLE_NOT_BUILT_IN:
-        return ENOSYS;
-
-    case CURLE_COULDNT_RESOLVE_PROXY:
-    case CURLE_COULDNT_RESOLVE_HOST:
-    case CURLE_FTP_CANT_GET_HOST:
-        return EDESTADDRREQ; // Lookup failure
-
-    case CURLE_COULDNT_CONNECT:
-    case CURLE_SEND_ERROR:
-    case CURLE_RECV_ERROR:
-        if (curl_easy_getinfo(easy, CURLINFO_OS_ERRNO, &lval) == CURLE_OK)
-            return lval;
-        else
-            return ECONNABORTED;
-
-    case CURLE_REMOTE_ACCESS_DENIED:
-    case CURLE_LOGIN_DENIED:
-    case CURLE_TFTP_PERM:
-        return EACCES;
-
-    case CURLE_PARTIAL_FILE:
-        return EPIPE;
-
-    case CURLE_HTTP_RETURNED_ERROR:
-        if (curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &lval) == CURLE_OK)
-            return http_status_errno(lval);
-        else
-            return EIO;
-
-    case CURLE_WRITE_ERROR:
-    case CURLE_READ_ERROR:
-        return ENOTRECOVERABLE; // Indicates bugs in our callback routines
-
-    case CURLE_OUT_OF_MEMORY:
-        return ENOMEM;
-
-    case CURLE_OPERATION_TIMEDOUT:
-        return ETIMEDOUT;
-
-    case CURLE_RANGE_ERROR:
-        return ESPIPE;
-
-    case CURLE_SSL_CONNECT_ERROR:
-        // TODO return SSL error buffer messages
-        return ECONNABORTED;
-
-    case CURLE_FILE_COULDNT_READ_FILE:
-    case CURLE_TFTP_NOTFOUND:
-        return ENOENT;
-
-    case CURLE_TOO_MANY_REDIRECTS:
-        return ELOOP;
-
-    case CURLE_FILESIZE_EXCEEDED:
-        return EFBIG;
-
-    case CURLE_REMOTE_DISK_FULL:
-        return ENOSPC;
-
-    case CURLE_REMOTE_FILE_EXISTS:
-        return EEXIST;
-
-    default:
-        return EIO;
-    }
-}
-
-static int multi_errno(CURLMcode errm)
-{
-    switch (errm) {
-    case CURLM_CALL_MULTI_PERFORM:
-    case CURLM_OK:
-        return 0;
-
-    case CURLM_BAD_HANDLE:
-    case CURLM_BAD_EASY_HANDLE:
-    case CURLM_BAD_SOCKET:
-        return EBADF;
-
-    case CURLM_OUT_OF_MEMORY:
-        return ENOMEM;
-
-    default:
-        return EIO;
-    }
-}
-
-
-static struct {
-    CURLM *multi;
-    kstring_t useragent;
-    int nrunning;
-    unsigned perform_again : 1;
-} curl = { NULL, { 0, 0, NULL }, 0, 0 };
-
-static void libcurl_exit()
-{
-    (void) curl_multi_cleanup(curl.multi);
-    curl.multi = NULL;
-
-    free(curl.useragent.s);
-    curl.useragent.l = curl.useragent.m = 0; curl.useragent.s = NULL;
-
-    curl_global_cleanup();
-}
-
-
-static void process_messages()
-{
-    CURLMsg *msg;
-    int remaining;
-
-    while ((msg = curl_multi_info_read(curl.multi, &remaining)) != NULL) {
-        hFILE_libcurl *fp = NULL;
-        curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **) &fp);
-        switch (msg->msg) {
-        case CURLMSG_DONE:
-            fp->finished = 1;
-            fp->final_result = msg->data.result;
-            break;
-
-        default:
-            break;
-        }
-    }
-}
-
-static int wait_perform()
-{
-    fd_set rd, wr, ex;
-    int maxfd, nrunning;
-    long timeout;
-    CURLMcode errm;
-
-    FD_ZERO(&rd);
-    FD_ZERO(&wr);
-    FD_ZERO(&ex);
-    if (curl_multi_fdset(curl.multi, &rd, &wr, &ex, &maxfd) != CURLM_OK)
-        maxfd = -1, timeout = 1000;
-    else if (maxfd < 0)
-        timeout = 100;  // as recommended by curl_multi_fdset(3)
-    else {
-        if (curl_multi_timeout(curl.multi, &timeout) != CURLM_OK)
-            timeout = 1000;
-        else if (timeout < 0)
-            timeout = 10000;  // as recommended by curl_multi_timeout(3)
-    }
-
-    if (timeout > 0 && ! curl.perform_again) {
-        struct timeval tval;
-        tval.tv_sec  = (timeout / 1000);
-        tval.tv_usec = (timeout % 1000) * 1000;
-
-        if (select(maxfd + 1, &rd, &wr, &ex, &tval) < 0) return -1;
-    }
-
-    errm = curl_multi_perform(curl.multi, &nrunning);
-    curl.perform_again = 0;
-    if (errm == CURLM_CALL_MULTI_PERFORM) curl.perform_again = 1;
-    else if (errm != CURLM_OK) { errno = multi_errno(errm); return -1; }
-
-    if (nrunning < curl.nrunning) process_messages();
-    return 0;
-}
-
-
-static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *fpv)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-    size_t n = size * nmemb;
-
-    if (n > fp->buffer.len) { fp->paused = 1; return CURL_WRITEFUNC_PAUSE; }
-    else if (n == 0) return 0;
-
-    memcpy(fp->buffer.ptr.rd, ptr, n);
-    fp->buffer.ptr.rd += n;
-    fp->buffer.len -= n;
-    return n;
-}
-
-static ssize_t libcurl_read(hFILE *fpv, void *bufferv, size_t nbytes)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-    char *buffer = (char *) bufferv;
-    CURLcode err;
-
-    fp->buffer.ptr.rd = buffer;
-    fp->buffer.len = nbytes;
-    fp->paused = 0;
-    err = curl_easy_pause(fp->easy, CURLPAUSE_CONT);
-    if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); return -1; }
-
-    while (! fp->paused && ! fp->finished)
-        if (wait_perform() < 0) return -1;
-
-    nbytes = fp->buffer.ptr.rd - buffer;
-    fp->buffer.ptr.rd = NULL;
-    fp->buffer.len = 0;
-
-    if (fp->finished && fp->final_result != CURLE_OK) {
-        errno = easy_errno(fp->easy, fp->final_result);
-        return -1;
-    }
-
-    return nbytes;
-}
-
-static size_t send_callback(char *ptr, size_t size, size_t nmemb, void *fpv)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-    size_t n = size * nmemb;
-
-    if (fp->buffer.len == 0) {
-        // Send buffer is empty; normally pause, or signal EOF if we're closing
-        if (fp->closing) return 0;
-        else { fp->paused = 1; return CURL_READFUNC_PAUSE; }
-    }
-
-    if (n > fp->buffer.len) n = fp->buffer.len;
-    memcpy(ptr, fp->buffer.ptr.wr, n);
-    fp->buffer.ptr.wr += n;
-    fp->buffer.len -= n;
-    return n;
-}
-
-static ssize_t libcurl_write(hFILE *fpv, const void *bufferv, size_t nbytes)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-    const char *buffer = (const char *) bufferv;
-    CURLcode err;
-
-    fp->buffer.ptr.wr = buffer;
-    fp->buffer.len = nbytes;
-    fp->paused = 0;
-    err = curl_easy_pause(fp->easy, CURLPAUSE_CONT);
-    if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); return -1; }
-
-    while (! fp->paused && ! fp->finished)
-        if (wait_perform() < 0) return -1;
-
-    nbytes = fp->buffer.ptr.wr - buffer;
-    fp->buffer.ptr.wr = NULL;
-    fp->buffer.len = 0;
-
-    if (fp->finished && fp->final_result != CURLE_OK) {
-        errno = easy_errno(fp->easy, fp->final_result);
-        return -1;
-    }
-
-    return nbytes;
-}
-
-static off_t libcurl_seek(hFILE *fpv, off_t offset, int whence)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-
-    CURLcode err;
-    CURLMcode errm;
-    off_t origin, pos;
-
-    switch (whence) {
-    case SEEK_SET:
-        origin = 0;
-        break;
-    case SEEK_CUR:
-        errno = ENOSYS;
-        return -1;
-    case SEEK_END:
-        if (fp->file_size < 0) { errno = ESPIPE; return -1; }
-        origin = fp->file_size;
-        break;
-    default:
-        errno = EINVAL;
-        return -1;
-    }
-
-    // Check 0 <= origin+offset < fp->file_size carefully, avoiding overflow
-    if ((offset < 0)? origin + offset < 0
-                : (fp->file_size >= 0 && offset > fp->file_size - origin)) {
-        errno = EINVAL;
-        return -1;
-    }
-
-    pos = origin + offset;
-
-    errm = curl_multi_remove_handle(curl.multi, fp->easy);
-    if (errm != CURLM_OK) { errno = multi_errno(errm); return -1; }
-    curl.nrunning--;
-
-    // TODO If we seem to be doing random access, use CURLOPT_RANGE to do
-    // limited reads (e.g. about a BAM block!) so seeking can reuse the
-    // existing connection more often.
-
-    if (pos <= 2147483647) err = curl_easy_setopt(fp->easy, CURLOPT_RESUME_FROM, (long) pos);
-    else err = curl_easy_setopt(fp->easy, CURLOPT_RESUME_FROM_LARGE, (curl_off_t) pos);
-    if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); return -1; }
-
-    fp->buffer.len = 0;
-    fp->paused = fp->finished = 0;
-
-    errm = curl_multi_add_handle(curl.multi, fp->easy);
-    if (errm != CURLM_OK) { errno = multi_errno(errm); return -1; }
-    curl.nrunning++;
-
-    err = curl_easy_pause(fp->easy, CURLPAUSE_CONT);
-    if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); return -1; }
-
-    while (! fp->paused && ! fp->finished)
-        if (wait_perform() < 0) return -1;
-
-    if (fp->finished && fp->final_result != CURLE_OK) {
-        errno = easy_errno(fp->easy, fp->final_result);
-        return -1;
-    }
-
-    return pos;
-}
-
-static int libcurl_close(hFILE *fpv)
-{
-    hFILE_libcurl *fp = (hFILE_libcurl *) fpv;
-    CURLcode err;
-    CURLMcode errm;
-    int save_errno = 0;
-
-    // Before closing the file, unpause it and perform on it so that uploads
-    // have the opportunity to signal EOF to the server -- see send_callback().
-
-    fp->buffer.len = 0;
-    fp->closing = 1;
-    fp->paused = 0;
-    err = curl_easy_pause(fp->easy, CURLPAUSE_CONT);
-    if (err != CURLE_OK) save_errno = easy_errno(fp->easy, err);
-
-    while (save_errno == 0 && ! fp->paused && ! fp->finished)
-        if (wait_perform() < 0) save_errno = errno;
-
-    if (fp->finished && fp->final_result != CURLE_OK)
-        save_errno = easy_errno(fp->easy, fp->final_result);
-
-    errm = curl_multi_remove_handle(curl.multi, fp->easy);
-    if (errm != CURLM_OK && save_errno == 0) save_errno = multi_errno(errm);
-    curl.nrunning--;
-
-    curl_easy_cleanup(fp->easy);
-
-    if (save_errno) { errno = save_errno; return -1; }
-    else return 0;
-}
-
-static const struct hFILE_backend libcurl_backend =
-{
-    libcurl_read, libcurl_write, libcurl_seek, NULL, libcurl_close
-};
-
-static int add_header(hFILE_libcurl *fp, const char *header)
-{
-    struct curl_slist *list = curl_slist_append(fp->headers, header);
-    if (list == NULL) { errno = ENOMEM; return -1; }
-    fp->headers = list;
-    return 0;
-}
-
-static int
-add_s3_settings(hFILE_libcurl *fp, const char *url, kstring_t *message);
-
-hFILE *hopen_libcurl(const char *url, const char *modes)
-{
-    hFILE_libcurl *fp;
-    char mode;
-    const char *s;
-    CURLcode err;
-    CURLMcode errm;
-    int save;
-
-    if ((s = strpbrk(modes, "rwa+")) != NULL) {
-        mode = *s;
-        if (strpbrk(&s[1], "rwa+")) mode = 'e';
-    }
-    else mode = '\0';
-
-    if (mode != 'r' && mode != 'w') { errno = EINVAL; return NULL; }
-
-    fp = (hFILE_libcurl *) hfile_init(sizeof (hFILE_libcurl), modes, 0);
-    if (fp == NULL) return NULL;
-
-    fp->easy = curl_easy_init();
-    if (fp->easy == NULL) { errno = ENOMEM; goto error; }
-
-    fp->headers = NULL;
-    fp->file_size = -1;
-    fp->buffer.ptr.rd = NULL;
-    fp->buffer.len = 0;
-    fp->final_result = (CURLcode) -1;
-    fp->paused = fp->closing = fp->finished = 0;
-
-    // Make a route to the hFILE_libcurl* given just a CURL* easy handle
-    err = curl_easy_setopt(fp->easy, CURLOPT_PRIVATE, fp);
-
-    if (mode == 'r') {
-        err |= curl_easy_setopt(fp->easy, CURLOPT_WRITEFUNCTION, recv_callback);
-        err |= curl_easy_setopt(fp->easy, CURLOPT_WRITEDATA, fp);
-    }
-    else {
-        err |= curl_easy_setopt(fp->easy, CURLOPT_READFUNCTION, send_callback);
-        err |= curl_easy_setopt(fp->easy, CURLOPT_READDATA, fp);
-        err |= curl_easy_setopt(fp->easy, CURLOPT_UPLOAD, 1L);
-        if (add_header(fp, "Transfer-Encoding: chunked") < 0) goto error;
-    }
-
-    if (tolower(url[0]) == 's' && url[1] == '3') {
-        // Construct the HTTP-Method/Content-MD5/Content-Type part of the
-        // message to be signed.  This will be destroyed by add_s3_settings().
-        kstring_t message = { 0, 0, NULL };
-        kputs((mode == 'r')? "GET\n" : "PUT\n", &message);
-        kputc('\n', &message);
-        kputc('\n', &message);
-        if (add_s3_settings(fp, url, &message) < 0) goto error;
-    }
-    else
-        err |= curl_easy_setopt(fp->easy, CURLOPT_URL, url);
-
-    err |= curl_easy_setopt(fp->easy, CURLOPT_USERAGENT, curl.useragent.s);
-    if (fp->headers)
-        err |= curl_easy_setopt(fp->easy, CURLOPT_HTTPHEADER, fp->headers);
-    err |= curl_easy_setopt(fp->easy, CURLOPT_FOLLOWLOCATION, 1L);
-    err |= curl_easy_setopt(fp->easy, CURLOPT_FAILONERROR, 1L);
-    if (hts_verbose >= 8)
-        err |= curl_easy_setopt(fp->easy, CURLOPT_VERBOSE, 1L);
-
-    if (err != 0) { errno = ENOSYS; goto error; }
-
-    errm = curl_multi_add_handle(curl.multi, fp->easy);
-    if (errm != CURLM_OK) { errno = multi_errno(errm); goto error; }
-    curl.nrunning++;
-
-    while (! fp->paused && ! fp->finished)
-        if (wait_perform() < 0) goto error_remove;
-
-    if (fp->finished && fp->final_result != CURLE_OK) {
-        errno = easy_errno(fp->easy, fp->final_result);
-        goto error_remove;
-    }
-
-    if (mode == 'r') {
-        double dval;
-        if (curl_easy_getinfo(fp->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD,
-                              &dval) == CURLE_OK && dval >= 0.0)
-            fp->file_size = (off_t) (dval + 0.1);
-    }
-
-    fp->base.backend = &libcurl_backend;
-    return &fp->base;
-
-error_remove:
-    save = errno;
-    (void) curl_multi_remove_handle(curl.multi, fp->easy);
-    curl.nrunning--;
-    errno = save;
-
-error:
-    save = errno;
-    curl_easy_cleanup(fp->easy);
-    if (fp->headers) curl_slist_free_all(fp->headers);
-    hfile_destroy((hFILE *) fp);
-    errno = save;
-    return NULL;
-}
-
-int PLUGIN_GLOBAL(hfile_plugin_init,_libcurl)(struct hFILE_plugin *self)
-{
-    static const struct hFILE_scheme_handler handler =
-        { hopen_libcurl, hfile_always_remote, "libcurl", 50 };
-
-    const curl_version_info_data *info;
-    const char * const *protocol;
-    CURLcode err;
-
-    err = curl_global_init(CURL_GLOBAL_ALL);
-    if (err != CURLE_OK) { errno = easy_errno(NULL, err); return -1; }
-
-    curl.multi = curl_multi_init();
-    if (curl.multi == NULL) { curl_global_cleanup(); errno = EIO; return -1; }
-
-    info = curl_version_info(CURLVERSION_NOW);
-    ksprintf(&curl.useragent, "htslib/%s libcurl/%s",
-             hts_version(), info->version);
-
-    curl.nrunning = 0;
-    curl.perform_again = 0;
-    self->name = "libcurl";
-    self->destroy = libcurl_exit;
-
-    for (protocol = info->protocols; *protocol; protocol++)
-        hfile_add_scheme_handler(*protocol, &handler);
-
-    hfile_add_scheme_handler("s3", &handler);
-    hfile_add_scheme_handler("s3+http", &handler);
-    if (info->features & CURL_VERSION_SSL)
-        hfile_add_scheme_handler("s3+https", &handler);
-
-    return 0;
-}
-
-
-/*******************
- * Rewrite S3 URLs *
- *******************/
-
-#if defined HAVE_COMMONCRYPTO
-
-#include <CommonCrypto/CommonHMAC.h>
-
-#define DIGEST_BUFSIZ CC_SHA1_DIGEST_LENGTH
-
-static size_t
-s3_sign(unsigned char *digest, kstring_t *key, kstring_t *message)
-{
-    CCHmac(kCCHmacAlgSHA1, key->s, key->l, message->s, message->l, digest);
-    return CC_SHA1_DIGEST_LENGTH;
-}
-
-#elif defined HAVE_HMAC
-
-#include <openssl/hmac.h>
-
-#define DIGEST_BUFSIZ EVP_MAX_MD_SIZE
-
-static size_t
-s3_sign(unsigned char *digest, kstring_t *key, kstring_t *message)
-{
-    unsigned int len;
-    HMAC(EVP_sha1(), key->s, key->l,
-         (unsigned char *) message->s, message->l, digest, &len);
-    return len;
-}
-
-#else
-#error No HMAC() routine found by configure
-#endif
-
-static void
-urldecode_kput(const char *s, int len, hFILE_libcurl *fp, kstring_t *str)
-{
-    if (memchr(s, '%', len) != NULL) {
-        int len2;
-        char *s2 = curl_easy_unescape(fp->easy, s, len, &len2);
-        if (s2 == NULL) abort();
-        kputsn(s2, len2, str);
-        curl_free(s2);
-    }
-    else kputsn(s, len, str);
-}
-
-static void base64_kput(const unsigned char *data, size_t len, kstring_t *str)
-{
-    static const char base64[] =
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-    size_t i = 0;
-    unsigned x = 0;
-    int bits = 0, pad = 0;
-
-    while (bits || i < len) {
-        if (bits < 6) {
-            x <<= 8, bits += 8;
-            if (i < len) x |= data[i++];
-            else pad++;
-        }
-
-        bits -= 6;
-        kputc(base64[(x >> bits) & 63], str);
-    }
-
-    str->l -= pad;
-    kputsn("==", pad, str);
-}
-
-static int is_dns_compliant(const char *s0, const char *slim)
-{
-    int has_nondigit = 0, len = 0;
-    const char *s;
-
-    for (s = s0; s < slim; len++, s++)
-        if (islower(*s))
-            has_nondigit = 1;
-        else if (*s == '-') {
-            has_nondigit = 1;
-            if (s == s0 || s+1 == slim) return 0;
-        }
-        else if (isdigit(*s))
-            ;
-        else if (*s == '.') {
-            if (s == s0 || ! isalnum(s[-1])) return 0;
-            if (s+1 == slim || ! isalnum(s[1])) return 0;
-        }
-        else return 0;
-
-    return has_nondigit && len >= 3 && len <= 63;
-}
-
-static FILE *expand_tilde_open(const char *fname, const char *mode)
-{
-    FILE *fp;
-
-    if (strncmp(fname, "~/", 2) == 0) {
-        kstring_t full_fname = { 0, 0, NULL };
-        const char *home = getenv("HOME");
-        if (! home) return NULL;
-
-        kputs(home, &full_fname);
-        kputs(&fname[1], &full_fname);
-
-        fp = fopen(full_fname.s, mode);
-        free(full_fname.s);
-    }
-    else
-        fp = fopen(fname, mode);
-
-    return fp;
-}
-
-static void parse_ini(const char *fname, const char *section, ...)
-{
-    kstring_t line = { 0, 0, NULL };
-    int active = 1;  // Start active, so global properties are accepted
-    char *s;
-
-    FILE *fp = expand_tilde_open(fname, "r");
-    if (fp == NULL) return;
-
-    while (line.l = 0, kgetline(&line, (kgets_func *) fgets, fp) >= 0)
-        if (line.s[0] == '[' && (s = strchr(line.s, ']')) != NULL) {
-            *s = '\0';
-            active = (strcmp(&line.s[1], section) == 0);
-        }
-        else if (active && (s = strpbrk(line.s, ":=")) != NULL) {
-            const char *key = line.s, *value = &s[1], *akey;
-            va_list args;
-
-            while (isspace(*key)) key++;
-            while (s > key && isspace(s[-1])) s--;
-            *s = '\0';
-
-            while (isspace(*value)) value++;
-            while (line.l > 0 && isspace(line.s[line.l-1]))
-                line.s[--line.l] = '\0';
-
-            va_start(args, section);
-            while ((akey = va_arg(args, const char *)) != NULL) {
-                kstring_t *avar = va_arg(args, kstring_t *);
-                if (strcmp(key, akey) == 0) { kputs(value, avar); break; }
-            }
-            va_end(args);
-        }
-
-    fclose(fp);
-    free(line.s);
-}
-
-static void parse_simple(const char *fname, kstring_t *id, kstring_t *secret)
-{
-    kstring_t text = { 0, 0, NULL };
-    char *s;
-    size_t len;
-
-    FILE *fp = expand_tilde_open(fname, "r");
-    if (fp == NULL) return;
-
-    while (kgetline(&text, (kgets_func *) fgets, fp) >= 0)
-        kputc(' ', &text);
-    fclose(fp);
-
-    s = text.s;
-    while (isspace(*s)) s++;
-    kputsn(s, len = strcspn(s, " \t"), id);
-
-    s += len;
-    while (isspace(*s)) s++;
-    kputsn(s, strcspn(s, " \t"), secret);
-
-    free(text.s);
-}
-
-static int
-add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
-{
-    int ret, save;
-    const char *bucket, *path;
-    char date_hdr[40];
-    CURLcode err;
-
-    kstring_t url = { 0, 0, NULL };
-    kstring_t profile = { 0, 0, NULL };
-    kstring_t id = { 0, 0, NULL };
-    kstring_t secret = { 0, 0, NULL };
-    kstring_t token = { 0, 0, NULL };
-    kstring_t token_hdr = { 0, 0, NULL };
-    kstring_t auth_hdr = { 0, 0, NULL };
-
-    time_t now = time(NULL);
-#ifdef HAVE_GMTIME_R
-    struct tm tm_buffer;
-    struct tm *tm = gmtime_r(&now, &tm_buffer);
-#else
-    struct tm *tm = gmtime(&now);
-#endif
-
-    strftime(date_hdr, sizeof date_hdr, "Date: %a, %d %b %Y %H:%M:%S GMT", tm);
-    if (add_header(fp, date_hdr) < 0) goto error;
-    kputs(&date_hdr[6], message);
-    kputc('\n', message);
-
-    // Our S3 URL format is s3[+SCHEME]://[ID[:SECRET[:TOKEN]]@]BUCKET/PATH
-
-    if (s3url[2] == '+') {
-        bucket = strchr(s3url, ':') + 1;
-        kputsn(&s3url[3], bucket - &s3url[3], &url);
-    }
-    else {
-        kputs("https:", &url);
-        bucket = &s3url[3];
-    }
-    while (*bucket == '/') kputc(*bucket++, &url);
-
-    path = bucket + strcspn(bucket, "/?#@");
-    if (*path == '@') {
-        const char *colon = strpbrk(bucket, ":@");
-        if (*colon != ':') {
-            urldecode_kput(bucket, colon - bucket, fp, &profile);
-        }
-        else {
-            const char *colon2 = strpbrk(&colon[1], ":@");
-            urldecode_kput(bucket, colon - bucket, fp, &id);
-            urldecode_kput(&colon[1], colon2 - &colon[1], fp, &secret);
-            if (*colon2 == ':')
-                urldecode_kput(&colon2[1], path - &colon2[1], fp, &token);
-        }
-
-        bucket = &path[1];
-        path = bucket + strcspn(bucket, "/?#");
-    }
-    else {
-        // If the URL has no ID[:SECRET]@, consider environment variables.
-        const char *v;
-        if ((v = getenv("AWS_ACCESS_KEY_ID")) != NULL) kputs(v, &id);
-        if ((v = getenv("AWS_SECRET_ACCESS_KEY")) != NULL) kputs(v, &secret);
-        if ((v = getenv("AWS_SESSION_TOKEN")) != NULL) kputs(v, &token);
-
-        if ((v = getenv("AWS_DEFAULT_PROFILE")) != NULL) kputs(v, &profile);
-        else if ((v = getenv("AWS_PROFILE")) != NULL) kputs(v, &profile);
-        else kputs("default", &profile);
-    }
-
-    // Use virtual hosted-style access if possible, otherwise path-style.
-    if (is_dns_compliant(bucket, path)) {
-        kputsn(bucket, path - bucket, &url);
-        kputs(".s3.amazonaws.com", &url);
-    }
-    else {
-        kputs("s3.amazonaws.com/", &url);
-        kputsn(bucket, path - bucket, &url);
-    }
-    kputs(path, &url);
-
-    if (id.l == 0) {
-        const char *v = getenv("AWS_SHARED_CREDENTIALS_FILE");
-        parse_ini(v? v : "~/.aws/credentials", profile.s,
-                  "aws_access_key_id", &id, "aws_secret_access_key", &secret,
-                  "aws_session_token", &token, NULL);
-    }
-    if (id.l == 0)
-        parse_ini("~/.s3cfg", profile.s, "access_key", &id,
-                  "secret_key", &secret, "access_token", &token, NULL);
-    if (id.l == 0)
-        parse_simple("~/.awssecret", &id, &secret);
-
-    if (token.l > 0) {
-        kputs("x-amz-security-token:", message);
-        kputs(token.s, message);
-        kputc('\n', message);
-
-        kputs("X-Amz-Security-Token: ", &token_hdr);
-        kputs(token.s, &token_hdr);
-        if (add_header(fp, token_hdr.s) < 0) goto error;
-    }
-
-    kputc('/', message);
-    kputs(bucket, message); // CanonicalizedResource is '/' + bucket + path
-
-    err = curl_easy_setopt(fp->easy, CURLOPT_URL, url.s);
-    if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); goto error; }
-
-    // If we have no id/secret, we can't sign the request but will
-    // still be able to access public data sets.
-    if (id.l > 0 && secret.l > 0) {
-        unsigned char digest[DIGEST_BUFSIZ];
-        size_t digest_len = s3_sign(digest, &secret, message);
-
-        kputs("Authorization: AWS ", &auth_hdr);
-        kputs(id.s, &auth_hdr);
-        kputc(':', &auth_hdr);
-        base64_kput(digest, digest_len, &auth_hdr);
-
-        if (add_header(fp, auth_hdr.s) < 0) goto error;
-    }
-
-    ret = 0;
-    goto free_and_return;
-
-error:
-    ret = -1;
-
-free_and_return:
-    save = errno;
-    free(url.s);
-    free(profile.s);
-    free(id.s);
-    free(secret.s);
-    free(token.s);
-    free(token_hdr.s);
-    free(auth_hdr.s);
-    free(message->s);
-    errno = save;
-    return ret;
-}