nbdkit/SOURCES/0014-curl-Move-configuratio...

2076 lines
62 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 06d446098c2fbc23bb7dc2159568c39db1623c8b Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 27 Jul 2023 14:27:36 +0100
Subject: [PATCH] curl: Move configuration code to a separate file
Previously making a change to the curl handle configuration involved
updating code across several files. Centralise this code in one
place.
After this commit, configuration changes only affect
plugins/curl/config.c, although you will still need to change the
documentation in nbdkit-curl-plugin.pod.
This change is just refactoring. No change in functionality is
intended.
(cherry picked from commit ef762737bcad713f6a55092d88285bdf466a781c)
---
plugins/curl/Makefile.am | 1 +
plugins/curl/config.c | 974 +++++++++++++++++++++++++++++++++++++++
plugins/curl/curl.c | 476 +------------------
plugins/curl/curldefs.h | 38 +-
plugins/curl/pool.c | 455 +-----------------
5 files changed, 998 insertions(+), 946 deletions(-)
create mode 100644 plugins/curl/config.c
diff --git a/plugins/curl/Makefile.am b/plugins/curl/Makefile.am
index f5f1cc1a..7529fb2f 100644
--- a/plugins/curl/Makefile.am
+++ b/plugins/curl/Makefile.am
@@ -38,6 +38,7 @@ if HAVE_CURL
plugin_LTLIBRARIES = nbdkit-curl-plugin.la
nbdkit_curl_plugin_la_SOURCES = \
+ config.c \
curldefs.h \
curl.c \
pool.c \
diff --git a/plugins/curl/config.c b/plugins/curl/config.c
new file mode 100644
index 00000000..742d6080
--- /dev/null
+++ b/plugins/curl/config.c
@@ -0,0 +1,974 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <curl/curl.h>
+
+#include <nbdkit-plugin.h>
+
+#include "ascii-ctype.h"
+#include "ascii-string.h"
+#include "cleanup.h"
+
+#include "curldefs.h"
+
+/* Plugin configuration. */
+const char *url = NULL; /* required */
+
+static const char *cainfo = NULL;
+static const char *capath = NULL;
+static char *cookie = NULL;
+static const char *cookiefile = NULL;
+static const char *cookiejar = NULL;
+static bool followlocation = true;
+static struct curl_slist *headers = NULL;
+static long http_version = CURL_HTTP_VERSION_NONE;
+static long ipresolve = CURL_IPRESOLVE_WHATEVER;
+static char *password = NULL;
+#ifndef HAVE_CURLOPT_PROTOCOLS_STR
+static long protocols = CURLPROTO_ALL;
+#else
+static const char *protocols = NULL;
+#endif
+static const char *proxy = NULL;
+static char *proxy_password = NULL;
+static const char *proxy_user = NULL;
+static struct curl_slist *resolves = NULL;
+static bool sslverify = true;
+static const char *ssl_cipher_list = NULL;
+static long ssl_version = CURL_SSLVERSION_DEFAULT;
+static const char *tls13_ciphers = NULL;
+static bool tcp_keepalive = false;
+static bool tcp_nodelay = true;
+static uint32_t timeout = 0;
+static const char *unix_socket_path = NULL;
+static const char *user = NULL;
+static const char *user_agent = NULL;
+
+static int debug_cb (CURL *handle, curl_infotype type,
+ const char *data, size_t size, void *);
+static size_t write_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
+static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
+static int get_content_length_accept_range (struct curl_handle *ch);
+static bool try_fallback_GET_method (struct curl_handle *ch);
+static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
+static size_t error_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
+
+/* Use '-D curl.verbose=1' to set. */
+NBDKIT_DLL_PUBLIC int curl_debug_verbose = 0;
+
+void
+unload_config (void)
+{
+ free (cookie);
+ if (headers)
+ curl_slist_free_all (headers);
+ free (password);
+ free (proxy_password);
+ if (resolves)
+ curl_slist_free_all (resolves);
+}
+
+#ifndef HAVE_CURLOPT_PROTOCOLS_STR
+/* See <curl/curl.h> */
+static struct { const char *name; long bitmask; } curl_protocols[] = {
+ { "http", CURLPROTO_HTTP },
+ { "https", CURLPROTO_HTTPS },
+ { "ftp", CURLPROTO_FTP },
+ { "ftps", CURLPROTO_FTPS },
+ { "scp", CURLPROTO_SCP },
+ { "sftp", CURLPROTO_SFTP },
+ { "telnet", CURLPROTO_TELNET },
+ { "ldap", CURLPROTO_LDAP },
+ { "ldaps", CURLPROTO_LDAPS },
+ { "dict", CURLPROTO_DICT },
+ { "file", CURLPROTO_FILE },
+ { "tftp", CURLPROTO_TFTP },
+ { "imap", CURLPROTO_IMAP },
+ { "imaps", CURLPROTO_IMAPS },
+ { "pop3", CURLPROTO_POP3 },
+ { "pop3s", CURLPROTO_POP3S },
+ { "smtp", CURLPROTO_SMTP },
+ { "smtps", CURLPROTO_SMTPS },
+ { "rtsp", CURLPROTO_RTSP },
+ { "rtmp", CURLPROTO_RTMP },
+ { "rtmpt", CURLPROTO_RTMPT },
+ { "rtmpe", CURLPROTO_RTMPE },
+ { "rtmpte", CURLPROTO_RTMPTE },
+ { "rtmps", CURLPROTO_RTMPS },
+ { "rtmpts", CURLPROTO_RTMPTS },
+ { "gopher", CURLPROTO_GOPHER },
+#ifdef CURLPROTO_SMB
+ { "smb", CURLPROTO_SMB },
+#endif
+#ifdef CURLPROTO_SMBS
+ { "smbs", CURLPROTO_SMBS },
+#endif
+#ifdef CURLPROTO_MQTT
+ { "mqtt", CURLPROTO_MQTT },
+#endif
+ { NULL }
+};
+
+/* Parse the protocols parameter. */
+static int
+parse_protocols (const char *value)
+{
+ size_t n, i;
+
+ protocols = 0;
+
+ while (*value) {
+ n = strcspn (value, ",");
+ for (i = 0; curl_protocols[i].name != NULL; ++i) {
+ if (strlen (curl_protocols[i].name) == n &&
+ strncmp (value, curl_protocols[i].name, n) == 0) {
+ protocols |= curl_protocols[i].bitmask;
+ goto found;
+ }
+ }
+ nbdkit_error ("protocols: protocol name not found: %.*s", (int) n, value);
+ return -1;
+
+ found:
+ value += n;
+ if (*value == ',')
+ value++;
+ }
+
+ if (protocols == 0) {
+ nbdkit_error ("protocols: empty list of protocols is not allowed");
+ return -1;
+ }
+
+ nbdkit_debug ("curl: protocols: %ld", protocols);
+
+ return 0;
+}
+#endif /* !HAVE_CURLOPT_PROTOCOLS_STR */
+
+/* Called for each key=value passed on the command line. */
+int
+curl_config (const char *key, const char *value)
+{
+ int r;
+
+ if (strcmp (key, "cainfo") == 0) {
+ cainfo = value;
+ }
+
+ else if (strcmp (key, "capath") == 0) {
+ capath = value;
+ }
+
+ else if (strcmp (key, "connections") == 0) {
+ if (nbdkit_parse_unsigned ("connections", value,
+ &connections) == -1)
+ return -1;
+ if (connections == 0) {
+ nbdkit_error ("connections parameter must not be 0");
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "cookie") == 0) {
+ free (cookie);
+ if (nbdkit_read_password (value, &cookie) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "cookiefile") == 0) {
+ /* Reject cookiefile=- because it will cause libcurl to try to
+ * read from stdin when we connect.
+ */
+ if (strcmp (value, "-") == 0) {
+ nbdkit_error ("cookiefile parameter cannot be \"-\"");
+ return -1;
+ }
+ cookiefile = value;
+ }
+
+ else if (strcmp (key, "cookiejar") == 0) {
+ /* Reject cookiejar=- because it will cause libcurl to try to
+ * write to stdout.
+ */
+ if (strcmp (value, "-") == 0) {
+ nbdkit_error ("cookiejar parameter cannot be \"-\"");
+ return -1;
+ }
+ cookiejar = value;
+ }
+
+ else if (strcmp (key, "cookie-script") == 0) {
+ cookie_script = value;
+ }
+
+ else if (strcmp (key, "cookie-script-renew") == 0) {
+ if (nbdkit_parse_unsigned ("cookie-script-renew", value,
+ &cookie_script_renew) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "followlocation") == 0) {
+ r = nbdkit_parse_bool (value);
+ if (r == -1)
+ return -1;
+ followlocation = r;
+ }
+
+ else if (strcmp (key, "header") == 0) {
+ headers = curl_slist_append (headers, value);
+ if (headers == NULL) {
+ nbdkit_error ("curl_slist_append: %m");
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "header-script") == 0) {
+ header_script = value;
+ }
+
+ else if (strcmp (key, "header-script-renew") == 0) {
+ if (nbdkit_parse_unsigned ("header-script-renew", value,
+ &header_script_renew) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "http-version") == 0) {
+ if (strcmp (value, "none") == 0)
+ http_version = CURL_HTTP_VERSION_NONE;
+ else if (strcmp (value, "1.0") == 0)
+ http_version = CURL_HTTP_VERSION_1_0;
+ else if (strcmp (value, "1.1") == 0)
+ http_version = CURL_HTTP_VERSION_1_1;
+#ifdef HAVE_CURL_HTTP_VERSION_2_0
+ else if (strcmp (value, "2.0") == 0)
+ http_version = CURL_HTTP_VERSION_2_0;
+#endif
+#ifdef HAVE_CURL_HTTP_VERSION_2TLS
+ else if (strcmp (value, "2TLS") == 0)
+ http_version = CURL_HTTP_VERSION_2TLS;
+#endif
+#ifdef HAVE_CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE
+ else if (strcmp (value, "2-prior-knowledge") == 0)
+ http_version = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE;
+#endif
+#ifdef HAVE_CURL_HTTP_VERSION_3
+ else if (strcmp (value, "3") == 0)
+ http_version = CURL_HTTP_VERSION_3;
+#endif
+#ifdef HAVE_CURL_HTTP_VERSION_3ONLY
+ else if (strcmp (value, "3only") == 0)
+ http_version = CURL_HTTP_VERSION_3ONLY;
+#endif
+ else {
+ nbdkit_error ("unknown http-version: %s", value);
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "ipresolve") == 0) {
+ if (strcmp (value, "any") == 0 || strcmp (value, "whatever") == 0)
+ ipresolve = CURL_IPRESOLVE_WHATEVER;
+ else if (strcmp (value, "v4") == 0 || strcmp (value, "4") == 0)
+ ipresolve = CURL_IPRESOLVE_V4;
+ else if (strcmp (value, "v6") == 0 || strcmp (value, "6") == 0)
+ ipresolve = CURL_IPRESOLVE_V6;
+ else {
+ nbdkit_error ("unknown ipresolve: %s", value);
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "password") == 0) {
+ free (password);
+ if (nbdkit_read_password (value, &password) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "protocols") == 0) {
+#ifndef HAVE_CURLOPT_PROTOCOLS_STR
+ if (parse_protocols (value) == -1)
+ return -1;
+#else
+ protocols = value;
+#endif
+ }
+
+ else if (strcmp (key, "proxy") == 0) {
+ proxy = value;
+ }
+
+ else if (strcmp (key, "proxy-password") == 0) {
+ free (proxy_password);
+ if (nbdkit_read_password (value, &proxy_password) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "proxy-user") == 0)
+ proxy_user = value;
+
+ else if (strcmp (key, "resolve") == 0) {
+ resolves = curl_slist_append (headers, value);
+ if (resolves == NULL) {
+ nbdkit_error ("curl_slist_append: %m");
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "sslverify") == 0) {
+ r = nbdkit_parse_bool (value);
+ if (r == -1)
+ return -1;
+ sslverify = r;
+ }
+
+ else if (strcmp (key, "ssl-version") == 0) {
+ if (strcmp (value, "default") == 0)
+ ssl_version = CURL_SSLVERSION_DEFAULT;
+ else if (strcmp (value, "tlsv1") == 0)
+ ssl_version = CURL_SSLVERSION_TLSv1;
+ else if (strcmp (value, "sslv2") == 0)
+ ssl_version = CURL_SSLVERSION_SSLv2;
+ else if (strcmp (value, "sslv3") == 0)
+ ssl_version = CURL_SSLVERSION_SSLv3;
+ else if (strcmp (value, "tlsv1.0") == 0)
+ ssl_version = CURL_SSLVERSION_TLSv1_0;
+ else if (strcmp (value, "tlsv1.1") == 0)
+ ssl_version = CURL_SSLVERSION_TLSv1_1;
+ else if (strcmp (value, "tlsv1.2") == 0)
+ ssl_version = CURL_SSLVERSION_TLSv1_2;
+ else if (strcmp (value, "tlsv1.3") == 0)
+ ssl_version = CURL_SSLVERSION_TLSv1_3;
+#ifdef HAVE_CURL_SSLVERSION_MAX_DEFAULT
+ else if (strcmp (value, "max-default") == 0)
+ ssl_version = CURL_SSLVERSION_MAX_DEFAULT;
+#endif
+#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_0
+ else if (strcmp (value, "max-tlsv1.0") == 0)
+ ssl_version = CURL_SSLVERSION_MAX_TLSv1_0;
+#endif
+#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_1
+ else if (strcmp (value, "max-tlsv1.1") == 0)
+ ssl_version = CURL_SSLVERSION_MAX_TLSv1_1;
+#endif
+#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_2
+ else if (strcmp (value, "max-tlsv1.2") == 0)
+ ssl_version = CURL_SSLVERSION_MAX_TLSv1_2;
+#endif
+#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_3
+ else if (strcmp (value, "max-tlsv1.3") == 0)
+ ssl_version = CURL_SSLVERSION_MAX_TLSv1_3;
+#endif
+ else {
+ nbdkit_error ("unknown ssl-version: %s", value);
+ return -1;
+ }
+ }
+
+ else if (strcmp (key, "ssl-cipher-list") == 0)
+ ssl_cipher_list = value;
+
+ else if (strcmp (key, "tls13-ciphers") == 0)
+ tls13_ciphers = value;
+
+ else if (strcmp (key, "tcp-keepalive") == 0) {
+ r = nbdkit_parse_bool (value);
+ if (r == -1)
+ return -1;
+ tcp_keepalive = r;
+ }
+
+ else if (strcmp (key, "tcp-nodelay") == 0) {
+ r = nbdkit_parse_bool (value);
+ if (r == -1)
+ return -1;
+ tcp_nodelay = r;
+ }
+
+ else if (strcmp (key, "timeout") == 0) {
+ if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1)
+ return -1;
+#if LONG_MAX < UINT32_MAX
+ /* C17 5.2.4.2.1 requires that LONG_MAX is at least 2^31 - 1.
+ * However a large positive number might still exceed the limit.
+ */
+ if (timeout > LONG_MAX) {
+ nbdkit_error ("timeout is too large");
+ return -1;
+ }
+#endif
+ }
+
+ else if (strcmp (key, "unix-socket-path") == 0 ||
+ strcmp (key, "unix_socket_path") == 0)
+ unix_socket_path = value;
+
+ else if (strcmp (key, "url") == 0)
+ url = value;
+
+ else if (strcmp (key, "user") == 0)
+ user = value;
+
+ else if (strcmp (key, "user-agent") == 0)
+ user_agent = value;
+
+ else {
+ nbdkit_error ("unknown parameter '%s'", key);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Check the user did pass a url parameter. */
+int
+curl_config_complete (void)
+{
+ if (url == NULL) {
+ nbdkit_error ("you must supply the url=<URL> parameter "
+ "after the plugin name on the command line");
+ return -1;
+ }
+
+ if (headers && header_script) {
+ nbdkit_error ("header and header-script cannot be used at the same time");
+ return -1;
+ }
+
+ if (!header_script && header_script_renew) {
+ nbdkit_error ("header-script-renew cannot be used without header-script");
+ return -1;
+ }
+
+ if (cookie && cookie_script) {
+ nbdkit_error ("cookie and cookie-script cannot be used at the same time");
+ return -1;
+ }
+
+ if (!cookie_script && cookie_script_renew) {
+ nbdkit_error ("cookie-script-renew cannot be used without cookie-script");
+ return -1;
+ }
+
+ return 0;
+}
+
+const char *curl_config_help =
+ "cainfo=<CAINFO> Path to Certificate Authority file.\n"
+ "capath=<CAPATH> Path to directory with CA certificates.\n"
+ "connections=<N> Number of libcurl connections to use.\n"
+ "cookie=<COOKIE> Set HTTP/HTTPS cookies.\n"
+ "cookiefile= Enable cookie processing.\n"
+ "cookiefile=<FILENAME> Read cookies from file.\n"
+ "cookiejar=<FILENAME> Read and write cookies to jar.\n"
+ "cookie-script=<SCRIPT> Script to set HTTP/HTTPS cookies.\n"
+ "cookie-script-renew=<SECS> Time to renew HTTP/HTTPS cookies.\n"
+ "followlocation=false Do not follow redirects.\n"
+ "header=<HEADER> Set HTTP/HTTPS header.\n"
+ "header-script=<SCRIPT> Script to set HTTP/HTTPS headers.\n"
+ "header-script-renew=<SECS> Time to renew HTTP/HTTPS headers.\n"
+ "http-version=none|... Force a particular HTTP protocol.\n"
+ "ipresolve=any|v4|v6 Force IPv4 or IPv6.\n"
+ "password=<PASSWORD> The password for the user account.\n"
+ "protocols=PROTO,PROTO,.. Limit protocols allowed.\n"
+ "proxy=<PROXY> Set proxy URL.\n"
+ "proxy-password=<PASSWORD> The proxy password.\n"
+ "proxy-user=<USER> The proxy user.\n"
+ "resolve=<HOST>:<PORT>:<ADDR> Custom host to IP address resolution.\n"
+ "sslverify=false Do not verify SSL certificate of remote host.\n"
+ "ssl-cipher-list=C1:C2:.. Specify TLS/SSL cipher suites to be used.\n"
+ "ssl-version=<VERSION> Specify preferred TLS/SSL version.\n"
+ "tcp-keepalive=true Enable TCP keepalives.\n"
+ "tcp-nodelay=false Disable Nagles algorithm.\n"
+ "timeout=<TIMEOUT> Set the timeout for requests (seconds).\n"
+ "tls13-ciphers=C1:C2:.. Specify TLS 1.3 cipher suites to be used.\n"
+ "unix-socket-path=<PATH> Open Unix domain socket instead of TCP/IP.\n"
+ "url=<URL> (required) The disk image URL to serve.\n"
+ "user=<USER> The user to log in as.\n"
+ "user-agent=<USER-AGENT> Send user-agent header for HTTP/HTTPS."
+ ;
+
+/* Allocate and initialize a new libcurl handle. */
+struct curl_handle *
+allocate_handle (void)
+{
+ struct curl_handle *ch;
+ CURLcode r;
+
+ ch = calloc (1, sizeof *ch);
+ if (ch == NULL) {
+ nbdkit_error ("calloc: %m");
+ free (ch);
+ return NULL;
+ }
+
+ ch->c = curl_easy_init ();
+ if (ch->c == NULL) {
+ nbdkit_error ("curl_easy_init: failed: %m");
+ goto err;
+ }
+
+ if (curl_debug_verbose) {
+ /* NB: Constants must be explicitly long because the parameter is
+ * varargs.
+ */
+ curl_easy_setopt (ch->c, CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt (ch->c, CURLOPT_DEBUGFUNCTION, debug_cb);
+ }
+
+ curl_easy_setopt (ch->c, CURLOPT_ERRORBUFFER, ch->errbuf);
+
+ r = CURLE_OK;
+ if (unix_socket_path) {
+#if HAVE_CURLOPT_UNIX_SOCKET_PATH
+ r = curl_easy_setopt (ch->c, CURLOPT_UNIX_SOCKET_PATH, unix_socket_path);
+#else
+ r = CURLE_UNKNOWN_OPTION;
+#endif
+ }
+ if (r != CURLE_OK) {
+ display_curl_error (ch, r, "curl_easy_setopt: CURLOPT_UNIX_SOCKET_PATH");
+ goto err;
+ }
+
+ /* Set the URL. */
+ r = curl_easy_setopt (ch->c, CURLOPT_URL, url);
+ if (r != CURLE_OK) {
+ display_curl_error (ch, r, "curl_easy_setopt: CURLOPT_URL [%s]", url);
+ goto err;
+ }
+
+ /* Various options we always set.
+ *
+ * NB: Both here and below constants must be explicitly long because
+ * the parameter is varargs.
+ *
+ * For use of CURLOPT_NOSIGNAL see:
+ * https://curl.se/libcurl/c/CURLOPT_NOSIGNAL.html
+ */
+ curl_easy_setopt (ch->c, CURLOPT_NOSIGNAL, 1L);
+ curl_easy_setopt (ch->c, CURLOPT_AUTOREFERER, 1L);
+ if (followlocation)
+ curl_easy_setopt (ch->c, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt (ch->c, CURLOPT_FAILONERROR, 1L);
+
+ /* Options. */
+ if (cainfo) {
+ if (strlen (cainfo) == 0)
+ curl_easy_setopt (ch->c, CURLOPT_CAINFO, NULL);
+ else
+ curl_easy_setopt (ch->c, CURLOPT_CAINFO, cainfo);
+ }
+ if (capath)
+ curl_easy_setopt (ch->c, CURLOPT_CAPATH, capath);
+ if (cookie)
+ curl_easy_setopt (ch->c, CURLOPT_COOKIE, cookie);
+ if (cookiefile)
+ curl_easy_setopt (ch->c, CURLOPT_COOKIEFILE, cookiefile);
+ if (cookiejar)
+ curl_easy_setopt (ch->c, CURLOPT_COOKIEJAR, cookiejar);
+ if (headers)
+ curl_easy_setopt (ch->c, CURLOPT_HTTPHEADER, headers);
+ if (http_version != CURL_HTTP_VERSION_NONE)
+ curl_easy_setopt (ch->c, CURLOPT_HTTP_VERSION, (long) http_version);
+ if (ipresolve != CURL_IPRESOLVE_WHATEVER)
+ curl_easy_setopt (ch->c, CURLOPT_IPRESOLVE, (long) ipresolve);
+
+ if (password)
+ curl_easy_setopt (ch->c, CURLOPT_PASSWORD, password);
+#ifndef HAVE_CURLOPT_PROTOCOLS_STR
+ if (protocols != CURLPROTO_ALL) {
+ curl_easy_setopt (ch->c, CURLOPT_PROTOCOLS, protocols);
+ curl_easy_setopt (ch->c, CURLOPT_REDIR_PROTOCOLS, protocols);
+ }
+#else /* HAVE_CURLOPT_PROTOCOLS_STR (new in 7.85.0) */
+ if (protocols) {
+ curl_easy_setopt (ch->c, CURLOPT_PROTOCOLS_STR, protocols);
+ curl_easy_setopt (ch->c, CURLOPT_REDIR_PROTOCOLS_STR, protocols);
+ }
+#endif /* HAVE_CURLOPT_PROTOCOLS_STR */
+ if (proxy)
+ curl_easy_setopt (ch->c, CURLOPT_PROXY, proxy);
+ if (proxy_password)
+ curl_easy_setopt (ch->c, CURLOPT_PROXYPASSWORD, proxy_password);
+ if (proxy_user)
+ curl_easy_setopt (ch->c, CURLOPT_PROXYUSERNAME, proxy_user);
+ if (!sslverify) {
+ curl_easy_setopt (ch->c, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt (ch->c, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+ if (resolves)
+ curl_easy_setopt (ch->c, CURLOPT_RESOLVE, resolves);
+ if (ssl_version != CURL_SSLVERSION_DEFAULT)
+ curl_easy_setopt (ch->c, CURLOPT_SSLVERSION, (long) ssl_version);
+ if (ssl_cipher_list)
+ curl_easy_setopt (ch->c, CURLOPT_SSL_CIPHER_LIST, ssl_cipher_list);
+ if (tls13_ciphers) {
+#if (LIBCURL_VERSION_MAJOR > 7) || \
+ (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 61)
+ curl_easy_setopt (ch->c, CURLOPT_TLS13_CIPHERS, tls13_ciphers);
+#else
+ /* This is not available before curl-7.61 */
+ nbdkit_error ("tls13-ciphers is not supported in this build of "
+ "nbdkit-curl-plugin");
+ goto err;
+#endif
+ }
+ if (tcp_keepalive)
+ curl_easy_setopt (ch->c, CURLOPT_TCP_KEEPALIVE, 1L);
+ if (!tcp_nodelay)
+ curl_easy_setopt (ch->c, CURLOPT_TCP_NODELAY, 0L);
+ if (timeout > 0)
+ curl_easy_setopt (ch->c, CURLOPT_TIMEOUT, (long) timeout);
+ if (user)
+ curl_easy_setopt (ch->c, CURLOPT_USERNAME, user);
+ if (user_agent)
+ curl_easy_setopt (ch->c, CURLOPT_USERAGENT, user_agent);
+
+ if (get_content_length_accept_range (ch) == -1)
+ goto err;
+
+ /* Get set up for reading and writing. */
+ curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, NULL);
+ curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, NULL);
+ curl_easy_setopt (ch->c, CURLOPT_WRITEFUNCTION, write_cb);
+ curl_easy_setopt (ch->c, CURLOPT_WRITEDATA, ch);
+ /* These are only used if !readonly but we always register them. */
+ curl_easy_setopt (ch->c, CURLOPT_READFUNCTION, read_cb);
+ curl_easy_setopt (ch->c, CURLOPT_READDATA, ch);
+
+ return ch;
+
+ err:
+ if (ch->c)
+ curl_easy_cleanup (ch->c);
+ free (ch);
+ return NULL;
+}
+
+void
+free_handle (struct curl_handle *ch)
+{
+ curl_easy_cleanup (ch->c);
+ if (ch->headers_copy)
+ curl_slist_free_all (ch->headers_copy);
+ free (ch);
+}
+
+/* When using CURLOPT_VERBOSE, this callback is used to redirect
+ * messages to nbdkit_debug (instead of stderr).
+ */
+static int
+debug_cb (CURL *handle, curl_infotype type,
+ const char *data, size_t size, void *opaque)
+{
+ size_t origsize = size;
+ CLEANUP_FREE char *str;
+
+ /* The data parameter passed is NOT \0-terminated, but also it may
+ * have \n or \r\n line endings. The only sane way to deal with
+ * this is to copy the string. (The data strings may also be
+ * multi-line, but we don't deal with that here).
+ */
+ str = malloc (size + 1);
+ if (str == NULL)
+ goto out;
+ memcpy (str, data, size);
+ str[size] = '\0';
+
+ while (size > 0 && (str[size-1] == '\n' || str[size-1] == '\r')) {
+ str[size-1] = '\0';
+ size--;
+ }
+
+ switch (type) {
+ case CURLINFO_TEXT:
+ nbdkit_debug ("%s", str);
+ break;
+ case CURLINFO_HEADER_IN:
+ nbdkit_debug ("S: %s", str);
+ break;
+ case CURLINFO_HEADER_OUT:
+ nbdkit_debug ("C: %s", str);
+ break;
+ default:
+ /* Assume everything else is binary data that we cannot print. */
+ nbdkit_debug ("<data with size=%zu>", origsize);
+ }
+
+ out:
+ return 0;
+}
+
+/* NB: The terminology used by libcurl is confusing!
+ *
+ * WRITEFUNCTION / write_cb is used when reading from the remote server
+ * READFUNCTION / read_cb is used when writing to the remote server.
+ *
+ * We use the same terminology as libcurl here.
+ */
+
+static size_t
+write_cb (char *ptr, size_t size, size_t nmemb, void *opaque)
+{
+ struct curl_handle *ch = opaque;
+ size_t orig_realsize = size * nmemb;
+ size_t realsize = orig_realsize;
+
+ assert (ch->write_buf);
+
+ /* Don't read more than the requested amount of data, even if the
+ * server or libcurl sends more.
+ */
+ if (realsize > ch->write_count)
+ realsize = ch->write_count;
+
+ memcpy (ch->write_buf, ptr, realsize);
+
+ ch->write_count -= realsize;
+ ch->write_buf += realsize;
+
+ return orig_realsize;
+}
+
+static size_t
+read_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
+{
+ struct curl_handle *ch = opaque;
+ size_t realsize = size * nmemb;
+
+ assert (ch->read_buf);
+ if (realsize > ch->read_count)
+ realsize = ch->read_count;
+
+ memcpy (ptr, ch->read_buf, realsize);
+
+ ch->read_count -= realsize;
+ ch->read_buf += realsize;
+
+ return realsize;
+}
+
+/* Get the file size and also whether the remote HTTP server
+ * supports byte ranges.
+ */
+static int
+get_content_length_accept_range (struct curl_handle *ch)
+{
+ CURLcode r;
+ long code;
+#ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
+ curl_off_t o;
+#else
+ double d;
+#endif
+
+ /* We must run the scripts if necessary and set headers in the
+ * handle.
+ */
+ if (do_scripts (ch) == -1)
+ return -1;
+
+ /* Set this flag in the handle to false. The callback should set it
+ * to true if byte ranges are supported, which we check below.
+ */
+ ch->accept_range = false;
+
+ /* No Body, not nobody! This forces a HEAD request. */
+ curl_easy_setopt (ch->c, CURLOPT_NOBODY, 1L);
+ curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, ch);
+ r = curl_easy_perform (ch->c);
+ update_times (ch->c);
+ if (r != CURLE_OK) {
+ display_curl_error (ch, r,
+ "problem doing HEAD request to fetch size of URL [%s]",
+ url);
+
+ /* Get the HTTP status code, if available. */
+ r = curl_easy_getinfo (ch->c, CURLINFO_RESPONSE_CODE, &code);
+ if (r == CURLE_OK)
+ nbdkit_debug ("HTTP status code: %ld", code);
+ else
+ code = -1;
+
+ /* See comment on try_fallback_GET_method below. */
+ if (code != 403 || !try_fallback_GET_method (ch))
+ return -1;
+ }
+
+ /* Get the content length.
+ *
+ * Note there is some subtlety here: For web servers using chunked
+ * encoding, either the Content-Length header will not be present,
+ * or if present it should be ignored. (For such servers the only
+ * way to find out the true length would be to read all of the
+ * content, which we don't want to do).
+ *
+ * Curl itself resolves this for us. It will ignore the
+ * Content-Length header if chunked encoding is used, returning the
+ * length as -1 which we check below (see also
+ * curl:lib/http.c:Curl_http_size).
+ */
+#ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
+ r = curl_easy_getinfo (ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &o);
+ if (r != CURLE_OK) {
+ display_curl_error (ch, r,
+ "could not get length of remote file [%s]", url);
+ return -1;
+ }
+
+ if (o == -1) {
+ nbdkit_error ("could not get length of remote file [%s], "
+ "is the URL correct?", url);
+ return -1;
+ }
+
+ ch->exportsize = o;
+#else
+ r = curl_easy_getinfo (ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
+ if (r != CURLE_OK) {
+ display_curl_error (ch, r,
+ "could not get length of remote file [%s]", url);
+ return -1;
+ }
+
+ if (d == -1) {
+ nbdkit_error ("could not get length of remote file [%s], "
+ "is the URL correct?", url);
+ return -1;
+ }
+
+ ch->exportsize = d;
+#endif
+ nbdkit_debug ("content length: %" PRIi64, ch->exportsize);
+
+ /* If this is HTTP, check that byte ranges are supported. */
+ if (ascii_strncasecmp (url, "http://", strlen ("http://")) == 0 ||
+ ascii_strncasecmp (url, "https://", strlen ("https://")) == 0) {
+ if (!ch->accept_range) {
+ nbdkit_error ("server does not support 'range' (byte range) requests");
+ return -1;
+ }
+
+ nbdkit_debug ("accept range supported (for HTTP/HTTPS)");
+ }
+
+ return 0;
+}
+
+/* S3 servers can return 403 Forbidden for HEAD but still respond
+ * to GET, so we give it a second chance in that case.
+ * https://github.com/kubevirt/containerized-data-importer/issues/2737
+ *
+ * This function issues a GET request with a writefunction that always
+ * returns an error, thus effectively getting the headers but
+ * abandoning the transfer as soon as possible after.
+ */
+static bool
+try_fallback_GET_method (struct curl_handle *ch)
+{
+ CURLcode r;
+
+ nbdkit_debug ("attempting to fetch headers using GET method");
+
+ curl_easy_setopt (ch->c, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, ch);
+ curl_easy_setopt (ch->c, CURLOPT_WRITEFUNCTION, error_cb);
+ curl_easy_setopt (ch->c, CURLOPT_WRITEDATA, ch);
+ r = curl_easy_perform (ch->c);
+ update_times (ch->c);
+
+ /* We expect CURLE_WRITE_ERROR here, but CURLE_OK is possible too
+ * (eg if the remote has zero length). Other errors might happen
+ * but we ignore them since it is a fallback path.
+ */
+ return r == CURLE_OK || r == CURLE_WRITE_ERROR;
+}
+
+static size_t
+header_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
+{
+ struct curl_handle *ch = opaque;
+ size_t realsize = size * nmemb;
+ const char *header = ptr;
+ const char *end = header + realsize;
+ const char *accept_ranges = "accept-ranges:";
+ const char *bytes = "bytes";
+
+ if (realsize >= strlen (accept_ranges) &&
+ ascii_strncasecmp (header, accept_ranges, strlen (accept_ranges)) == 0) {
+ const char *p = strchr (header, ':') + 1;
+
+ /* Skip whitespace between the header name and value. */
+ while (p < end && *p && ascii_isspace (*p))
+ p++;
+
+ if (end - p >= strlen (bytes)
+ && strncmp (p, bytes, strlen (bytes)) == 0) {
+ /* Check that there is nothing but whitespace after the value. */
+ p += strlen (bytes);
+ while (p < end && *p && ascii_isspace (*p))
+ p++;
+
+ if (p == end || !*p)
+ ch->accept_range = true;
+ }
+ }
+
+ return realsize;
+}
+
+static size_t
+error_cb (char *ptr, size_t size, size_t nmemb, void *opaque)
+{
+#ifdef CURL_WRITEFUNC_ERROR
+ return CURL_WRITEFUNC_ERROR;
+#else
+ return 0; /* in older curl, any size < requested will also be an error */
+#endif
+}
diff --git a/plugins/curl/curl.c b/plugins/curl/curl.c
index 0d1fcfee..99a7e00b 100644
--- a/plugins/curl/curl.c
+++ b/plugins/curl/curl.c
@@ -52,46 +52,10 @@
#include "curldefs.h"
-/* Plugin configuration. */
-const char *url = NULL; /* required */
-
-const char *cainfo = NULL;
-const char *capath = NULL;
-unsigned connections = 4;
-char *cookie = NULL;
-const char *cookiefile = NULL;
-const char *cookiejar = NULL;
const char *cookie_script = NULL;
unsigned cookie_script_renew = 0;
-bool followlocation = true;
-struct curl_slist *headers = NULL;
const char *header_script = NULL;
unsigned header_script_renew = 0;
-long http_version = CURL_HTTP_VERSION_NONE;
-long ipresolve = CURL_IPRESOLVE_WHATEVER;
-char *password = NULL;
-#ifndef HAVE_CURLOPT_PROTOCOLS_STR
-long protocols = CURLPROTO_ALL;
-#else
-const char *protocols = NULL;
-#endif
-const char *proxy = NULL;
-char *proxy_password = NULL;
-const char *proxy_user = NULL;
-struct curl_slist *resolves = NULL;
-bool sslverify = true;
-const char *ssl_cipher_list = NULL;
-long ssl_version = CURL_SSLVERSION_DEFAULT;
-const char *tls13_ciphers = NULL;
-bool tcp_keepalive = false;
-bool tcp_nodelay = true;
-uint32_t timeout = 0;
-const char *unix_socket_path = NULL;
-const char *user = NULL;
-const char *user_agent = NULL;
-
-/* Use '-D curl.verbose=1' to set. */
-NBDKIT_DLL_PUBLIC int curl_debug_verbose = 0;
static void
curl_load (void)
@@ -110,438 +74,13 @@ curl_load (void)
static void
curl_unload (void)
{
- free (cookie);
- if (headers)
- curl_slist_free_all (headers);
- free (password);
- free (proxy_password);
- if (resolves)
- curl_slist_free_all (resolves);
+ unload_config ();
scripts_unload ();
unload_pool ();
display_times ();
curl_global_cleanup ();
}
-#ifndef HAVE_CURLOPT_PROTOCOLS_STR
-/* See <curl/curl.h> */
-static struct { const char *name; long bitmask; } curl_protocols[] = {
- { "http", CURLPROTO_HTTP },
- { "https", CURLPROTO_HTTPS },
- { "ftp", CURLPROTO_FTP },
- { "ftps", CURLPROTO_FTPS },
- { "scp", CURLPROTO_SCP },
- { "sftp", CURLPROTO_SFTP },
- { "telnet", CURLPROTO_TELNET },
- { "ldap", CURLPROTO_LDAP },
- { "ldaps", CURLPROTO_LDAPS },
- { "dict", CURLPROTO_DICT },
- { "file", CURLPROTO_FILE },
- { "tftp", CURLPROTO_TFTP },
- { "imap", CURLPROTO_IMAP },
- { "imaps", CURLPROTO_IMAPS },
- { "pop3", CURLPROTO_POP3 },
- { "pop3s", CURLPROTO_POP3S },
- { "smtp", CURLPROTO_SMTP },
- { "smtps", CURLPROTO_SMTPS },
- { "rtsp", CURLPROTO_RTSP },
- { "rtmp", CURLPROTO_RTMP },
- { "rtmpt", CURLPROTO_RTMPT },
- { "rtmpe", CURLPROTO_RTMPE },
- { "rtmpte", CURLPROTO_RTMPTE },
- { "rtmps", CURLPROTO_RTMPS },
- { "rtmpts", CURLPROTO_RTMPTS },
- { "gopher", CURLPROTO_GOPHER },
-#ifdef CURLPROTO_SMB
- { "smb", CURLPROTO_SMB },
-#endif
-#ifdef CURLPROTO_SMBS
- { "smbs", CURLPROTO_SMBS },
-#endif
-#ifdef CURLPROTO_MQTT
- { "mqtt", CURLPROTO_MQTT },
-#endif
- { NULL }
-};
-
-/* Parse the protocols parameter. */
-static int
-parse_protocols (const char *value)
-{
- size_t n, i;
-
- protocols = 0;
-
- while (*value) {
- n = strcspn (value, ",");
- for (i = 0; curl_protocols[i].name != NULL; ++i) {
- if (strlen (curl_protocols[i].name) == n &&
- strncmp (value, curl_protocols[i].name, n) == 0) {
- protocols |= curl_protocols[i].bitmask;
- goto found;
- }
- }
- nbdkit_error ("protocols: protocol name not found: %.*s", (int) n, value);
- return -1;
-
- found:
- value += n;
- if (*value == ',')
- value++;
- }
-
- if (protocols == 0) {
- nbdkit_error ("protocols: empty list of protocols is not allowed");
- return -1;
- }
-
- nbdkit_debug ("curl: protocols: %ld", protocols);
-
- return 0;
-}
-#endif /* !HAVE_CURLOPT_PROTOCOLS_STR */
-
-/* Called for each key=value passed on the command line. */
-static int
-curl_config (const char *key, const char *value)
-{
- int r;
-
- if (strcmp (key, "cainfo") == 0) {
- cainfo = value;
- }
-
- else if (strcmp (key, "capath") == 0) {
- capath = value;
- }
-
- else if (strcmp (key, "connections") == 0) {
- if (nbdkit_parse_unsigned ("connections", value,
- &connections) == -1)
- return -1;
- if (connections == 0) {
- nbdkit_error ("connections parameter must not be 0");
- return -1;
- }
- }
-
- else if (strcmp (key, "cookie") == 0) {
- free (cookie);
- if (nbdkit_read_password (value, &cookie) == -1)
- return -1;
- }
-
- else if (strcmp (key, "cookiefile") == 0) {
- /* Reject cookiefile=- because it will cause libcurl to try to
- * read from stdin when we connect.
- */
- if (strcmp (value, "-") == 0) {
- nbdkit_error ("cookiefile parameter cannot be \"-\"");
- return -1;
- }
- cookiefile = value;
- }
-
- else if (strcmp (key, "cookiejar") == 0) {
- /* Reject cookiejar=- because it will cause libcurl to try to
- * write to stdout.
- */
- if (strcmp (value, "-") == 0) {
- nbdkit_error ("cookiejar parameter cannot be \"-\"");
- return -1;
- }
- cookiejar = value;
- }
-
- else if (strcmp (key, "cookie-script") == 0) {
- cookie_script = value;
- }
-
- else if (strcmp (key, "cookie-script-renew") == 0) {
- if (nbdkit_parse_unsigned ("cookie-script-renew", value,
- &cookie_script_renew) == -1)
- return -1;
- }
-
- else if (strcmp (key, "followlocation") == 0) {
- r = nbdkit_parse_bool (value);
- if (r == -1)
- return -1;
- followlocation = r;
- }
-
- else if (strcmp (key, "header") == 0) {
- headers = curl_slist_append (headers, value);
- if (headers == NULL) {
- nbdkit_error ("curl_slist_append: %m");
- return -1;
- }
- }
-
- else if (strcmp (key, "header-script") == 0) {
- header_script = value;
- }
-
- else if (strcmp (key, "header-script-renew") == 0) {
- if (nbdkit_parse_unsigned ("header-script-renew", value,
- &header_script_renew) == -1)
- return -1;
- }
-
- else if (strcmp (key, "http-version") == 0) {
- if (strcmp (value, "none") == 0)
- http_version = CURL_HTTP_VERSION_NONE;
- else if (strcmp (value, "1.0") == 0)
- http_version = CURL_HTTP_VERSION_1_0;
- else if (strcmp (value, "1.1") == 0)
- http_version = CURL_HTTP_VERSION_1_1;
-#ifdef HAVE_CURL_HTTP_VERSION_2_0
- else if (strcmp (value, "2.0") == 0)
- http_version = CURL_HTTP_VERSION_2_0;
-#endif
-#ifdef HAVE_CURL_HTTP_VERSION_2TLS
- else if (strcmp (value, "2TLS") == 0)
- http_version = CURL_HTTP_VERSION_2TLS;
-#endif
-#ifdef HAVE_CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE
- else if (strcmp (value, "2-prior-knowledge") == 0)
- http_version = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE;
-#endif
-#ifdef HAVE_CURL_HTTP_VERSION_3
- else if (strcmp (value, "3") == 0)
- http_version = CURL_HTTP_VERSION_3;
-#endif
-#ifdef HAVE_CURL_HTTP_VERSION_3ONLY
- else if (strcmp (value, "3only") == 0)
- http_version = CURL_HTTP_VERSION_3ONLY;
-#endif
- else {
- nbdkit_error ("unknown http-version: %s", value);
- return -1;
- }
- }
-
- else if (strcmp (key, "ipresolve") == 0) {
- if (strcmp (value, "any") == 0 || strcmp (value, "whatever") == 0)
- ipresolve = CURL_IPRESOLVE_WHATEVER;
- else if (strcmp (value, "v4") == 0 || strcmp (value, "4") == 0)
- ipresolve = CURL_IPRESOLVE_V4;
- else if (strcmp (value, "v6") == 0 || strcmp (value, "6") == 0)
- ipresolve = CURL_IPRESOLVE_V6;
- else {
- nbdkit_error ("unknown ipresolve: %s", value);
- return -1;
- }
- }
-
- else if (strcmp (key, "password") == 0) {
- free (password);
- if (nbdkit_read_password (value, &password) == -1)
- return -1;
- }
-
- else if (strcmp (key, "protocols") == 0) {
-#ifndef HAVE_CURLOPT_PROTOCOLS_STR
- if (parse_protocols (value) == -1)
- return -1;
-#else
- protocols = value;
-#endif
- }
-
- else if (strcmp (key, "proxy") == 0) {
- proxy = value;
- }
-
- else if (strcmp (key, "proxy-password") == 0) {
- free (proxy_password);
- if (nbdkit_read_password (value, &proxy_password) == -1)
- return -1;
- }
-
- else if (strcmp (key, "proxy-user") == 0)
- proxy_user = value;
-
- else if (strcmp (key, "resolve") == 0) {
- resolves = curl_slist_append (headers, value);
- if (resolves == NULL) {
- nbdkit_error ("curl_slist_append: %m");
- return -1;
- }
- }
-
- else if (strcmp (key, "sslverify") == 0) {
- r = nbdkit_parse_bool (value);
- if (r == -1)
- return -1;
- sslverify = r;
- }
-
- else if (strcmp (key, "ssl-version") == 0) {
- if (strcmp (value, "default") == 0)
- ssl_version = CURL_SSLVERSION_DEFAULT;
- else if (strcmp (value, "tlsv1") == 0)
- ssl_version = CURL_SSLVERSION_TLSv1;
- else if (strcmp (value, "sslv2") == 0)
- ssl_version = CURL_SSLVERSION_SSLv2;
- else if (strcmp (value, "sslv3") == 0)
- ssl_version = CURL_SSLVERSION_SSLv3;
- else if (strcmp (value, "tlsv1.0") == 0)
- ssl_version = CURL_SSLVERSION_TLSv1_0;
- else if (strcmp (value, "tlsv1.1") == 0)
- ssl_version = CURL_SSLVERSION_TLSv1_1;
- else if (strcmp (value, "tlsv1.2") == 0)
- ssl_version = CURL_SSLVERSION_TLSv1_2;
- else if (strcmp (value, "tlsv1.3") == 0)
- ssl_version = CURL_SSLVERSION_TLSv1_3;
-#ifdef HAVE_CURL_SSLVERSION_MAX_DEFAULT
- else if (strcmp (value, "max-default") == 0)
- ssl_version = CURL_SSLVERSION_MAX_DEFAULT;
-#endif
-#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_0
- else if (strcmp (value, "max-tlsv1.0") == 0)
- ssl_version = CURL_SSLVERSION_MAX_TLSv1_0;
-#endif
-#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_1
- else if (strcmp (value, "max-tlsv1.1") == 0)
- ssl_version = CURL_SSLVERSION_MAX_TLSv1_1;
-#endif
-#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_2
- else if (strcmp (value, "max-tlsv1.2") == 0)
- ssl_version = CURL_SSLVERSION_MAX_TLSv1_2;
-#endif
-#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_3
- else if (strcmp (value, "max-tlsv1.3") == 0)
- ssl_version = CURL_SSLVERSION_MAX_TLSv1_3;
-#endif
- else {
- nbdkit_error ("unknown ssl-version: %s", value);
- return -1;
- }
- }
-
- else if (strcmp (key, "ssl-cipher-list") == 0)
- ssl_cipher_list = value;
-
- else if (strcmp (key, "tls13-ciphers") == 0)
- tls13_ciphers = value;
-
- else if (strcmp (key, "tcp-keepalive") == 0) {
- r = nbdkit_parse_bool (value);
- if (r == -1)
- return -1;
- tcp_keepalive = r;
- }
-
- else if (strcmp (key, "tcp-nodelay") == 0) {
- r = nbdkit_parse_bool (value);
- if (r == -1)
- return -1;
- tcp_nodelay = r;
- }
-
- else if (strcmp (key, "timeout") == 0) {
- if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1)
- return -1;
-#if LONG_MAX < UINT32_MAX
- /* C17 5.2.4.2.1 requires that LONG_MAX is at least 2^31 - 1.
- * However a large positive number might still exceed the limit.
- */
- if (timeout > LONG_MAX) {
- nbdkit_error ("timeout is too large");
- return -1;
- }
-#endif
- }
-
- else if (strcmp (key, "unix-socket-path") == 0 ||
- strcmp (key, "unix_socket_path") == 0)
- unix_socket_path = value;
-
- else if (strcmp (key, "url") == 0)
- url = value;
-
- else if (strcmp (key, "user") == 0)
- user = value;
-
- else if (strcmp (key, "user-agent") == 0)
- user_agent = value;
-
- else {
- nbdkit_error ("unknown parameter '%s'", key);
- return -1;
- }
-
- return 0;
-}
-
-/* Check the user did pass a url parameter. */
-static int
-curl_config_complete (void)
-{
- if (url == NULL) {
- nbdkit_error ("you must supply the url=<URL> parameter "
- "after the plugin name on the command line");
- return -1;
- }
-
- if (headers && header_script) {
- nbdkit_error ("header and header-script cannot be used at the same time");
- return -1;
- }
-
- if (!header_script && header_script_renew) {
- nbdkit_error ("header-script-renew cannot be used without header-script");
- return -1;
- }
-
- if (cookie && cookie_script) {
- nbdkit_error ("cookie and cookie-script cannot be used at the same time");
- return -1;
- }
-
- if (!cookie_script && cookie_script_renew) {
- nbdkit_error ("cookie-script-renew cannot be used without cookie-script");
- return -1;
- }
-
- return 0;
-}
-
-#define curl_config_help \
- "cainfo=<CAINFO> Path to Certificate Authority file.\n" \
- "capath=<CAPATH> Path to directory with CA certificates.\n" \
- "connections=<N> Number of libcurl connections to use.\n" \
- "cookie=<COOKIE> Set HTTP/HTTPS cookies.\n" \
- "cookiefile= Enable cookie processing.\n" \
- "cookiefile=<FILENAME> Read cookies from file.\n" \
- "cookiejar=<FILENAME> Read and write cookies to jar.\n" \
- "cookie-script=<SCRIPT> Script to set HTTP/HTTPS cookies.\n" \
- "cookie-script-renew=<SECS> Time to renew HTTP/HTTPS cookies.\n" \
- "followlocation=false Do not follow redirects.\n" \
- "header=<HEADER> Set HTTP/HTTPS header.\n" \
- "header-script=<SCRIPT> Script to set HTTP/HTTPS headers.\n" \
- "header-script-renew=<SECS> Time to renew HTTP/HTTPS headers.\n" \
- "http-version=none|... Force a particular HTTP protocol.\n" \
- "ipresolve=any|v4|v6 Force IPv4 or IPv6.\n" \
- "password=<PASSWORD> The password for the user account.\n" \
- "protocols=PROTO,PROTO,.. Limit protocols allowed.\n" \
- "proxy=<PROXY> Set proxy URL.\n" \
- "proxy-password=<PASSWORD> The proxy password.\n" \
- "proxy-user=<USER> The proxy user.\n" \
- "resolve=<HOST>:<PORT>:<ADDR> Custom host to IP address resolution.\n" \
- "sslverify=false Do not verify SSL certificate of remote host.\n" \
- "ssl-cipher-list=C1:C2:.. Specify TLS/SSL cipher suites to be used.\n" \
- "ssl-version=<VERSION> Specify preferred TLS/SSL version.\n" \
- "tcp-keepalive=true Enable TCP keepalives.\n" \
- "tcp-nodelay=false Disable Nagles algorithm.\n" \
- "timeout=<TIMEOUT> Set the timeout for requests (seconds).\n" \
- "tls13-ciphers=C1:C2:.. Specify TLS 1.3 cipher suites to be used.\n" \
- "unix-socket-path=<PATH> Open Unix domain socket instead of TCP/IP.\n" \
- "url=<URL> (required) The disk image URL to serve.\n" \
- "user=<USER> The user to log in as.\n" \
- "user-agent=<USER-AGENT> Send user-agent header for HTTP/HTTPS."
-
/* Create the per-connection handle. */
static void *
curl_open (int readonly)
@@ -704,7 +243,11 @@ static struct nbdkit_plugin plugin = {
.unload = curl_unload,
.config = curl_config,
.config_complete = curl_config_complete,
- .config_help = curl_config_help,
+ /* We can't set this here because of an obscure corner of the C
+ * language. "error: initializer element is not constant". See
+ * https://stackoverflow.com/questions/3025050
+ */
+ //.config_help = curl_config_help,
.magic_config_key = "url",
.open = curl_open,
.close = curl_close,
@@ -714,4 +257,11 @@ static struct nbdkit_plugin plugin = {
.pwrite = curl_pwrite,
};
+static void set_help (void) __attribute__ ((constructor));
+static void
+set_help (void)
+{
+ plugin.config_help = curl_config_help;
+}
+
NBDKIT_REGISTER_PLUGIN (plugin)
diff --git a/plugins/curl/curldefs.h b/plugins/curl/curldefs.h
index dd9791aa..9169b256 100644
--- a/plugins/curl/curldefs.h
+++ b/plugins/curl/curldefs.h
@@ -57,40 +57,12 @@
extern const char *url;
-extern const char *cainfo;
-extern const char *capath;
extern unsigned connections;
-extern char *cookie;
-extern const char *cookiefile;
-extern const char *cookiejar;
+
extern const char *cookie_script;
extern unsigned cookie_script_renew;
-extern bool followlocation;
-extern struct curl_slist *headers;
extern const char *header_script;
extern unsigned header_script_renew;
-extern long http_version;
-extern long ipresolve;
-extern char *password;
-#ifndef HAVE_CURLOPT_PROTOCOLS_STR
-extern long protocols;
-#else
-extern const char *protocols;
-#endif
-extern const char *proxy;
-extern char *proxy_password;
-extern const char *proxy_user;
-extern bool sslverify;
-extern const char *ssl_cipher_list;
-extern long ssl_version;
-extern struct curl_slist *resolves;
-extern const char *tls13_ciphers;
-extern bool tcp_keepalive;
-extern bool tcp_nodelay;
-extern uint32_t timeout;
-extern const char *unix_socket_path;
-extern const char *user;
-extern const char *user_agent;
extern int curl_debug_verbose;
@@ -130,6 +102,14 @@ struct curl_handle {
struct curl_slist *headers_copy;
};
+/* config.c */
+extern int curl_config (const char *key, const char *value);
+extern int curl_config_complete (void);
+extern const char *curl_config_help;
+extern void unload_config (void);
+extern struct curl_handle *allocate_handle (void);
+extern void free_handle (struct curl_handle *);
+
/* pool.c */
extern void load_pool (void);
extern void unload_pool (void);
diff --git a/plugins/curl/pool.c b/plugins/curl/pool.c
index 8db69a71..91e56f07 100644
--- a/plugins/curl/pool.c
+++ b/plugins/curl/pool.c
@@ -52,8 +52,6 @@
#include <nbdkit-plugin.h>
-#include "ascii-ctype.h"
-#include "ascii-string.h"
#include "cleanup.h"
#include "vector.h"
@@ -62,16 +60,7 @@
/* Use '-D curl.pool=1' to debug handle pool. */
NBDKIT_DLL_PUBLIC int curl_debug_pool = 0;
-static struct curl_handle *allocate_handle (void);
-static void free_handle (struct curl_handle *);
-static int debug_cb (CURL *handle, curl_infotype type,
- const char *data, size_t size, void *);
-static size_t write_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
-static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
-static int get_content_length_accept_range (struct curl_handle *ch);
-static bool try_fallback_GET_method (struct curl_handle *ch);
-static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
-static size_t error_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
+unsigned connections = 4;
/* This lock protects access to the curl_handles vector below. */
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
@@ -179,445 +168,3 @@ put_handle (struct curl_handle *ch)
if (waiting > 0)
pthread_cond_signal (&cond);
}
-
-/* Allocate and initialize a new libcurl handle. */
-static struct curl_handle *
-allocate_handle (void)
-{
- struct curl_handle *ch;
- CURLcode r;
-
- ch = calloc (1, sizeof *ch);
- if (ch == NULL) {
- nbdkit_error ("calloc: %m");
- free (ch);
- return NULL;
- }
-
- ch->c = curl_easy_init ();
- if (ch->c == NULL) {
- nbdkit_error ("curl_easy_init: failed: %m");
- goto err;
- }
-
- if (curl_debug_verbose) {
- /* NB: Constants must be explicitly long because the parameter is
- * varargs.
- */
- curl_easy_setopt (ch->c, CURLOPT_VERBOSE, 1L);
- curl_easy_setopt (ch->c, CURLOPT_DEBUGFUNCTION, debug_cb);
- }
-
- curl_easy_setopt (ch->c, CURLOPT_ERRORBUFFER, ch->errbuf);
-
- r = CURLE_OK;
- if (unix_socket_path) {
-#if HAVE_CURLOPT_UNIX_SOCKET_PATH
- r = curl_easy_setopt (ch->c, CURLOPT_UNIX_SOCKET_PATH, unix_socket_path);
-#else
- r = CURLE_UNKNOWN_OPTION;
-#endif
- }
- if (r != CURLE_OK) {
- display_curl_error (ch, r, "curl_easy_setopt: CURLOPT_UNIX_SOCKET_PATH");
- goto err;
- }
-
- /* Set the URL. */
- r = curl_easy_setopt (ch->c, CURLOPT_URL, url);
- if (r != CURLE_OK) {
- display_curl_error (ch, r, "curl_easy_setopt: CURLOPT_URL [%s]", url);
- goto err;
- }
-
- /* Various options we always set.
- *
- * NB: Both here and below constants must be explicitly long because
- * the parameter is varargs.
- *
- * For use of CURLOPT_NOSIGNAL see:
- * https://curl.se/libcurl/c/CURLOPT_NOSIGNAL.html
- */
- curl_easy_setopt (ch->c, CURLOPT_NOSIGNAL, 1L);
- curl_easy_setopt (ch->c, CURLOPT_AUTOREFERER, 1L);
- if (followlocation)
- curl_easy_setopt (ch->c, CURLOPT_FOLLOWLOCATION, 1L);
- curl_easy_setopt (ch->c, CURLOPT_FAILONERROR, 1L);
-
- /* Options. */
- if (cainfo) {
- if (strlen (cainfo) == 0)
- curl_easy_setopt (ch->c, CURLOPT_CAINFO, NULL);
- else
- curl_easy_setopt (ch->c, CURLOPT_CAINFO, cainfo);
- }
- if (capath)
- curl_easy_setopt (ch->c, CURLOPT_CAPATH, capath);
- if (cookie)
- curl_easy_setopt (ch->c, CURLOPT_COOKIE, cookie);
- if (cookiefile)
- curl_easy_setopt (ch->c, CURLOPT_COOKIEFILE, cookiefile);
- if (cookiejar)
- curl_easy_setopt (ch->c, CURLOPT_COOKIEJAR, cookiejar);
- if (headers)
- curl_easy_setopt (ch->c, CURLOPT_HTTPHEADER, headers);
- if (http_version != CURL_HTTP_VERSION_NONE)
- curl_easy_setopt (ch->c, CURLOPT_HTTP_VERSION, (long) http_version);
- if (ipresolve != CURL_IPRESOLVE_WHATEVER)
- curl_easy_setopt (ch->c, CURLOPT_IPRESOLVE, (long) ipresolve);
-
- if (password)
- curl_easy_setopt (ch->c, CURLOPT_PASSWORD, password);
-#ifndef HAVE_CURLOPT_PROTOCOLS_STR
- if (protocols != CURLPROTO_ALL) {
- curl_easy_setopt (ch->c, CURLOPT_PROTOCOLS, protocols);
- curl_easy_setopt (ch->c, CURLOPT_REDIR_PROTOCOLS, protocols);
- }
-#else /* HAVE_CURLOPT_PROTOCOLS_STR (new in 7.85.0) */
- if (protocols) {
- curl_easy_setopt (ch->c, CURLOPT_PROTOCOLS_STR, protocols);
- curl_easy_setopt (ch->c, CURLOPT_REDIR_PROTOCOLS_STR, protocols);
- }
-#endif /* HAVE_CURLOPT_PROTOCOLS_STR */
- if (proxy)
- curl_easy_setopt (ch->c, CURLOPT_PROXY, proxy);
- if (proxy_password)
- curl_easy_setopt (ch->c, CURLOPT_PROXYPASSWORD, proxy_password);
- if (proxy_user)
- curl_easy_setopt (ch->c, CURLOPT_PROXYUSERNAME, proxy_user);
- if (!sslverify) {
- curl_easy_setopt (ch->c, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt (ch->c, CURLOPT_SSL_VERIFYHOST, 0L);
- }
- if (resolves)
- curl_easy_setopt (ch->c, CURLOPT_RESOLVE, resolves);
- if (ssl_version != CURL_SSLVERSION_DEFAULT)
- curl_easy_setopt (ch->c, CURLOPT_SSLVERSION, (long) ssl_version);
- if (ssl_cipher_list)
- curl_easy_setopt (ch->c, CURLOPT_SSL_CIPHER_LIST, ssl_cipher_list);
- if (tls13_ciphers) {
-#if (LIBCURL_VERSION_MAJOR > 7) || \
- (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 61)
- curl_easy_setopt (ch->c, CURLOPT_TLS13_CIPHERS, tls13_ciphers);
-#else
- /* This is not available before curl-7.61 */
- nbdkit_error ("tls13-ciphers is not supported in this build of "
- "nbdkit-curl-plugin");
- goto err;
-#endif
- }
- if (tcp_keepalive)
- curl_easy_setopt (ch->c, CURLOPT_TCP_KEEPALIVE, 1L);
- if (!tcp_nodelay)
- curl_easy_setopt (ch->c, CURLOPT_TCP_NODELAY, 0L);
- if (timeout > 0)
- curl_easy_setopt (ch->c, CURLOPT_TIMEOUT, (long) timeout);
- if (user)
- curl_easy_setopt (ch->c, CURLOPT_USERNAME, user);
- if (user_agent)
- curl_easy_setopt (ch->c, CURLOPT_USERAGENT, user_agent);
-
- if (get_content_length_accept_range (ch) == -1)
- goto err;
-
- /* Get set up for reading and writing. */
- curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, NULL);
- curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, NULL);
- curl_easy_setopt (ch->c, CURLOPT_WRITEFUNCTION, write_cb);
- curl_easy_setopt (ch->c, CURLOPT_WRITEDATA, ch);
- /* These are only used if !readonly but we always register them. */
- curl_easy_setopt (ch->c, CURLOPT_READFUNCTION, read_cb);
- curl_easy_setopt (ch->c, CURLOPT_READDATA, ch);
-
- return ch;
-
- err:
- if (ch->c)
- curl_easy_cleanup (ch->c);
- free (ch);
- return NULL;
-}
-
-static void
-free_handle (struct curl_handle *ch)
-{
- curl_easy_cleanup (ch->c);
- if (ch->headers_copy)
- curl_slist_free_all (ch->headers_copy);
- free (ch);
-}
-
-/* When using CURLOPT_VERBOSE, this callback is used to redirect
- * messages to nbdkit_debug (instead of stderr).
- */
-static int
-debug_cb (CURL *handle, curl_infotype type,
- const char *data, size_t size, void *opaque)
-{
- size_t origsize = size;
- CLEANUP_FREE char *str;
-
- /* The data parameter passed is NOT \0-terminated, but also it may
- * have \n or \r\n line endings. The only sane way to deal with
- * this is to copy the string. (The data strings may also be
- * multi-line, but we don't deal with that here).
- */
- str = malloc (size + 1);
- if (str == NULL)
- goto out;
- memcpy (str, data, size);
- str[size] = '\0';
-
- while (size > 0 && (str[size-1] == '\n' || str[size-1] == '\r')) {
- str[size-1] = '\0';
- size--;
- }
-
- switch (type) {
- case CURLINFO_TEXT:
- nbdkit_debug ("%s", str);
- break;
- case CURLINFO_HEADER_IN:
- nbdkit_debug ("S: %s", str);
- break;
- case CURLINFO_HEADER_OUT:
- nbdkit_debug ("C: %s", str);
- break;
- default:
- /* Assume everything else is binary data that we cannot print. */
- nbdkit_debug ("<data with size=%zu>", origsize);
- }
-
- out:
- return 0;
-}
-
-/* NB: The terminology used by libcurl is confusing!
- *
- * WRITEFUNCTION / write_cb is used when reading from the remote server
- * READFUNCTION / read_cb is used when writing to the remote server.
- *
- * We use the same terminology as libcurl here.
- */
-
-static size_t
-write_cb (char *ptr, size_t size, size_t nmemb, void *opaque)
-{
- struct curl_handle *ch = opaque;
- size_t orig_realsize = size * nmemb;
- size_t realsize = orig_realsize;
-
- assert (ch->write_buf);
-
- /* Don't read more than the requested amount of data, even if the
- * server or libcurl sends more.
- */
- if (realsize > ch->write_count)
- realsize = ch->write_count;
-
- memcpy (ch->write_buf, ptr, realsize);
-
- ch->write_count -= realsize;
- ch->write_buf += realsize;
-
- return orig_realsize;
-}
-
-static size_t
-read_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
-{
- struct curl_handle *ch = opaque;
- size_t realsize = size * nmemb;
-
- assert (ch->read_buf);
- if (realsize > ch->read_count)
- realsize = ch->read_count;
-
- memcpy (ptr, ch->read_buf, realsize);
-
- ch->read_count -= realsize;
- ch->read_buf += realsize;
-
- return realsize;
-}
-
-/* Get the file size and also whether the remote HTTP server
- * supports byte ranges.
- */
-static int
-get_content_length_accept_range (struct curl_handle *ch)
-{
- CURLcode r;
- long code;
-#ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
- curl_off_t o;
-#else
- double d;
-#endif
-
- /* We must run the scripts if necessary and set headers in the
- * handle.
- */
- if (do_scripts (ch) == -1)
- return -1;
-
- /* Set this flag in the handle to false. The callback should set it
- * to true if byte ranges are supported, which we check below.
- */
- ch->accept_range = false;
-
- /* No Body, not nobody! This forces a HEAD request. */
- curl_easy_setopt (ch->c, CURLOPT_NOBODY, 1L);
- curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, header_cb);
- curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, ch);
- r = curl_easy_perform (ch->c);
- update_times (ch->c);
- if (r != CURLE_OK) {
- display_curl_error (ch, r,
- "problem doing HEAD request to fetch size of URL [%s]",
- url);
-
- /* Get the HTTP status code, if available. */
- r = curl_easy_getinfo (ch->c, CURLINFO_RESPONSE_CODE, &code);
- if (r == CURLE_OK)
- nbdkit_debug ("HTTP status code: %ld", code);
- else
- code = -1;
-
- /* See comment on try_fallback_GET_method below. */
- if (code != 403 || !try_fallback_GET_method (ch))
- return -1;
- }
-
- /* Get the content length.
- *
- * Note there is some subtlety here: For web servers using chunked
- * encoding, either the Content-Length header will not be present,
- * or if present it should be ignored. (For such servers the only
- * way to find out the true length would be to read all of the
- * content, which we don't want to do).
- *
- * Curl itself resolves this for us. It will ignore the
- * Content-Length header if chunked encoding is used, returning the
- * length as -1 which we check below (see also
- * curl:lib/http.c:Curl_http_size).
- */
-#ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
- r = curl_easy_getinfo (ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &o);
- if (r != CURLE_OK) {
- display_curl_error (ch, r,
- "could not get length of remote file [%s]", url);
- return -1;
- }
-
- if (o == -1) {
- nbdkit_error ("could not get length of remote file [%s], "
- "is the URL correct?", url);
- return -1;
- }
-
- ch->exportsize = o;
-#else
- r = curl_easy_getinfo (ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
- if (r != CURLE_OK) {
- display_curl_error (ch, r,
- "could not get length of remote file [%s]", url);
- return -1;
- }
-
- if (d == -1) {
- nbdkit_error ("could not get length of remote file [%s], "
- "is the URL correct?", url);
- return -1;
- }
-
- ch->exportsize = d;
-#endif
- nbdkit_debug ("content length: %" PRIi64, ch->exportsize);
-
- /* If this is HTTP, check that byte ranges are supported. */
- if (ascii_strncasecmp (url, "http://", strlen ("http://")) == 0 ||
- ascii_strncasecmp (url, "https://", strlen ("https://")) == 0) {
- if (!ch->accept_range) {
- nbdkit_error ("server does not support 'range' (byte range) requests");
- return -1;
- }
-
- nbdkit_debug ("accept range supported (for HTTP/HTTPS)");
- }
-
- return 0;
-}
-
-/* S3 servers can return 403 Forbidden for HEAD but still respond
- * to GET, so we give it a second chance in that case.
- * https://github.com/kubevirt/containerized-data-importer/issues/2737
- *
- * This function issues a GET request with a writefunction that always
- * returns an error, thus effectively getting the headers but
- * abandoning the transfer as soon as possible after.
- */
-static bool
-try_fallback_GET_method (struct curl_handle *ch)
-{
- CURLcode r;
-
- nbdkit_debug ("attempting to fetch headers using GET method");
-
- curl_easy_setopt (ch->c, CURLOPT_HTTPGET, 1L);
- curl_easy_setopt (ch->c, CURLOPT_HEADERFUNCTION, header_cb);
- curl_easy_setopt (ch->c, CURLOPT_HEADERDATA, ch);
- curl_easy_setopt (ch->c, CURLOPT_WRITEFUNCTION, error_cb);
- curl_easy_setopt (ch->c, CURLOPT_WRITEDATA, ch);
- r = curl_easy_perform (ch->c);
- update_times (ch->c);
-
- /* We expect CURLE_WRITE_ERROR here, but CURLE_OK is possible too
- * (eg if the remote has zero length). Other errors might happen
- * but we ignore them since it is a fallback path.
- */
- return r == CURLE_OK || r == CURLE_WRITE_ERROR;
-}
-
-static size_t
-header_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
-{
- struct curl_handle *ch = opaque;
- size_t realsize = size * nmemb;
- const char *header = ptr;
- const char *end = header + realsize;
- const char *accept_ranges = "accept-ranges:";
- const char *bytes = "bytes";
-
- if (realsize >= strlen (accept_ranges) &&
- ascii_strncasecmp (header, accept_ranges, strlen (accept_ranges)) == 0) {
- const char *p = strchr (header, ':') + 1;
-
- /* Skip whitespace between the header name and value. */
- while (p < end && *p && ascii_isspace (*p))
- p++;
-
- if (end - p >= strlen (bytes)
- && strncmp (p, bytes, strlen (bytes)) == 0) {
- /* Check that there is nothing but whitespace after the value. */
- p += strlen (bytes);
- while (p < end && *p && ascii_isspace (*p))
- p++;
-
- if (p == end || !*p)
- ch->accept_range = true;
- }
- }
-
- return realsize;
-}
-
-static size_t
-error_cb (char *ptr, size_t size, size_t nmemb, void *opaque)
-{
-#ifdef CURL_WRITEFUNC_ERROR
- return CURL_WRITEFUNC_ERROR;
-#else
- return 0; /* in older curl, any size < requested will also be an error */
-#endif
-}
--
2.39.3