62
#define CURL_BLOCK_OPT_PASSWORD_SECRET "password-secret"
63
#define CURL_BLOCK_OPT_PROXY_USERNAME "proxy-username"
64
#define CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET "proxy-password-secret"
65
+#define CURL_BLOCK_OPT_FORCE_RANGE "force-range"
66
67
#define CURL_BLOCK_OPT_READAHEAD_DEFAULT (256 * 1024)
68
#define CURL_BLOCK_OPT_SSLVERIFY_DEFAULT true
69
#define CURL_BLOCK_OPT_TIMEOUT_DEFAULT 5
70
+#define CURL_BLOCK_OPT_FORCE_RANGE_DEFAULT false
71
72
struct BDRVCURLState;
73
struct CURLState;
208
{
209
BDRVCURLState *s = opaque;
210
size_t realsize = size * nmemb;
209
- const char *p = ptr;
210
- const char *end = p + realsize;
211
- const char *t = "accept-ranges : bytes "; /* A lowercase template */
211
+ g_autofree char *header = g_strstrip(g_strndup(ptr, realsize));
212
+ char *val = strchr(header, ':');
213
213
- /* check if header matches the "t" template */
214
- for (;;) {
215
- if (*t == ' ') { /* space in t matches any amount of isspace in p */
216
- if (p < end && g_ascii_isspace(*p)) {
217
- ++p;
218
- } else {
219
- ++t;
220
- }
221
- } else if (*t && p < end && *t == g_ascii_tolower(*p)) {
222
- ++p, ++t;
223
- } else {
224
- break;
225
- }
214
+ if (!val) {
215
+ return realsize;
216
}
217
228
- if (!*t && p == end) { /* if we managed to reach ends of both strings */
229
- s->accept_range = true;
218
+ *val++ = '\0';
219
+ g_strchomp(header);
220
+ while (g_ascii_isspace(*val)) {
221
+ ++val;
222
+ }
223
+
224
+ trace_curl_header_cb(header, val);
225
+
226
+ if (!g_ascii_strcasecmp(header, "accept-ranges")) {
227
+ if (!g_ascii_strcasecmp(val, "bytes")) {
228
+ s->accept_range = true;
229
+ }
230
+ } else if (!g_ascii_strcasecmp(header, "Content-Range")) {
231
+ /* Content-Range fmt is `bytes begin-end/full_size` */
232
+ val = strchr(val, '/');
233
+ if (val) {
234
+ if (qemu_strtou64(val + 1, NULL, 10, &s->len) < 0) {
235
+ s->len = UINT64_MAX;
236
+ }
237
+ }
238
}
239
240
return realsize;
676
.type = QEMU_OPT_STRING,
677
.help = "ID of secret used as password for HTTP proxy auth",
678
},
679
+ {
680
+ .name = CURL_BLOCK_OPT_FORCE_RANGE,
681
+ .type = QEMU_OPT_BOOL,
682
+ .help = "Assume HTTP range requests are supported",
683
+ },
684
{ /* end of list */ }
685
},
686
};
703
#endif
704
const char *secretid;
705
const char *protocol_delimiter;
706
+ bool force_range;
707
int ret;
708
709
bdrv_graph_rdlock_main_loop();
821
}
822
823
s->accept_range = false;
824
+ s->len = UINT64_MAX;
825
+ force_range = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_FORCE_RANGE,
826
+ CURL_BLOCK_OPT_FORCE_RANGE_DEFAULT);
827
+ /*
828
+ * When minimal CURL will be bumped to `7.83`, the header callback + manual
829
+ * parsing can be replaced by `curl_easy_header` calls
830
+ */
831
if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1L) ||
832
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_header_cb) ||
833
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s)) {
813
- pstrcpy(state->errmsg, CURL_ERROR_SIZE,
814
- "curl library initialization failed.");
815
- goto out;
834
+ goto out_init;
835
+ }
836
+ if (force_range) {
837
+ if (curl_easy_setopt(state->curl, CURLOPT_CUSTOMREQUEST, "GET") ||
838
+ curl_easy_setopt(state->curl, CURLOPT_RANGE, "0-0")) {
839
+ goto out_init;
840
+ }
841
}
842
+
843
if (curl_easy_perform(state->curl))
844
goto out;
819
- /* CURL 7.55.0 deprecates CURLINFO_CONTENT_LENGTH_DOWNLOAD in favour of
820
- * the *_T version which returns a more sensible type for content length.
821
- */
845
+
846
+ if (!force_range) {
847
+ /*
848
+ * CURL 7.55.0 deprecates CURLINFO_CONTENT_LENGTH_DOWNLOAD in favour of
849
+ * the *_T version which returns a more sensible type for content
850
+ * length.
851
+ */
852
#if LIBCURL_VERSION_NUM >= 0x073700
823
- if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl)) {
824
- goto out;
825
- }
853
+ if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
854
+ &cl)) {
855
+ goto out;
856
+ }
857
#else
827
- if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &cl)) {
828
- goto out;
829
- }
858
+ if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD,
859
+ &cl)) {
860
+ goto out;
861
+ }
862
#endif
831
- if (cl < 0) {
863
+ if (cl >= 0) {
864
+ s->len = cl;
865
+ }
866
+ }
867
+
868
+ if (s->len == UINT64_MAX) {
869
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
870
"Server didn't report file size.");
871
goto out;
872
}
873
837
- s->len = cl;
838
-
874
if ((!strncasecmp(s->url, "http://", strlen("http://"))
875
|| !strncasecmp(s->url, "https://", strlen("https://")))
876
&& !s->accept_range) {
891
qemu_opts_del(opts);
892
return 0;
893
894
+out_init:
895
+ pstrcpy(state->errmsg, CURL_ERROR_SIZE,
896
+ "curl library initialization failed.");
897
out:
898
error_setg(errp, "CURL: Error opening file: %s", state->errmsg);
899
curl_easy_cleanup(state->curl);