|
|
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 Nagle’s 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 Nagle’s 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
|
|
|
|