curl: Split out the libcurl handle from the plugin handle
[nbdkit.git] / plugins / curl / curl.c
blobd90438e30baf73733aa1a65f7528ddcd0e7ebf10
1 /* nbdkit
2 * Copyright (C) 2014-2023 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdbool.h>
39 #include <stdint.h>
40 #include <inttypes.h>
41 #include <limits.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <errno.h>
45 #include <assert.h>
47 #include <curl/curl.h>
49 #include <nbdkit-plugin.h>
51 #include "ascii-ctype.h"
52 #include "ascii-string.h"
53 #include "cleanup.h"
55 #include "curldefs.h"
57 /* Plugin configuration. */
58 const char *url = NULL; /* required */
60 const char *cainfo = NULL;
61 const char *capath = NULL;
62 char *cookie = NULL;
63 const char *cookiefile = NULL;
64 const char *cookiejar = NULL;
65 const char *cookie_script = NULL;
66 unsigned cookie_script_renew = 0;
67 bool followlocation = true;
68 struct curl_slist *headers = NULL;
69 const char *header_script = NULL;
70 unsigned header_script_renew = 0;
71 char *password = NULL;
72 #ifndef HAVE_CURLOPT_PROTOCOLS_STR
73 long protocols = CURLPROTO_ALL;
74 #else
75 const char *protocols = NULL;
76 #endif
77 const char *proxy = NULL;
78 char *proxy_password = NULL;
79 const char *proxy_user = NULL;
80 bool sslverify = true;
81 const char *ssl_cipher_list = NULL;
82 const char *ssl_version = NULL;
83 const char *tls13_ciphers = NULL;
84 bool tcp_keepalive = false;
85 bool tcp_nodelay = true;
86 uint32_t timeout = 0;
87 const char *unix_socket_path = NULL;
88 const char *user = NULL;
89 const char *user_agent = NULL;
91 /* Use '-D curl.verbose=1' to set. */
92 NBDKIT_DLL_PUBLIC int curl_debug_verbose = 0;
94 static void
95 curl_load (void)
97 CURLcode r;
99 r = curl_global_init (CURL_GLOBAL_DEFAULT);
100 if (r != CURLE_OK) {
101 nbdkit_error ("libcurl initialization failed: %d", (int) r);
102 exit (EXIT_FAILURE);
106 static void
107 curl_unload (void)
109 free (cookie);
110 if (headers)
111 curl_slist_free_all (headers);
112 free (password);
113 free (proxy_password);
114 scripts_unload ();
115 curl_global_cleanup ();
118 #ifndef HAVE_CURLOPT_PROTOCOLS_STR
119 /* See <curl/curl.h> */
120 static struct { const char *name; long bitmask; } curl_protocols[] = {
121 { "http", CURLPROTO_HTTP },
122 { "https", CURLPROTO_HTTPS },
123 { "ftp", CURLPROTO_FTP },
124 { "ftps", CURLPROTO_FTPS },
125 { "scp", CURLPROTO_SCP },
126 { "sftp", CURLPROTO_SFTP },
127 { "telnet", CURLPROTO_TELNET },
128 { "ldap", CURLPROTO_LDAP },
129 { "ldaps", CURLPROTO_LDAPS },
130 { "dict", CURLPROTO_DICT },
131 { "file", CURLPROTO_FILE },
132 { "tftp", CURLPROTO_TFTP },
133 { "imap", CURLPROTO_IMAP },
134 { "imaps", CURLPROTO_IMAPS },
135 { "pop3", CURLPROTO_POP3 },
136 { "pop3s", CURLPROTO_POP3S },
137 { "smtp", CURLPROTO_SMTP },
138 { "smtps", CURLPROTO_SMTPS },
139 { "rtsp", CURLPROTO_RTSP },
140 { "rtmp", CURLPROTO_RTMP },
141 { "rtmpt", CURLPROTO_RTMPT },
142 { "rtmpe", CURLPROTO_RTMPE },
143 { "rtmpte", CURLPROTO_RTMPTE },
144 { "rtmps", CURLPROTO_RTMPS },
145 { "rtmpts", CURLPROTO_RTMPTS },
146 { "gopher", CURLPROTO_GOPHER },
147 #ifdef CURLPROTO_SMB
148 { "smb", CURLPROTO_SMB },
149 #endif
150 #ifdef CURLPROTO_SMBS
151 { "smbs", CURLPROTO_SMBS },
152 #endif
153 #ifdef CURLPROTO_MQTT
154 { "mqtt", CURLPROTO_MQTT },
155 #endif
156 { NULL }
159 /* Parse the protocols parameter. */
160 static int
161 parse_protocols (const char *value)
163 size_t n, i;
165 protocols = 0;
167 while (*value) {
168 n = strcspn (value, ",");
169 for (i = 0; curl_protocols[i].name != NULL; ++i) {
170 if (strlen (curl_protocols[i].name) == n &&
171 strncmp (value, curl_protocols[i].name, n) == 0) {
172 protocols |= curl_protocols[i].bitmask;
173 goto found;
176 nbdkit_error ("protocols: protocol name not found: %.*s", (int) n, value);
177 return -1;
179 found:
180 value += n;
181 if (*value == ',')
182 value++;
185 if (protocols == 0) {
186 nbdkit_error ("protocols: empty list of protocols is not allowed");
187 return -1;
190 nbdkit_debug ("curl: protocols: %ld", protocols);
192 return 0;
194 #endif /* !HAVE_CURLOPT_PROTOCOLS_STR */
196 /* Called for each key=value passed on the command line. */
197 static int
198 curl_config (const char *key, const char *value)
200 int r;
202 if (strcmp (key, "cainfo") == 0) {
203 cainfo = value;
206 else if (strcmp (key, "capath") == 0) {
207 capath = value;
210 else if (strcmp (key, "cookie") == 0) {
211 free (cookie);
212 if (nbdkit_read_password (value, &cookie) == -1)
213 return -1;
216 else if (strcmp (key, "cookiefile") == 0) {
217 /* Reject cookiefile=- because it will cause libcurl to try to
218 * read from stdin when we connect.
220 if (strcmp (value, "-") == 0) {
221 nbdkit_error ("cookiefile parameter cannot be \"-\"");
222 return -1;
224 cookiefile = value;
227 else if (strcmp (key, "cookiejar") == 0) {
228 /* Reject cookiejar=- because it will cause libcurl to try to
229 * write to stdout.
231 if (strcmp (value, "-") == 0) {
232 nbdkit_error ("cookiejar parameter cannot be \"-\"");
233 return -1;
235 cookiejar = value;
238 else if (strcmp (key, "cookie-script") == 0) {
239 cookie_script = value;
242 else if (strcmp (key, "cookie-script-renew") == 0) {
243 if (nbdkit_parse_unsigned ("cookie-script-renew", value,
244 &cookie_script_renew) == -1)
245 return -1;
248 else if (strcmp (key, "followlocation") == 0) {
249 r = nbdkit_parse_bool (value);
250 if (r == -1)
251 return -1;
252 followlocation = r;
255 else if (strcmp (key, "header") == 0) {
256 headers = curl_slist_append (headers, value);
257 if (headers == NULL) {
258 nbdkit_error ("curl_slist_append: %m");
259 return -1;
263 else if (strcmp (key, "header-script") == 0) {
264 header_script = value;
267 else if (strcmp (key, "header-script-renew") == 0) {
268 if (nbdkit_parse_unsigned ("header-script-renew", value,
269 &header_script_renew) == -1)
270 return -1;
273 else if (strcmp (key, "password") == 0) {
274 free (password);
275 if (nbdkit_read_password (value, &password) == -1)
276 return -1;
279 else if (strcmp (key, "protocols") == 0) {
280 #ifndef HAVE_CURLOPT_PROTOCOLS_STR
281 if (parse_protocols (value) == -1)
282 return -1;
283 #else
284 protocols = value;
285 #endif
288 else if (strcmp (key, "proxy") == 0) {
289 proxy = value;
292 else if (strcmp (key, "proxy-password") == 0) {
293 free (proxy_password);
294 if (nbdkit_read_password (value, &proxy_password) == -1)
295 return -1;
298 else if (strcmp (key, "proxy-user") == 0)
299 proxy_user = value;
301 else if (strcmp (key, "sslverify") == 0) {
302 r = nbdkit_parse_bool (value);
303 if (r == -1)
304 return -1;
305 sslverify = r;
308 else if (strcmp (key, "ssl-version") == 0)
309 ssl_version = value;
311 else if (strcmp (key, "ssl-cipher-list") == 0)
312 ssl_cipher_list = value;
314 else if (strcmp (key, "tls13-ciphers") == 0)
315 tls13_ciphers = value;
317 else if (strcmp (key, "tcp-keepalive") == 0) {
318 r = nbdkit_parse_bool (value);
319 if (r == -1)
320 return -1;
321 tcp_keepalive = r;
324 else if (strcmp (key, "tcp-nodelay") == 0) {
325 r = nbdkit_parse_bool (value);
326 if (r == -1)
327 return -1;
328 tcp_nodelay = r;
331 else if (strcmp (key, "timeout") == 0) {
332 if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1)
333 return -1;
334 #if LONG_MAX < UINT32_MAX
335 /* C17 5.2.4.2.1 requires that LONG_MAX is at least 2^31 - 1.
336 * However a large positive number might still exceed the limit.
338 if (timeout > LONG_MAX) {
339 nbdkit_error ("timeout is too large");
340 return -1;
342 #endif
345 else if (strcmp (key, "unix-socket-path") == 0 ||
346 strcmp (key, "unix_socket_path") == 0)
347 unix_socket_path = value;
349 else if (strcmp (key, "url") == 0)
350 url = value;
352 else if (strcmp (key, "user") == 0)
353 user = value;
355 else if (strcmp (key, "user-agent") == 0)
356 user_agent = value;
358 else {
359 nbdkit_error ("unknown parameter '%s'", key);
360 return -1;
363 return 0;
366 /* Check the user did pass a url parameter. */
367 static int
368 curl_config_complete (void)
370 if (url == NULL) {
371 nbdkit_error ("you must supply the url=<URL> parameter "
372 "after the plugin name on the command line");
373 return -1;
376 if (headers && header_script) {
377 nbdkit_error ("header and header-script cannot be used at the same time");
378 return -1;
381 if (!header_script && header_script_renew) {
382 nbdkit_error ("header-script-renew cannot be used without header-script");
383 return -1;
386 if (cookie && cookie_script) {
387 nbdkit_error ("cookie and cookie-script cannot be used at the same time");
388 return -1;
391 if (!cookie_script && cookie_script_renew) {
392 nbdkit_error ("cookie-script-renew cannot be used without cookie-script");
393 return -1;
396 return 0;
399 #define curl_config_help \
400 "cainfo=<CAINFO> Path to Certificate Authority file.\n" \
401 "capath=<CAPATH> Path to directory with CA certificates.\n" \
402 "cookie=<COOKIE> Set HTTP/HTTPS cookies.\n" \
403 "cookiefile= Enable cookie processing.\n" \
404 "cookiefile=<FILENAME> Read cookies from file.\n" \
405 "cookiejar=<FILENAME> Read and write cookies to jar.\n" \
406 "cookie-script=<SCRIPT> Script to set HTTP/HTTPS cookies.\n" \
407 "cookie-script-renew=<SECS> Time to renew HTTP/HTTPS cookies.\n" \
408 "followlocation=false Do not follow redirects.\n" \
409 "header=<HEADER> Set HTTP/HTTPS header.\n" \
410 "header-script=<SCRIPT> Script to set HTTP/HTTPS headers.\n" \
411 "header-script-renew=<SECS> Time to renew HTTP/HTTPS headers.\n" \
412 "password=<PASSWORD> The password for the user account.\n" \
413 "protocols=PROTO,PROTO,.. Limit protocols allowed.\n" \
414 "proxy=<PROXY> Set proxy URL.\n" \
415 "proxy-password=<PASSWORD> The proxy password.\n" \
416 "proxy-user=<USER> The proxy user.\n" \
417 "timeout=<TIMEOUT> Set the timeout for requests (seconds).\n" \
418 "sslverify=false Do not verify SSL certificate of remote host.\n" \
419 "ssl-version=<VERSION> Specify preferred TLS/SSL version.\n " \
420 "ssl-cipher-list=C1:C2:.. Specify TLS/SSL cipher suites to be used.\n" \
421 "tls13-ciphers=C1:C2:.. Specify TLS 1.3 cipher suites to be used.\n" \
422 "tcp-keepalive=true Enable TCP keepalives.\n" \
423 "tcp-nodelay=false Disable Nagle’s algorithm.\n" \
424 "unix-socket-path=<PATH> Open Unix domain socket instead of TCP/IP.\n" \
425 "url=<URL> (required) The disk image URL to serve.\n" \
426 "user=<USER> The user to log in as.\n" \
427 "user-agent=<USER-AGENT> Send user-agent header for HTTP/HTTPS."
429 /* Translate CURLcode to nbdkit_error. */
430 #define display_curl_error(ch, r, fs, ...) \
431 do { \
432 nbdkit_error ((fs ": %s: %s"), ## __VA_ARGS__, \
433 curl_easy_strerror ((r)), (ch)->errbuf); \
434 } while (0)
436 static int debug_cb (CURL *handle, curl_infotype type,
437 const char *data, size_t size, void *);
438 static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
439 static size_t write_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
440 static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
442 /* Create the per-connection handle. */
443 static void *
444 curl_open (int readonly)
446 struct handle *h;
447 CURLcode r;
448 #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
449 curl_off_t o;
450 #else
451 double d;
452 #endif
454 h = calloc (1, sizeof *h);
455 if (h == NULL) {
456 nbdkit_error ("calloc: %m");
457 return NULL;
459 h->readonly = readonly;
461 h->ch = calloc (1, sizeof *h->ch);
462 if (h->ch == NULL) {
463 nbdkit_error ("calloc: %m");
464 free (h);
465 return NULL;
468 h->ch->c = curl_easy_init ();
469 if (h->ch->c == NULL) {
470 nbdkit_error ("curl_easy_init: failed: %m");
471 goto err;
474 if (curl_debug_verbose) {
475 /* NB: Constants must be explicitly long because the parameter is
476 * varargs.
478 curl_easy_setopt (h->ch->c, CURLOPT_VERBOSE, 1L);
479 curl_easy_setopt (h->ch->c, CURLOPT_DEBUGFUNCTION, debug_cb);
482 curl_easy_setopt (h->ch->c, CURLOPT_ERRORBUFFER, h->ch->errbuf);
484 r = CURLE_OK;
485 if (unix_socket_path) {
486 #if HAVE_CURLOPT_UNIX_SOCKET_PATH
487 r = curl_easy_setopt (h->ch->c, CURLOPT_UNIX_SOCKET_PATH, unix_socket_path);
488 #else
489 r = CURLE_UNKNOWN_OPTION;
490 #endif
492 if (r != CURLE_OK) {
493 display_curl_error (h->ch, r, "curl_easy_setopt: CURLOPT_UNIX_SOCKET_PATH");
494 goto err;
497 /* Set the URL. */
498 r = curl_easy_setopt (h->ch->c, CURLOPT_URL, url);
499 if (r != CURLE_OK) {
500 display_curl_error (h->ch, r, "curl_easy_setopt: CURLOPT_URL [%s]", url);
501 goto err;
504 /* Various options we always set.
506 * NB: Both here and below constants must be explicitly long because
507 * the parameter is varargs.
509 * For use of CURLOPT_NOSIGNAL see:
510 * https://curl.se/libcurl/c/CURLOPT_NOSIGNAL.html
512 curl_easy_setopt (h->ch->c, CURLOPT_NOSIGNAL, 1L);
513 curl_easy_setopt (h->ch->c, CURLOPT_AUTOREFERER, 1L);
514 if (followlocation)
515 curl_easy_setopt (h->ch->c, CURLOPT_FOLLOWLOCATION, 1L);
516 curl_easy_setopt (h->ch->c, CURLOPT_FAILONERROR, 1L);
518 /* Options. */
519 if (cainfo) {
520 if (strlen (cainfo) == 0)
521 curl_easy_setopt (h->ch->c, CURLOPT_CAINFO, NULL);
522 else
523 curl_easy_setopt (h->ch->c, CURLOPT_CAINFO, cainfo);
525 if (capath)
526 curl_easy_setopt (h->ch->c, CURLOPT_CAPATH, capath);
527 if (cookie)
528 curl_easy_setopt (h->ch->c, CURLOPT_COOKIE, cookie);
529 if (cookiefile)
530 curl_easy_setopt (h->ch->c, CURLOPT_COOKIEFILE, cookiefile);
531 if (cookiejar)
532 curl_easy_setopt (h->ch->c, CURLOPT_COOKIEJAR, cookiejar);
533 if (headers)
534 curl_easy_setopt (h->ch->c, CURLOPT_HTTPHEADER, headers);
535 if (password)
536 curl_easy_setopt (h->ch->c, CURLOPT_PASSWORD, password);
537 #ifndef HAVE_CURLOPT_PROTOCOLS_STR
538 if (protocols != CURLPROTO_ALL) {
539 curl_easy_setopt (h->ch->c, CURLOPT_PROTOCOLS, protocols);
540 curl_easy_setopt (h->ch->c, CURLOPT_REDIR_PROTOCOLS, protocols);
542 #else /* HAVE_CURLOPT_PROTOCOLS_STR (new in 7.85.0) */
543 if (protocols) {
544 curl_easy_setopt (h->ch->c, CURLOPT_PROTOCOLS_STR, protocols);
545 curl_easy_setopt (h->ch->c, CURLOPT_REDIR_PROTOCOLS_STR, protocols);
547 #endif /* HAVE_CURLOPT_PROTOCOLS_STR */
548 if (proxy)
549 curl_easy_setopt (h->ch->c, CURLOPT_PROXY, proxy);
550 if (proxy_password)
551 curl_easy_setopt (h->ch->c, CURLOPT_PROXYPASSWORD, proxy_password);
552 if (proxy_user)
553 curl_easy_setopt (h->ch->c, CURLOPT_PROXYUSERNAME, proxy_user);
554 if (!sslverify) {
555 curl_easy_setopt (h->ch->c, CURLOPT_SSL_VERIFYPEER, 0L);
556 curl_easy_setopt (h->ch->c, CURLOPT_SSL_VERIFYHOST, 0L);
558 if (ssl_version) {
559 if (strcmp (ssl_version, "tlsv1") == 0)
560 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
561 else if (strcmp (ssl_version, "sslv2") == 0)
562 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
563 else if (strcmp (ssl_version, "sslv3") == 0)
564 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
565 else if (strcmp (ssl_version, "tlsv1.0") == 0)
566 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0);
567 else if (strcmp (ssl_version, "tlsv1.1") == 0)
568 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1);
569 else if (strcmp (ssl_version, "tlsv1.2") == 0)
570 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
571 else if (strcmp (ssl_version, "tlsv1.3") == 0)
572 curl_easy_setopt (h->ch->c, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3);
573 else {
574 display_curl_error (h->ch, r, "curl_easy_setopt: CURLOPT_SSLVERSION [%s]",
575 ssl_version);
576 goto err;
580 if (ssl_cipher_list)
581 curl_easy_setopt (h->ch->c, CURLOPT_SSL_CIPHER_LIST, ssl_cipher_list);
582 if (tls13_ciphers) {
583 #if (LIBCURL_VERSION_MAJOR > 7) || \
584 (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 61)
585 curl_easy_setopt (h->ch->c, CURLOPT_TLS13_CIPHERS, tls13_ciphers);
586 #else
587 /* This is not available before curl-7.61 */
588 nbdkit_error ("tls13-ciphers is not supported in this build of "
589 "nbdkit-curl-plugin");
590 goto err;
591 #endif
593 if (tcp_keepalive)
594 curl_easy_setopt (h->ch->c, CURLOPT_TCP_KEEPALIVE, 1L);
595 if (!tcp_nodelay)
596 curl_easy_setopt (h->ch->c, CURLOPT_TCP_NODELAY, 0L);
597 if (timeout > 0)
598 /* NB: The cast is required here because the parameter is varargs
599 * treated as long, and not type safe.
601 curl_easy_setopt (h->ch->c, CURLOPT_TIMEOUT, (long) timeout);
602 if (user)
603 curl_easy_setopt (h->ch->c, CURLOPT_USERNAME, user);
604 if (user_agent)
605 curl_easy_setopt (h->ch->c, CURLOPT_USERAGENT, user_agent);
607 /* Get the file size and also whether the remote HTTP server
608 * supports byte ranges.
610 * We must run the scripts if necessary and set headers in the
611 * handle.
613 if (do_scripts (h->ch) == -1) goto err;
614 h->ch->accept_range = false;
615 curl_easy_setopt (h->ch->c, CURLOPT_NOBODY, 1L); /* No Body, not nobody! */
616 curl_easy_setopt (h->ch->c, CURLOPT_HEADERFUNCTION, header_cb);
617 curl_easy_setopt (h->ch->c, CURLOPT_HEADERDATA, h->ch);
618 r = curl_easy_perform (h->ch->c);
619 if (r != CURLE_OK) {
620 display_curl_error (h->ch, r,
621 "problem doing HEAD request to fetch size of URL [%s]",
622 url);
623 goto err;
626 #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
627 r = curl_easy_getinfo (h->ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &o);
628 if (r != CURLE_OK) {
629 display_curl_error (h->ch, r,
630 "could not get length of remote file [%s]", url);
631 goto err;
634 if (o == -1) {
635 nbdkit_error ("could not get length of remote file [%s], "
636 "is the URL correct?", url);
637 goto err;
640 h->ch->exportsize = o;
641 #else
642 r = curl_easy_getinfo (h->ch->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
643 if (r != CURLE_OK) {
644 display_curl_error (h->ch, r,
645 "could not get length of remote file [%s]", url);
646 goto err;
649 if (d == -1) {
650 nbdkit_error ("could not get length of remote file [%s], "
651 "is the URL correct?", url);
652 goto err;
655 h->ch->exportsize = d;
656 #endif
657 nbdkit_debug ("content length: %" PRIi64, h->ch->exportsize);
659 if (ascii_strncasecmp (url, "http://", strlen ("http://")) == 0 ||
660 ascii_strncasecmp (url, "https://", strlen ("https://")) == 0) {
661 if (!h->ch->accept_range) {
662 nbdkit_error ("server does not support 'range' (byte range) requests");
663 goto err;
666 nbdkit_debug ("accept range supported (for HTTP/HTTPS)");
669 /* Get set up for reading and writing. */
670 curl_easy_setopt (h->ch->c, CURLOPT_HEADERFUNCTION, NULL);
671 curl_easy_setopt (h->ch->c, CURLOPT_HEADERDATA, NULL);
672 curl_easy_setopt (h->ch->c, CURLOPT_WRITEFUNCTION, write_cb);
673 curl_easy_setopt (h->ch->c, CURLOPT_WRITEDATA, h->ch);
674 if (!readonly) {
675 curl_easy_setopt (h->ch->c, CURLOPT_READFUNCTION, read_cb);
676 curl_easy_setopt (h->ch->c, CURLOPT_READDATA, h->ch);
679 return h;
681 err:
682 if (h->ch->c)
683 curl_easy_cleanup (h->ch->c);
684 free (h->ch);
685 free (h);
686 return NULL;
689 /* When using CURLOPT_VERBOSE, this callback is used to redirect
690 * messages to nbdkit_debug (instead of stderr).
692 static int
693 debug_cb (CURL *handle, curl_infotype type,
694 const char *data, size_t size, void *opaque)
696 size_t origsize = size;
697 CLEANUP_FREE char *str;
699 /* The data parameter passed is NOT \0-terminated, but also it may
700 * have \n or \r\n line endings. The only sane way to deal with
701 * this is to copy the string. (The data strings may also be
702 * multi-line, but we don't deal with that here).
704 str = malloc (size + 1);
705 if (str == NULL)
706 goto out;
707 memcpy (str, data, size);
708 str[size] = '\0';
710 while (size > 0 && (str[size-1] == '\n' || str[size-1] == '\r')) {
711 str[size-1] = '\0';
712 size--;
715 switch (type) {
716 case CURLINFO_TEXT:
717 nbdkit_debug ("%s", str);
718 break;
719 case CURLINFO_HEADER_IN:
720 nbdkit_debug ("S: %s", str);
721 break;
722 case CURLINFO_HEADER_OUT:
723 nbdkit_debug ("C: %s", str);
724 break;
725 default:
726 /* Assume everything else is binary data that we cannot print. */
727 nbdkit_debug ("<data with size=%zu>", origsize);
730 out:
731 return 0;
734 static size_t
735 header_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
737 struct curl_handle *ch = opaque;
738 size_t realsize = size * nmemb;
739 const char *header = ptr;
740 const char *end = header + realsize;
741 const char *accept_ranges = "accept-ranges:";
742 const char *bytes = "bytes";
744 if (realsize >= strlen (accept_ranges) &&
745 ascii_strncasecmp (header, accept_ranges, strlen (accept_ranges)) == 0) {
746 const char *p = strchr (header, ':') + 1;
748 /* Skip whitespace between the header name and value. */
749 while (p < end && *p && ascii_isspace (*p))
750 p++;
752 if (end - p >= strlen (bytes)
753 && strncmp (p, bytes, strlen (bytes)) == 0) {
754 /* Check that there is nothing but whitespace after the value. */
755 p += strlen (bytes);
756 while (p < end && *p && ascii_isspace (*p))
757 p++;
759 if (p == end || !*p)
760 ch->accept_range = true;
764 return realsize;
767 /* Free up the per-connection handle. */
768 static void
769 curl_close (void *handle)
771 struct handle *h = handle;
773 curl_easy_cleanup (h->ch->c);
774 if (h->ch->headers_copy)
775 curl_slist_free_all (h->ch->headers_copy);
776 free (h->ch);
777 free (h);
780 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
782 /* Get the file size. */
783 static int64_t
784 curl_get_size (void *handle)
786 struct handle *h = handle;
788 return h->ch->exportsize;
791 /* Multi-conn is safe for read-only connections, but HTTP does not
792 * have any concept of flushing so we cannot use it for read-write
793 * connections.
795 static int
796 curl_can_multi_conn (void *handle)
798 struct handle *h = handle;
800 return !! h->readonly;
803 /* NB: The terminology used by libcurl is confusing!
805 * WRITEFUNCTION / write_cb is used when reading from the remote server
806 * READFUNCTION / read_cb is used when writing to the remote server.
808 * We use the same terminology as libcurl here.
811 /* Read data from the remote server. */
812 static int
813 curl_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
815 struct handle *h = handle;
816 CURLcode r;
817 char range[128];
819 /* Run the scripts if necessary and set headers in the handle. */
820 if (do_scripts (h->ch) == -1) return -1;
822 /* Tell the write_cb where we want the data to be written. write_cb
823 * will update this if the data comes in multiple sections.
825 h->ch->write_buf = buf;
826 h->ch->write_count = count;
828 curl_easy_setopt (h->ch->c, CURLOPT_HTTPGET, 1L);
830 /* Make an HTTP range request. */
831 snprintf (range, sizeof range, "%" PRIu64 "-%" PRIu64,
832 offset, offset + count);
833 curl_easy_setopt (h->ch->c, CURLOPT_RANGE, range);
835 /* The assumption here is that curl will look after timeouts. */
836 r = curl_easy_perform (h->ch->c);
837 if (r != CURLE_OK) {
838 display_curl_error (h->ch, r, "pread: curl_easy_perform");
839 return -1;
842 /* Could use curl_easy_getinfo here to obtain further information
843 * about the connection.
846 /* As far as I understand the cURL API, this should never happen. */
847 assert (h->ch->write_count == 0);
849 return 0;
852 static size_t
853 write_cb (char *ptr, size_t size, size_t nmemb, void *opaque)
855 struct curl_handle *ch = opaque;
856 size_t orig_realsize = size * nmemb;
857 size_t realsize = orig_realsize;
859 assert (ch->write_buf);
861 /* Don't read more than the requested amount of data, even if the
862 * server or libcurl sends more.
864 if (realsize > ch->write_count)
865 realsize = ch->write_count;
867 memcpy (ch->write_buf, ptr, realsize);
869 ch->write_count -= realsize;
870 ch->write_buf += realsize;
872 return orig_realsize;
875 /* Write data to the remote server. */
876 static int
877 curl_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
879 struct handle *h = handle;
880 CURLcode r;
881 char range[128];
883 /* Run the scripts if necessary and set headers in the handle. */
884 if (do_scripts (h->ch) == -1) return -1;
886 /* Tell the read_cb where we want the data to be read from. read_cb
887 * will update this if the data comes in multiple sections.
889 h->ch->read_buf = buf;
890 h->ch->read_count = count;
892 curl_easy_setopt (h->ch->c, CURLOPT_UPLOAD, 1L);
894 /* Make an HTTP range request. */
895 snprintf (range, sizeof range, "%" PRIu64 "-%" PRIu64,
896 offset, offset + count);
897 curl_easy_setopt (h->ch->c, CURLOPT_RANGE, range);
899 /* The assumption here is that curl will look after timeouts. */
900 r = curl_easy_perform (h->ch->c);
901 if (r != CURLE_OK) {
902 display_curl_error (h->ch, r, "pwrite: curl_easy_perform");
903 return -1;
906 /* Could use curl_easy_getinfo here to obtain further information
907 * about the connection.
910 /* As far as I understand the cURL API, this should never happen. */
911 assert (h->ch->read_count == 0);
913 return 0;
916 static size_t
917 read_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
919 struct curl_handle *ch = opaque;
920 size_t realsize = size * nmemb;
922 assert (ch->read_buf);
923 if (realsize > ch->read_count)
924 realsize = ch->read_count;
926 memcpy (ptr, ch->read_buf, realsize);
928 ch->read_count -= realsize;
929 ch->read_buf += realsize;
931 return realsize;
934 static struct nbdkit_plugin plugin = {
935 .name = "curl",
936 .version = PACKAGE_VERSION,
937 .load = curl_load,
938 .unload = curl_unload,
939 .config = curl_config,
940 .config_complete = curl_config_complete,
941 .config_help = curl_config_help,
942 .magic_config_key = "url",
943 .open = curl_open,
944 .close = curl_close,
945 .get_size = curl_get_size,
946 .can_multi_conn = curl_can_multi_conn,
947 .pread = curl_pread,
948 .pwrite = curl_pwrite,
951 NBDKIT_REGISTER_PLUGIN(plugin)