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
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
47 #include <curl/curl.h>
49 #include <nbdkit-plugin.h>
51 #include "ascii-ctype.h"
52 #include "ascii-string.h"
57 /* Plugin configuration. */
58 const char *url
= NULL
; /* required */
60 const char *cainfo
= NULL
;
61 const char *capath
= 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
;
75 const char *protocols
= NULL
;
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;
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;
99 r
= curl_global_init (CURL_GLOBAL_DEFAULT
);
101 nbdkit_error ("libcurl initialization failed: %d", (int) r
);
111 curl_slist_free_all (headers
);
113 free (proxy_password
);
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
},
148 { "smb", CURLPROTO_SMB
},
150 #ifdef CURLPROTO_SMBS
151 { "smbs", CURLPROTO_SMBS
},
153 #ifdef CURLPROTO_MQTT
154 { "mqtt", CURLPROTO_MQTT
},
159 /* Parse the protocols parameter. */
161 parse_protocols (const char *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
;
176 nbdkit_error ("protocols: protocol name not found: %.*s", (int) n
, value
);
185 if (protocols
== 0) {
186 nbdkit_error ("protocols: empty list of protocols is not allowed");
190 nbdkit_debug ("curl: protocols: %ld", protocols
);
194 #endif /* !HAVE_CURLOPT_PROTOCOLS_STR */
196 /* Called for each key=value passed on the command line. */
198 curl_config (const char *key
, const char *value
)
202 if (strcmp (key
, "cainfo") == 0) {
206 else if (strcmp (key
, "capath") == 0) {
210 else if (strcmp (key
, "cookie") == 0) {
212 if (nbdkit_read_password (value
, &cookie
) == -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 \"-\"");
227 else if (strcmp (key
, "cookiejar") == 0) {
228 /* Reject cookiejar=- because it will cause libcurl to try to
231 if (strcmp (value
, "-") == 0) {
232 nbdkit_error ("cookiejar parameter cannot be \"-\"");
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)
248 else if (strcmp (key
, "followlocation") == 0) {
249 r
= nbdkit_parse_bool (value
);
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");
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)
273 else if (strcmp (key
, "password") == 0) {
275 if (nbdkit_read_password (value
, &password
) == -1)
279 else if (strcmp (key
, "protocols") == 0) {
280 #ifndef HAVE_CURLOPT_PROTOCOLS_STR
281 if (parse_protocols (value
) == -1)
288 else if (strcmp (key
, "proxy") == 0) {
292 else if (strcmp (key
, "proxy-password") == 0) {
293 free (proxy_password
);
294 if (nbdkit_read_password (value
, &proxy_password
) == -1)
298 else if (strcmp (key
, "proxy-user") == 0)
301 else if (strcmp (key
, "sslverify") == 0) {
302 r
= nbdkit_parse_bool (value
);
308 else if (strcmp (key
, "ssl-version") == 0)
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
);
324 else if (strcmp (key
, "tcp-nodelay") == 0) {
325 r
= nbdkit_parse_bool (value
);
331 else if (strcmp (key
, "timeout") == 0) {
332 if (nbdkit_parse_uint32_t ("timeout", value
, &timeout
) == -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");
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)
352 else if (strcmp (key
, "user") == 0)
355 else if (strcmp (key
, "user-agent") == 0)
359 nbdkit_error ("unknown parameter '%s'", key
);
366 /* Check the user did pass a url parameter. */
368 curl_config_complete (void)
371 nbdkit_error ("you must supply the url=<URL> parameter "
372 "after the plugin name on the command line");
376 if (headers
&& header_script
) {
377 nbdkit_error ("header and header-script cannot be used at the same time");
381 if (!header_script
&& header_script_renew
) {
382 nbdkit_error ("header-script-renew cannot be used without header-script");
386 if (cookie
&& cookie_script
) {
387 nbdkit_error ("cookie and cookie-script cannot be used at the same time");
391 if (!cookie_script
&& cookie_script_renew
) {
392 nbdkit_error ("cookie-script-renew cannot be used without cookie-script");
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, ...) \
432 nbdkit_error ((fs ": %s: %s"), ## __VA_ARGS__, \
433 curl_easy_strerror ((r)), (ch)->errbuf); \
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. */
444 curl_open (int readonly
)
448 #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
454 h
= calloc (1, sizeof *h
);
456 nbdkit_error ("calloc: %m");
459 h
->readonly
= readonly
;
461 h
->ch
= calloc (1, sizeof *h
->ch
);
463 nbdkit_error ("calloc: %m");
468 h
->ch
->c
= curl_easy_init ();
469 if (h
->ch
->c
== NULL
) {
470 nbdkit_error ("curl_easy_init: failed: %m");
474 if (curl_debug_verbose
) {
475 /* NB: Constants must be explicitly long because the parameter is
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
);
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
);
489 r
= CURLE_UNKNOWN_OPTION
;
493 display_curl_error (h
->ch
, r
, "curl_easy_setopt: CURLOPT_UNIX_SOCKET_PATH");
498 r
= curl_easy_setopt (h
->ch
->c
, CURLOPT_URL
, url
);
500 display_curl_error (h
->ch
, r
, "curl_easy_setopt: CURLOPT_URL [%s]", url
);
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);
515 curl_easy_setopt (h
->ch
->c
, CURLOPT_FOLLOWLOCATION
, 1L);
516 curl_easy_setopt (h
->ch
->c
, CURLOPT_FAILONERROR
, 1L);
520 if (strlen (cainfo
) == 0)
521 curl_easy_setopt (h
->ch
->c
, CURLOPT_CAINFO
, NULL
);
523 curl_easy_setopt (h
->ch
->c
, CURLOPT_CAINFO
, cainfo
);
526 curl_easy_setopt (h
->ch
->c
, CURLOPT_CAPATH
, capath
);
528 curl_easy_setopt (h
->ch
->c
, CURLOPT_COOKIE
, cookie
);
530 curl_easy_setopt (h
->ch
->c
, CURLOPT_COOKIEFILE
, cookiefile
);
532 curl_easy_setopt (h
->ch
->c
, CURLOPT_COOKIEJAR
, cookiejar
);
534 curl_easy_setopt (h
->ch
->c
, CURLOPT_HTTPHEADER
, headers
);
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) */
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 */
549 curl_easy_setopt (h
->ch
->c
, CURLOPT_PROXY
, proxy
);
551 curl_easy_setopt (h
->ch
->c
, CURLOPT_PROXYPASSWORD
, proxy_password
);
553 curl_easy_setopt (h
->ch
->c
, CURLOPT_PROXYUSERNAME
, proxy_user
);
555 curl_easy_setopt (h
->ch
->c
, CURLOPT_SSL_VERIFYPEER
, 0L);
556 curl_easy_setopt (h
->ch
->c
, CURLOPT_SSL_VERIFYHOST
, 0L);
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
);
574 display_curl_error (h
->ch
, r
, "curl_easy_setopt: CURLOPT_SSLVERSION [%s]",
581 curl_easy_setopt (h
->ch
->c
, CURLOPT_SSL_CIPHER_LIST
, ssl_cipher_list
);
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
);
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");
594 curl_easy_setopt (h
->ch
->c
, CURLOPT_TCP_KEEPALIVE
, 1L);
596 curl_easy_setopt (h
->ch
->c
, CURLOPT_TCP_NODELAY
, 0L);
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
);
603 curl_easy_setopt (h
->ch
->c
, CURLOPT_USERNAME
, user
);
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
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
);
620 display_curl_error (h
->ch
, r
,
621 "problem doing HEAD request to fetch size of URL [%s]",
626 #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
627 r
= curl_easy_getinfo (h
->ch
->c
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
, &o
);
629 display_curl_error (h
->ch
, r
,
630 "could not get length of remote file [%s]", url
);
635 nbdkit_error ("could not get length of remote file [%s], "
636 "is the URL correct?", url
);
640 h
->ch
->exportsize
= o
;
642 r
= curl_easy_getinfo (h
->ch
->c
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &d
);
644 display_curl_error (h
->ch
, r
,
645 "could not get length of remote file [%s]", url
);
650 nbdkit_error ("could not get length of remote file [%s], "
651 "is the URL correct?", url
);
655 h
->ch
->exportsize
= d
;
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");
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
);
675 curl_easy_setopt (h
->ch
->c
, CURLOPT_READFUNCTION
, read_cb
);
676 curl_easy_setopt (h
->ch
->c
, CURLOPT_READDATA
, h
->ch
);
683 curl_easy_cleanup (h
->ch
->c
);
689 /* When using CURLOPT_VERBOSE, this callback is used to redirect
690 * messages to nbdkit_debug (instead of stderr).
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);
707 memcpy (str
, data
, size
);
710 while (size
> 0 && (str
[size
-1] == '\n' || str
[size
-1] == '\r')) {
717 nbdkit_debug ("%s", str
);
719 case CURLINFO_HEADER_IN
:
720 nbdkit_debug ("S: %s", str
);
722 case CURLINFO_HEADER_OUT
:
723 nbdkit_debug ("C: %s", str
);
726 /* Assume everything else is binary data that we cannot print. */
727 nbdkit_debug ("<data with size=%zu>", origsize
);
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
))
752 if (end
- p
>= strlen (bytes
)
753 && strncmp (p
, bytes
, strlen (bytes
)) == 0) {
754 /* Check that there is nothing but whitespace after the value. */
756 while (p
< end
&& *p
&& ascii_isspace (*p
))
760 ch
->accept_range
= true;
767 /* Free up the per-connection handle. */
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
);
780 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
782 /* Get the file size. */
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
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. */
813 curl_pread (void *handle
, void *buf
, uint32_t count
, uint64_t offset
)
815 struct handle
*h
= handle
;
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
);
838 display_curl_error (h
->ch
, r
, "pread: curl_easy_perform");
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);
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. */
877 curl_pwrite (void *handle
, const void *buf
, uint32_t count
, uint64_t offset
)
879 struct handle
*h
= handle
;
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
);
902 display_curl_error (h
->ch
, r
, "pwrite: curl_easy_perform");
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);
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
;
934 static struct nbdkit_plugin plugin
= {
936 .version
= PACKAGE_VERSION
,
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",
945 .get_size
= curl_get_size
,
946 .can_multi_conn
= curl_can_multi_conn
,
948 .pwrite
= curl_pwrite
,
951 NBDKIT_REGISTER_PLUGIN(plugin
)