FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/libcurl.c
Date: 2026-09-23 07:39:07
Exec Total Coverage
Lines: 3 695 0.4%
Functions: 1 30 3.3%
Branches: 2 414 0.5%

Line Branch Exec Source
1 /*
2 * libcurl based HTTP(S) protocol
3 * Copyright (C) 2026 Kacper Michajłow
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include "config_components.h"
23
24 #include <curl/curl.h>
25 #include <inttypes.h>
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "libavutil/avstring.h"
31 #include "libavutil/bprint.h"
32 #include "libavutil/error.h"
33 #include "libavutil/fifo.h"
34 #include "libavutil/log.h"
35 #include "libavutil/macros.h"
36 #include "libavutil/mem.h"
37 #include "libavutil/opt.h"
38 #include "libavutil/thread.h"
39 #include "libavutil/time.h"
40
41 #include "avformat.h"
42 #include "http.h"
43 #include "internal.h"
44 #include "url.h"
45 #include "version.h"
46
47 #define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION)
48 #define CURL_DEFAULT_BUFFER_SIZE (4 << 20)
49
50 /* Blocking waits wake up this often so url_read()/open can poll the interrupt
51 * callback. */
52 #define CURL_WAIT_US 100000
53
54 typedef struct CurlContext CurlContext;
55
56 enum cmd_kind {
57 CMD_ADD, /* add the easy handle to the multi and start the transfer */
58 CMD_REMOVE, /* remove the easy handle from the multi */
59 CMD_UNPAUSE, /* resume a transfer paused because the FIFO was full */
60 CMD_SEEK, /* restart the transfer at a new byte offset */
61 };
62
63 typedef struct CurlCmd {
64 enum cmd_kind kind;
65 CurlContext *ctx;
66 int64_t pos; /* CMD_SEEK target offset */
67 int sync; /* caller waits for completion */
68 int done;
69 struct CurlCmd *next;
70 } CurlCmd;
71
72 typedef struct CurlLoop {
73 AVFormatContext *avfc; /* owning context (if any) */
74
75 pthread_t thread;
76 CURLM *multi;
77 CURLSH *share; /* shared cookies/HSTS */
78
79 pthread_mutex_t mutex; /* guards the command queue, exit and cmd->done */
80 pthread_cond_t cond; /* signaled when a sync command completes */
81 CurlCmd *cmd_head, *cmd_tail;
82 int exit;
83
84 /* Connection statistics (updated by loop thread) */
85 int64_t total_bytes;
86 int64_t total_time_us;
87 int num_connections;
88 int num_redirects;
89 int num_requests;
90 int num_errors;
91 } CurlLoop;
92
93 struct CurlContext {
94 const AVClass *class;
95 URLContext *h;
96
97 CurlLoop *loop;
98 int private_loop; /* loop is owned by this context (not shared) */
99 CURL *easy;
100 struct curl_slist *header_list;
101
102 /* AVOptions. */
103 char *user_agent;
104 char *referer;
105 char *headers;
106 char *http_proxy;
107 char *cookies;
108 char *ca_file;
109 char *cert_file;
110 char *key_file;
111 char *location; /* effective URL after redirects (output) */
112 int64_t off; /* initial byte offset */
113 int64_t end_off; /* exclusive upper byte bound (0 = none) */
114 int tls_verify;
115 int seekable_opt;
116 int connect_timeout;
117 int max_redirects;
118 int multiple_requests;
119 int http_version;
120 int64_t buffer_size;
121 int64_t request_size;
122 int64_t initial_request_size;
123 int64_t short_seek_size;
124 int max_retries;
125
126 int64_t logical_pos; /* next byte url_read() will return, caller side */
127
128 /* Producer bookkeeping, touched only by the loop thread. */
129 int active; /* currently added to the multi */
130 int64_t request_start; /* absolute offset the current request began at */
131 int64_t request_received;/* bytes delivered in the current request */
132 int64_t request_end; /* expected end of request, or -1 if unknown */
133 int retry_count; /* consecutive recoverable failures */
134 int is_initial; /* using reduced request size */
135 int seek_queued; /* soft seeking; drain remaining bytes until done */
136
137 /* Per-response-block header scratch, loop thread only. */
138 int hdr_accept_ranges;
139 int hdr_compressed;
140 int64_t hdr_content_start; /* inclusive start, or -1 */
141 int64_t hdr_content_end; /* inclusive end, or -1 */
142 int64_t hdr_content_total; /* if known, or -1 */
143
144 /* Probe result. Set by the loop thread, read by url_open() once probed. */
145 int probed;
146 int stream_ok;
147 int seekable;
148 int64_t content_size;
149
150 /* Shared transfer state, guarded by mutex. */
151 pthread_mutex_t mutex;
152 pthread_cond_t cond;
153 AVFifo *fifo;
154 int paused; /* write callback paused, FIFO was full */
155 int status; /* current stream status (AVERROR code) */
156 int aborted; /* transfer should stop (open was interrupted) */
157 };
158
159 /* Guards lazy creation of a format context's shared loop. */
160 static AVMutex curl_loop_lock = AV_MUTEX_INITIALIZER;
161
162 static int curlcode_to_averror(CURLcode code)
163 {
164 switch (code) {
165 case CURLE_OK: return 0;
166 case CURLE_URL_MALFORMAT:
167 case CURLE_UNSUPPORTED_PROTOCOL: return AVERROR(EINVAL);
168 case CURLE_COULDNT_RESOLVE_PROXY:
169 case CURLE_COULDNT_RESOLVE_HOST: return AVERROR(EHOSTUNREACH);
170 case CURLE_COULDNT_CONNECT: return AVERROR(ECONNREFUSED);
171 case CURLE_OPERATION_TIMEDOUT: return AVERROR(ETIMEDOUT);
172 case CURLE_LOGIN_DENIED:
173 case CURLE_REMOTE_ACCESS_DENIED: return AVERROR(EACCES);
174 case CURLE_OUT_OF_MEMORY: return AVERROR(ENOMEM);
175 case CURLE_PEER_FAILED_VERIFICATION:
176 case CURLE_SSL_CACERT_BADFILE: return AVERROR_INVALIDDATA;
177 default: return AVERROR(EIO);
178 }
179 }
180
181 static int is_recoverable(CURLcode code)
182 {
183 switch (code) {
184 case CURLE_RECV_ERROR:
185 case CURLE_SEND_ERROR:
186 case CURLE_PARTIAL_FILE:
187 case CURLE_OPERATION_TIMEDOUT:
188 case CURLE_GOT_NOTHING:
189 case CURLE_COULDNT_CONNECT:
190 case CURLE_COULDNT_RESOLVE_HOST:
191 case CURLE_HTTP2:
192 case CURLE_HTTP2_STREAM:
193 return 1;
194 default:
195 return 0;
196 }
197 }
198
199 /* ------------------------------------------------------------------------- */
200 /* curl callbacks (run on the loop thread) */
201 /* ------------------------------------------------------------------------- */
202
203 static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
204 {
205 CurlContext *c = userdata;
206 size_t bytes = size * nmemb;
207 size_t space;
208
209 pthread_mutex_lock(&c->mutex);
210
211 if (c->aborted || !c->stream_ok) {
212 pthread_mutex_unlock(&c->mutex);
213 return CURL_WRITEFUNC_ERROR;
214 }
215
216 if (c->seek_queued) {
217 pthread_mutex_unlock(&c->mutex);
218 return bytes; /* discard */
219 }
220
221 space = av_fifo_can_write(c->fifo);
222 if (space < bytes) {
223 /* pause the transfer and wait for the consumer to drain. */
224 c->paused = 1;
225 pthread_mutex_unlock(&c->mutex);
226 return CURL_WRITEFUNC_PAUSE;
227 }
228
229 av_fifo_write(c->fifo, ptr, bytes);
230 c->paused = 0;
231 c->request_received += bytes;
232 pthread_cond_broadcast(&c->cond);
233 pthread_mutex_unlock(&c->mutex);
234
235 return bytes;
236 }
237
238 static int64_t parse_offset(const char *s)
239 {
240 int64_t v = strtoll(s, NULL, 10);
241 return v < 0 ? -1 : v;
242 }
243
244 /* "bytes $from-$to/$document_size" */
245 static void parse_content_range(CurlContext *c, const char *v)
246 {
247 while (av_isspace(*v))
248 v++;
249
250 if (av_strncasecmp(v, "bytes ", 6))
251 return;
252
253 const char *range = v + 6, *end;
254 if (range[0] != '*') {
255 c->hdr_content_start = parse_offset(range);
256 if ((end = strchr(range, '-')))
257 c->hdr_content_end = parse_offset(end + 1);
258 }
259
260 const char *slash = strchr(range, '/');
261 if (slash && slash[1] != '*')
262 c->hdr_content_total = parse_offset(slash + 1);
263 }
264
265 static size_t header_callback(char *ptr, size_t size, size_t nitems, void *userdata)
266 {
267 CurlContext *c = userdata;
268 size_t len = size * nitems;
269 size_t n = len;
270 long status = 0;
271
272 if (av_strncasecmp(ptr, "HTTP/", 5) == 0) {
273 c->hdr_accept_ranges = 0;
274 c->hdr_compressed = 0;
275 c->hdr_content_start = -1;
276 c->hdr_content_end = -1;
277 c->hdr_content_total = -1;
278 return len;
279 }
280 if (av_strncasecmp(ptr, "Accept-Ranges:", 14) == 0) {
281 c->hdr_accept_ranges = !!av_stristr(ptr + 14, "bytes");
282 return len;
283 }
284 if (av_strncasecmp(ptr, "Content-Encoding:", 17) == 0) {
285 c->hdr_compressed = !av_stristr(ptr + 17, "identity");
286 return len;
287 }
288 if (av_strncasecmp(ptr, "Content-Range:", 14) == 0) {
289 parse_content_range(c, ptr + 14);
290 return len;
291 }
292
293 /* Otherwise act only on the blank line that terminates the header block. */
294 while (n && (ptr[n - 1] == '\r' || ptr[n - 1] == '\n'))
295 n--;
296 if (n)
297 return len;
298
299 curl_easy_getinfo(c->easy, CURLINFO_RESPONSE_CODE, &status);
300
301 /* Interim (1xx) and redirect (3xx) responses produce an intermediate header
302 * block, wait for the final one. */
303 if (status < 200 || (status >= 300 && status < 400))
304 return len;
305
306 pthread_mutex_lock(&c->mutex);
307 if (status >= 200 && status < 300) {
308 int64_t content_start = status == 206 ? c->hdr_content_start : 0;
309 /* The reply must start at the offset we requested: for follow-up
310 * requests always, for the initial one when an explicit nonzero
311 * offset was requested. */
312 if ((c->probed ? c->seekable : c->off > 0) &&
313 content_start != c->request_start) {
314 av_log(c->h, AV_LOG_ERROR, "Server sent back unexpected reply "
315 "with offset %"PRId64" (expected %"PRId64")\n",
316 content_start, c->request_start);
317 c->loop->num_errors++;
318 c->stream_ok = 0;
319 if (!c->status)
320 c->status = AVERROR(EIO);
321 pthread_cond_broadcast(&c->cond);
322 pthread_mutex_unlock(&c->mutex);
323 return len;
324 }
325
326 c->stream_ok = 1;
327 /* Capture the post-redirect URL, this is exposed as "location" AVOption
328 * for compatibility with http.c. */
329 if (!c->probed) {
330 const char *eff = NULL;
331 if (curl_easy_getinfo(c->easy, CURLINFO_EFFECTIVE_URL, &eff) == CURLE_OK
332 && eff) {
333 char *dup = av_strdup(eff);
334 if (dup) {
335 av_free(c->location);
336 c->location = dup;
337 }
338 }
339 }
340 /* A compressed body is addressed in encoded form, so byte offsets are
341 * meaningless: not seekable. Note that we prefer compression over
342 * seekability, servers don't offer media in compressed form, so it
343 * gives us free compression for other payloads like text playlist. */
344 c->seekable = !c->hdr_compressed &&
345 (status == 206 || c->hdr_accept_ranges);
346 if (!c->hdr_compressed) {
347 int64_t total = c->hdr_content_total;
348 if (total < 0 && status != 206) {
349 curl_off_t cl = -1;
350 if (curl_easy_getinfo(c->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
351 &cl) == CURLE_OK && cl >= 0)
352 total = cl;
353 }
354 /* Don't unlearn a known size when a reply omits it. */
355 if (total >= 0)
356 c->content_size = total;
357 }
358 if (c->seekable) {
359 if (c->hdr_content_end >= 0)
360 c->request_end = c->hdr_content_end;
361 else
362 c->request_end = c->content_size > 0 ? c->content_size - 1 : -1;
363 }
364 /* Apply the user override on every reply so re-evaluation of a
365 * follow-up reply doesn't clobber it. */
366 if (c->seekable_opt >= 0)
367 c->seekable = c->seekable_opt;
368 } else {
369 c->loop->num_errors++;
370 c->stream_ok = 0;
371 if (!c->status)
372 c->status = ff_http_averror(status, AVERROR(EIO));
373 }
374 c->probed = 1;
375 pthread_cond_broadcast(&c->cond);
376 pthread_mutex_unlock(&c->mutex);
377
378 return len;
379 }
380
381 static int xferinfo_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow,
382 curl_off_t ultotal, curl_off_t ulnow)
383 {
384 CurlContext *c = userdata;
385 int aborted;
386 pthread_mutex_lock(&c->mutex);
387 aborted = c->aborted;
388 pthread_mutex_unlock(&c->mutex);
389 return aborted; /* non-zero aborts the transfer */
390 }
391
392 /* (Re)issue the request for the current offset and add it to the multi. Loop
393 * thread only. */
394 static void start_request(CurlContext *c)
395 {
396 if (!c->probed || c->seekable) {
397 int64_t start = c->request_start;
398 char range[48];
399 int64_t request_size = c->request_size;
400 if (c->is_initial && c->initial_request_size > 0)
401 request_size = c->initial_request_size;
402 if (request_size > 0 || c->end_off > 0) {
403 int64_t end = INT64_MAX;
404 if (request_size > 0 && start <= INT64_MAX - request_size)
405 end = start + request_size - 1;
406 if (c->content_size > 0)
407 end = FFMIN(end, c->content_size - 1);
408 if (c->end_off > 0)
409 end = FFMIN(end, c->end_off - 1);
410 snprintf(range, sizeof(range), "%"PRId64"-%"PRId64, start, end);
411 } else {
412 snprintf(range, sizeof(range), "%"PRId64"-", start);
413 }
414 curl_easy_setopt(c->easy, CURLOPT_RANGE, range);
415 } else {
416 curl_easy_setopt(c->easy, CURLOPT_RANGE, NULL);
417 }
418 c->loop->num_requests++;
419 c->request_received = 0;
420 c->request_end = -1;
421 c->active = 1;
422 CURLMcode res = curl_multi_add_handle(c->loop->multi, c->easy);
423 if (res != CURLM_OK) {
424 av_log(c->h, AV_LOG_ERROR, "curl_multi_add_handle: %s\n",
425 curl_multi_strerror(res));
426 c->active = 0;
427 pthread_mutex_lock(&c->mutex);
428 if (!c->status)
429 c->status = AVERROR(EIO);
430 pthread_cond_broadcast(&c->cond);
431 pthread_mutex_unlock(&c->mutex);
432 }
433 }
434
435 static void update_statistics(CurlContext *c)
436 {
437 CurlLoop *loop = c->loop;
438 CURL *e = c->easy;
439
440 curl_off_t recv = 0, time = 0;
441 curl_easy_getinfo(e, CURLINFO_SIZE_DOWNLOAD_T, &recv);
442 curl_easy_getinfo(e, CURLINFO_TOTAL_TIME_T, &time);
443
444 if (recv) {
445 av_log(c->h, AV_LOG_DEBUG, "%"PRId64" bytes received in %"PRId64" ms\n",
446 (int64_t) recv, (int64_t) time / 1000);
447
448 loop->total_bytes += recv;
449 loop->total_time_us += time;
450 }
451
452 long num_conns = 0, num_redirs = 0;
453 curl_easy_getinfo(e, CURLINFO_NUM_CONNECTS, &num_conns);
454 curl_easy_getinfo(e, CURLINFO_REDIRECT_COUNT, &num_redirs);
455 loop->num_connections += (int) num_conns;
456 loop->num_redirects += (int) num_redirs;
457 }
458
459 /* Transfer finished (or failed) */
460 static void on_done(CurlContext *c, CURLcode code)
461 {
462 int64_t received;
463 int aborted;
464
465 pthread_mutex_lock(&c->mutex);
466 aborted = c->aborted;
467 received = c->request_received;
468 /* Advance past delivered bytes so a retry or seek resumes at the right offset. */
469 if (received > INT64_MAX - c->request_start) {
470 if (!c->status)
471 c->status = AVERROR(EIO);
472 received = 0;
473 aborted = 1;
474 pthread_cond_broadcast(&c->cond);
475 }
476 c->request_start += received;
477 c->request_received = 0;
478 pthread_mutex_unlock(&c->mutex);
479 update_statistics(c);
480
481 if (!c->probed) {
482 /* Connection died before any usable header arrived. */
483 pthread_mutex_lock(&c->mutex);
484 c->probed = 1;
485 c->stream_ok = 0;
486 if (!c->status)
487 c->status = curlcode_to_averror(code);
488 c->loop->num_errors++;
489 pthread_cond_broadcast(&c->cond);
490 pthread_mutex_unlock(&c->mutex);
491 return;
492 }
493
494 if (aborted)
495 return;
496
497 if (c->seek_queued) {
498 /* previous soft seek drain finished; can start new request now */
499 c->seek_queued = 0;
500 start_request(c);
501 return;
502 }
503
504 if (code == CURLE_OK && c->stream_ok) {
505 c->retry_count = 0;
506 int64_t file_end = c->content_size > 0 ? c->content_size - 1 : -1;
507 if (c->end_off > 0)
508 file_end = FFMIN(file_end, c->end_off - 1);
509 if (c->seekable && c->request_end >= 0 && c->request_end < file_end) {
510 c->is_initial = 0;
511 start_request(c);
512 return;
513 }
514 pthread_mutex_lock(&c->mutex);
515 c->status = AVERROR_EOF;
516 pthread_cond_broadcast(&c->cond);
517 pthread_mutex_unlock(&c->mutex);
518 return;
519 }
520
521 if (c->stream_ok) {
522 av_log(c->h, AV_LOG_WARNING, "%s\n", curl_easy_strerror(code));
523 c->loop->num_errors++;
524 }
525
526 /* Resume seekable transfers after a recoverable error. */
527 if (c->seekable && is_recoverable(code) &&
528 c->retry_count < c->max_retries) {
529 c->retry_count++;
530 av_log(c->h, AV_LOG_WARNING, "Retrying (#%d) from %"PRId64"\n",
531 c->retry_count, c->request_start);
532 start_request(c);
533 return;
534 }
535
536 /* Unhandled generic curl error */
537 pthread_mutex_lock(&c->mutex);
538 if (!c->status)
539 c->status = curlcode_to_averror(code);
540 pthread_cond_broadcast(&c->cond);
541 pthread_mutex_unlock(&c->mutex);
542 }
543
544 /* ------------------------------------------------------------------------- */
545 /* event loop thread + command queue */
546 /* ------------------------------------------------------------------------- */
547
548 static int test_short_seek(CurlContext *c)
549 {
550 if (c->seek_queued)
551 return 1; /* short seek already queued */
552
553 if (c->short_seek_size <= 0 || /* short seek disabled */
554 c->request_end < 0) /* content size not known */
555 return 0;
556
557 const int64_t last = c->request_end - c->request_start;
558 return last - c->request_received < c->short_seek_size;
559 }
560
561 static void execute_command(CurlLoop *loop, CurlCmd *cmd)
562 {
563 CurlContext *c = cmd->ctx;
564
565 switch (cmd->kind) {
566 case CMD_ADD:
567 start_request(c);
568 break;
569 case CMD_REMOVE:
570 if (c->active) {
571 curl_multi_remove_handle(loop->multi, c->easy);
572 update_statistics(c);
573 c->active = 0;
574 }
575 break;
576 case CMD_UNPAUSE:
577 pthread_mutex_lock(&c->mutex);
578 c->paused = 0;
579 pthread_mutex_unlock(&c->mutex);
580 curl_easy_pause(c->easy, CURLPAUSE_CONT);
581 break;
582 case CMD_SEEK:
583 if (c->active && test_short_seek(c)) {
584 c->seek_queued = 1;
585 } else if (c->active) {
586 curl_multi_remove_handle(loop->multi, c->easy);
587 c->active = 0;
588 }
589 pthread_mutex_lock(&c->mutex);
590 av_fifo_reset2(c->fifo);
591 const int was_paused = c->paused;
592 c->paused = 0;
593 c->status = 0;
594 pthread_mutex_unlock(&c->mutex);
595 c->request_start = cmd->pos;
596 c->request_received = 0;
597 c->retry_count = 0;
598 if (!c->seek_queued)
599 start_request(c);
600 else if (was_paused)
601 curl_easy_pause(c->easy, CURLPAUSE_CONT);
602 break;
603 }
604 }
605
606 static void *curl_worker(void *arg)
607 {
608 CurlLoop *loop = arg;
609
610 ff_thread_setname("curl");
611
612 while (1) {
613 CurlCmd *cmd;
614 CURLMsg *msg;
615 int running = 0, left = 0, do_exit;
616
617 pthread_mutex_lock(&loop->mutex);
618 cmd = loop->cmd_head;
619 if (cmd) {
620 loop->cmd_head = cmd->next;
621 if (!loop->cmd_head)
622 loop->cmd_tail = NULL;
623 }
624 do_exit = loop->exit;
625 pthread_mutex_unlock(&loop->mutex);
626
627 if (cmd) {
628 execute_command(loop, cmd);
629 if (cmd->sync) {
630 pthread_mutex_lock(&loop->mutex);
631 cmd->done = 1;
632 pthread_cond_broadcast(&loop->cond);
633 pthread_mutex_unlock(&loop->mutex);
634 } else {
635 av_free(cmd);
636 }
637 continue; /* drain the whole queue before pumping curl */
638 }
639
640 if (do_exit)
641 break;
642
643 curl_multi_perform(loop->multi, &running);
644
645 while ((msg = curl_multi_info_read(loop->multi, &left))) {
646 CurlContext *c = NULL;
647 if (msg->msg != CURLMSG_DONE)
648 continue;
649 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &c);
650 curl_multi_remove_handle(loop->multi, msg->easy_handle);
651 if (c) {
652 c->active = 0;
653 on_done(c, msg->data.result);
654 }
655 }
656
657 curl_multi_poll(loop->multi, NULL, 0, 1000, NULL);
658 }
659
660 return NULL;
661 }
662
663 /* Dispatch a command to the loop. For sync commands the caller blocks until the
664 * loop thread has executed it. Returns 0 or a negative AVERROR. */
665 static int curl_dispatch(CurlLoop *loop, enum cmd_kind kind, CurlContext *c,
666 int64_t pos, int sync)
667 {
668 CurlCmd stackcmd = {0};
669 CurlCmd *cmd = sync ? &stackcmd : av_mallocz(sizeof(*cmd));
670
671 if (!cmd)
672 return AVERROR(ENOMEM);
673
674 cmd->kind = kind;
675 cmd->ctx = c;
676 cmd->pos = pos;
677 cmd->sync = sync;
678
679 pthread_mutex_lock(&loop->mutex);
680 if (loop->cmd_tail)
681 loop->cmd_tail->next = cmd;
682 else
683 loop->cmd_head = cmd;
684 loop->cmd_tail = cmd;
685 curl_multi_wakeup(loop->multi);
686 if (sync) {
687 while (!cmd->done)
688 pthread_cond_wait(&loop->cond, &loop->mutex);
689 }
690 pthread_mutex_unlock(&loop->mutex);
691
692 return 0;
693 }
694
695 static CurlLoop *curl_loop_create(AVFormatContext *avfc)
696 {
697 CurlLoop *loop = av_mallocz(sizeof(*loop));
698 if (!loop)
699 return NULL;
700 loop->avfc = avfc;
701
702 if (pthread_mutex_init(&loop->mutex, NULL))
703 goto fail;
704 if (pthread_cond_init(&loop->cond, NULL)) {
705 pthread_mutex_destroy(&loop->mutex);
706 goto fail;
707 }
708
709 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
710 goto fail2;
711
712 loop->multi = curl_multi_init();
713 if (!loop->multi)
714 goto fail3;
715 curl_multi_setopt(loop->multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
716
717 loop->share = curl_share_init();
718 if (!loop->share)
719 goto fail3;
720 curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
721 curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
722
723 if (pthread_create(&loop->thread, NULL, curl_worker, loop))
724 goto fail3;
725
726 return loop;
727
728 fail3:
729 curl_multi_cleanup(loop->multi);
730 curl_share_cleanup(loop->share);
731 curl_global_cleanup();
732 fail2:
733 pthread_cond_destroy(&loop->cond);
734 pthread_mutex_destroy(&loop->mutex);
735 fail:
736 av_free(loop);
737 return NULL;
738 }
739
740 static void print_statistics(CurlLoop *loop)
741 {
742 AVFormatContext *avfc = loop->avfc;
743
744 if (loop->total_bytes) {
745 double time = loop->total_time_us / 1000000.0;
746 double avg = time ? loop->total_bytes / time : 0;
747 av_log(avfc, AV_LOG_VERBOSE,
748 "libcurl: Overall %"PRId64" bytes received in %.0f ms = %.0f kB/s\n",
749 loop->total_bytes, time * 1e3, avg / 1e3);
750 }
751
752 if (loop->num_connections || loop->num_errors) {
753 av_log(avfc, AV_LOG_VERBOSE,
754 "libcurl: %d connections, %d redirects, %d requests, %d errors\n",
755 loop->num_connections, loop->num_redirects, loop->num_requests,
756 loop->num_errors);
757 }
758 }
759
760 static void curl_loop_destroy(CurlLoop *loop)
761 {
762 pthread_mutex_lock(&loop->mutex);
763 loop->exit = 1;
764 curl_multi_wakeup(loop->multi);
765 pthread_mutex_unlock(&loop->mutex);
766
767 pthread_join(loop->thread, NULL);
768 print_statistics(loop);
769
770 curl_multi_cleanup(loop->multi);
771 curl_share_cleanup(loop->share);
772 pthread_cond_destroy(&loop->cond);
773 pthread_mutex_destroy(&loop->mutex);
774 av_free(loop);
775
776 /* Released after the thread is joined and the multi handle is gone. */
777 curl_global_cleanup();
778 }
779
780 /* Attach a context to its event loop. With an owning AVFormatContext the loop is
781 * created lazily, cached on it, and shared across the demuxer's transfers so curl
782 * reuses connections; it is freed at format teardown. Without one the context
783 * gets a private loop freed on close. */
784 static int curl_loop_attach(CurlContext *c, AVFormatContext *avfc)
785 {
786 if (!avfc) {
787 c->loop = curl_loop_create(NULL);
788 c->private_loop = 1;
789 return c->loop ? 0 : AVERROR(ENOMEM);
790 }
791
792 pthread_mutex_lock(&curl_loop_lock);
793 c->loop = ffformatcontext(avfc)->curl_loop;
794 if (!c->loop) {
795 c->loop = curl_loop_create(avfc);
796 ffformatcontext(avfc)->curl_loop = c->loop;
797 }
798 pthread_mutex_unlock(&curl_loop_lock);
799
800 return c->loop ? 0 : AVERROR(ENOMEM);
801 }
802
803 17230 void ff_curl_loop_free(struct CurlLoop **loop)
804 {
805
2/4
✓ Branch 0 taken 17230 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 17230 times.
17230 if (loop && *loop) {
806 curl_loop_destroy(*loop);
807 *loop = NULL;
808 }
809 17230 }
810
811 /* ------------------------------------------------------------------------- */
812 /* URLProtocol callbacks */
813 /* ------------------------------------------------------------------------- */
814
815 static int libcurl_close(URLContext *h);
816
817 static int debug_callback(CURL *easy, curl_infotype type, char *data,
818 size_t size, void *userdata)
819 {
820 CurlContext *c = userdata;
821 const char *prefix, *p = data, *end = data + size;
822
823 switch (type) {
824 case CURLINFO_TEXT: prefix = "* "; break;
825 case CURLINFO_HEADER_IN: prefix = "< "; break;
826 case CURLINFO_HEADER_OUT: prefix = "> "; break;
827 default: return 0;
828 }
829
830 /* Split multiline payload into each log. */
831 while (p < end) {
832 const char *nl = memchr(p, '\n', end - p);
833 size_t len = (nl ? nl : end) - p;
834 while (len && p[len - 1] == '\r')
835 len--;
836 av_log(c->h, AV_LOG_DEBUG, "%s%.*s\n", prefix, (int)len, p);
837 if (!nl)
838 break;
839 p = nl + 1;
840 }
841 return 0;
842 }
843
844 /* Build the custom request header list from the referer and headers options. */
845 static struct curl_slist *build_headers(CurlContext *c)
846 {
847 struct curl_slist *list = NULL;
848
849 if (c->referer && c->referer[0]) {
850 char *h = av_asprintf("Referer: %s", c->referer);
851 if (h) {
852 list = curl_slist_append(list, h);
853 av_free(h);
854 }
855 }
856 if (c->headers && c->headers[0]) {
857 char *copy = av_strdup(c->headers);
858 char *line, *saveptr = NULL;
859 if (copy) {
860 for (line = av_strtok(copy, "\r\n", &saveptr); line;
861 line = av_strtok(NULL, "\r\n", &saveptr))
862 list = curl_slist_append(list, line);
863 av_free(copy);
864 }
865 }
866 return list;
867 }
868
869 static int setup_protocols(CurlContext *c)
870 {
871 const char *wl = c->h->protocol_whitelist;
872 const char *bl = c->h->protocol_blacklist;
873 if (!wl && !bl)
874 return 0;
875
876 AVBPrint bp;
877 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
878
879 curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
880 for (const char *const *p = info->protocols; *p; p++) {
881 const char *proto = *p;
882 if (av_strcasecmp(proto, "http") && av_strcasecmp(proto, "https"))
883 continue; /* only http(s) are supported by libcurl.c at the moment */
884 if (wl && av_match_list(proto, wl, ',') <= 0)
885 continue;
886 if (bl && av_match_list(proto, bl, ',') > 0)
887 continue;
888 if (bp.len)
889 av_bprint_chars(&bp, ',', 1);
890 av_bprintf(&bp, "%s", proto);
891 }
892
893 if (!av_bprint_is_complete(&bp)) {
894 av_bprint_finalize(&bp, NULL);
895 return AVERROR(ENOMEM);
896 }
897
898 if (!bp.len) {
899 av_log(c->h, AV_LOG_ERROR, "Set of allowed protocols is empty.\n");
900 av_bprint_finalize(&bp, NULL);
901 return AVERROR(EINVAL);
902 }
903
904 curl_easy_setopt(c->easy, CURLOPT_PROTOCOLS_STR, bp.str);
905 curl_easy_setopt(c->easy, CURLOPT_REDIR_PROTOCOLS_STR, bp.str);
906 av_bprint_finalize(&bp, NULL);
907 return 0;
908 }
909
910 static void setup_curl(CurlContext *c)
911 {
912 CURL *e = c->easy;
913 const char *url = c->h->filename;
914
915 /* Drop an optional "libcurl:" prefix that forces this protocol. */
916 av_strstart(url, "libcurl:", &url);
917
918 curl_easy_setopt(e, CURLOPT_URL, url);
919 curl_easy_setopt(e, CURLOPT_PRIVATE, c);
920 curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L);
921 curl_easy_setopt(e, CURLOPT_SHARE, c->loop->share);
922
923 curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, write_callback);
924 curl_easy_setopt(e, CURLOPT_WRITEDATA, c);
925 curl_easy_setopt(e, CURLOPT_HEADERFUNCTION, header_callback);
926 curl_easy_setopt(e, CURLOPT_HEADERDATA, c);
927
928 curl_easy_setopt(e, CURLOPT_NOPROGRESS, 0L);
929 curl_easy_setopt(e, CURLOPT_XFERINFOFUNCTION, xferinfo_callback);
930 curl_easy_setopt(e, CURLOPT_XFERINFODATA, c);
931
932 if (av_log_get_level() >= AV_LOG_DEBUG) {
933 curl_easy_setopt(e, CURLOPT_VERBOSE, 1L);
934 curl_easy_setopt(e, CURLOPT_DEBUGFUNCTION, debug_callback);
935 curl_easy_setopt(e, CURLOPT_DEBUGDATA, c);
936 }
937
938 curl_easy_setopt(e, CURLOPT_FOLLOWLOCATION, 1L);
939 curl_easy_setopt(e, CURLOPT_MAXREDIRS, (long)c->max_redirects);
940 curl_easy_setopt(e, CURLOPT_HTTP_VERSION, (long)c->http_version);
941 curl_easy_setopt(e, CURLOPT_TCP_KEEPALIVE, c->multiple_requests ? 1L : 0L);
942 curl_easy_setopt(e, CURLOPT_FORBID_REUSE, c->multiple_requests ? 0L : 1L);
943 curl_easy_setopt(e, CURLOPT_HSTS_CTRL, (long)CURLHSTS_ENABLE);
944 curl_easy_setopt(e, CURLOPT_ACCEPT_ENCODING,
945 c->off > 0 || c->end_off > 0 ? "identity" : "");
946 if (c->connect_timeout > 0)
947 curl_easy_setopt(e, CURLOPT_CONNECTTIMEOUT_MS,
948 (long)c->connect_timeout * 1000);
949
950 if (c->user_agent && c->user_agent[0])
951 curl_easy_setopt(e, CURLOPT_USERAGENT, c->user_agent);
952 if (c->http_proxy && c->http_proxy[0])
953 curl_easy_setopt(e, CURLOPT_PROXY, c->http_proxy);
954
955 curl_easy_setopt(e, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
956 curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, c->tls_verify ? 1L : 0L);
957 curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, c->tls_verify ? 2L : 0L);
958 if (c->ca_file)
959 curl_easy_setopt(e, CURLOPT_CAINFO, c->ca_file);
960 if (c->cert_file)
961 curl_easy_setopt(e, CURLOPT_SSLCERT, c->cert_file);
962 if (c->key_file)
963 curl_easy_setopt(e, CURLOPT_SSLKEY, c->key_file);
964
965 curl_easy_setopt(e, CURLOPT_COOKIEFILE, "");
966 if (c->cookies && c->cookies[0]) {
967 char *copy = av_strdup(c->cookies);
968 char *line, *saveptr = NULL;
969 if (copy) {
970 for (line = av_strtok(copy, "\r\n", &saveptr); line;
971 line = av_strtok(NULL, "\r\n", &saveptr)) {
972 char *sc = av_asprintf("Set-Cookie: %s", line);
973 if (sc) {
974 curl_easy_setopt(e, CURLOPT_COOKIELIST, sc);
975 av_free(sc);
976 }
977 }
978 av_free(copy);
979 }
980 }
981
982 c->header_list = build_headers(c);
983 if (c->header_list)
984 curl_easy_setopt(e, CURLOPT_HTTPHEADER, c->header_list);
985 }
986
987 static void curl_cond_wait(CurlContext *c)
988 {
989 int64_t t = av_gettime() + CURL_WAIT_US;
990 struct timespec ts = { .tv_sec = t / 1000000,
991 .tv_nsec = (t % 1000000) * 1000 };
992 pthread_cond_timedwait(&c->cond, &c->mutex, &ts);
993 }
994
995 /* Block until the transfer has been probed, the stream errored, or the open was
996 * interrupted. Returns 0, or a negative AVERROR. */
997 static int wait_for_probe(CurlContext *c)
998 {
999 URLContext *h = c->h;
1000 int ret = 0;
1001
1002 pthread_mutex_lock(&c->mutex);
1003 while (!c->probed && !c->status) {
1004 if (ff_check_interrupt(&h->interrupt_callback)) {
1005 c->aborted = 1;
1006 ret = AVERROR_EXIT;
1007 break;
1008 }
1009 curl_cond_wait(c);
1010 }
1011 if (!ret) {
1012 if (!c->stream_ok)
1013 ret = c->status ? c->status : AVERROR(EIO);
1014 }
1015 pthread_mutex_unlock(&c->mutex);
1016
1017 return ret;
1018 }
1019
1020 static int libcurl_open(URLContext *h, const char *url, int flags,
1021 AVDictionary **options)
1022 {
1023 /* Guard against non-thread-safe libcurl builds. This should never happen,
1024 * since libcurl is used only on platforms with thread support, and thread
1025 * safety is enabled unconditionally in libcurl when the platform supports
1026 * threads or atomics. */
1027 curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
1028 if (!(info->features & CURL_VERSION_THREADSAFE))
1029 return AVERROR(ENOSYS);
1030
1031 CurlContext *c = h->priv_data;
1032 const char *eff_url = h->filename;
1033 int ret;
1034
1035 c->h = h;
1036 c->content_size = -1;
1037 c->request_start = c->off;
1038 c->request_end = -1;
1039 c->logical_pos = c->off;
1040 c->is_initial = 1;
1041
1042 /* Report the request URL until header_callback replaces it post-redirect. */
1043 av_strstart(eff_url, "libcurl:", &eff_url);
1044 av_freep(&c->location);
1045 c->location = av_strdup(eff_url);
1046
1047 ret = pthread_mutex_init(&c->mutex, NULL);
1048 if (ret)
1049 return AVERROR(ret);
1050 ret = pthread_cond_init(&c->cond, NULL);
1051 if (ret) {
1052 pthread_mutex_destroy(&c->mutex);
1053 return AVERROR(ret);
1054 }
1055
1056 c->fifo = av_fifo_alloc2(c->buffer_size, 1, 0);
1057 if (!c->fifo) {
1058 ret = AVERROR(ENOMEM);
1059 goto fail;
1060 }
1061
1062 ret = curl_loop_attach(c, h->avfc);
1063 if (ret < 0)
1064 goto fail;
1065
1066 c->easy = curl_easy_init();
1067 if (!c->easy) {
1068 ret = AVERROR(ENOMEM);
1069 goto fail;
1070 }
1071
1072 ret = setup_protocols(c);
1073 if (ret < 0)
1074 goto fail;
1075
1076 setup_curl(c);
1077
1078 ret = curl_dispatch(c->loop, CMD_ADD, c, 0, 0);
1079 if (ret < 0)
1080 goto fail;
1081
1082 ret = wait_for_probe(c);
1083 if (ret < 0)
1084 goto fail;
1085
1086 pthread_mutex_lock(&c->mutex);
1087 h->is_streamed = !c->seekable;
1088 pthread_mutex_unlock(&c->mutex);
1089
1090 return 0;
1091
1092 fail:
1093 libcurl_close(h);
1094 return ret;
1095 }
1096
1097 static int libcurl_read(URLContext *h, unsigned char *buf, int size)
1098 {
1099 CurlContext *c = h->priv_data;
1100 int nonblock = h->flags & AVIO_FLAG_NONBLOCK;
1101 int ret;
1102
1103 pthread_mutex_lock(&c->mutex);
1104 while (1) {
1105 size_t avail = av_fifo_can_read(c->fifo);
1106
1107 if (avail) {
1108 int n = FFMIN(avail, (size_t)size);
1109 int unpause;
1110 av_fifo_read(c->fifo, buf, n);
1111 /* Resume a paused transfer once the FIFO is at least half empty. */
1112 unpause = c->paused && av_fifo_can_write(c->fifo) * 2 >= c->buffer_size;
1113 c->logical_pos += n;
1114 pthread_mutex_unlock(&c->mutex);
1115 if (unpause)
1116 curl_dispatch(c->loop, CMD_UNPAUSE, c, 0, 0);
1117 return n;
1118 }
1119 if (c->status) {
1120 ret = c->status;
1121 break;
1122 }
1123 if (nonblock) {
1124 ret = AVERROR(EAGAIN);
1125 break;
1126 }
1127 curl_cond_wait(c);
1128 /* Return to the avio layer so it can poll the interrupt callback. */
1129 nonblock = 1;
1130 }
1131 pthread_mutex_unlock(&c->mutex);
1132
1133 return ret;
1134 }
1135
1136 static int64_t libcurl_seek(URLContext *h, int64_t pos, int whence)
1137 {
1138 CurlContext *c = h->priv_data;
1139 int64_t newpos;
1140
1141 pthread_mutex_lock(&c->mutex);
1142 const int64_t content_size = c->content_size;
1143 const int seekable = c->seekable;
1144 pthread_mutex_unlock(&c->mutex);
1145
1146 if (whence == AVSEEK_SIZE)
1147 return content_size >= 0 ? content_size : AVERROR(ENOSYS);
1148
1149 if (!seekable)
1150 return AVERROR(ENOSYS);
1151
1152 switch (whence) {
1153 case SEEK_SET:
1154 newpos = pos;
1155 break;
1156 case SEEK_CUR:
1157 if (pos > INT64_MAX - c->logical_pos)
1158 return AVERROR(ERANGE);
1159 newpos = c->logical_pos + pos;
1160 break;
1161 case SEEK_END:
1162 if (content_size < 0)
1163 return AVERROR(ENOSYS);
1164 if (pos > INT64_MAX - content_size)
1165 return AVERROR(ERANGE);
1166 newpos = content_size + pos;
1167 break;
1168 default:
1169 return AVERROR(EINVAL);
1170 }
1171 if (newpos < 0)
1172 return AVERROR(EINVAL);
1173
1174 if (newpos == c->logical_pos)
1175 return newpos;
1176
1177 /* Restart the transfer at the new offset. Any failure of the new request
1178 * surfaces on the following url_read(). */
1179 curl_dispatch(c->loop, CMD_SEEK, c, newpos, 1);
1180 c->logical_pos = newpos;
1181
1182 return newpos;
1183 }
1184
1185 static int libcurl_close(URLContext *h)
1186 {
1187 CurlContext *c = h->priv_data;
1188
1189 if (c->loop) {
1190 if (c->easy) {
1191 /* Ensure the handle is out of the multi before we free it. */
1192 curl_dispatch(c->loop, CMD_REMOVE, c, 0, 1);
1193 curl_easy_cleanup(c->easy);
1194 c->easy = NULL;
1195 }
1196 /* A shared loop outlives the transfer for connection reuse. */
1197 if (c->private_loop)
1198 curl_loop_destroy(c->loop);
1199 c->loop = NULL;
1200 }
1201
1202 if (c->header_list)
1203 curl_slist_free_all(c->header_list);
1204 av_fifo_freep2(&c->fifo);
1205 pthread_cond_destroy(&c->cond);
1206 pthread_mutex_destroy(&c->mutex);
1207
1208 return 0;
1209 }
1210
1211 static int libcurl_get_short_seek(URLContext *h)
1212 {
1213 CurlContext *c = h->priv_data;
1214 if (c->short_seek_size >= 1)
1215 return FFMIN(c->short_seek_size, INT_MAX);
1216 return AVERROR(ENOSYS);
1217 }
1218
1219 #define OFFSET(x) offsetof(CurlContext, x)
1220 #define D AV_OPT_FLAG_DECODING_PARAM
1221 #define E AV_OPT_FLAG_ENCODING_PARAM
1222 static const AVOption options[] = {
1223 { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D },
1224 { "referer", "override Referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
1225 { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1226 { "http_proxy", "set HTTP proxy to tunnel through", OFFSET(http_proxy), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1227 { "cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
1228 { "location", "the actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1229 { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
1230 { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
1231 { "seekable", "control seekability of connection", OFFSET(seekable_opt), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D },
1232 { "tls_verify", "verify the peer certificate and hostname", OFFSET(tls_verify), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D | E },
1233 { "ca_file", "certificate authority bundle file", OFFSET(ca_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1234 { "cert_file", "client certificate file", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1235 { "key_file", "client private key file", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
1236 { "connect_timeout", "connection timeout in seconds (0 = libcurl default)", OFFSET(connect_timeout), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX / 1000, D | E },
1237 { "max_redirects", "maximum number of redirects to follow", OFFSET(max_redirects), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, D },
1238 { "multiple_requests", "reuse the connection across requests (HTTP keep-alive)", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D | E },
1239 { "max_retries", "maximum number of retries after a recoverable error", OFFSET(max_retries), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, D },
1240 { "buffer_size", "receive buffer size in bytes", OFFSET(buffer_size), AV_OPT_TYPE_INT64, { .i64 = CURL_DEFAULT_BUFFER_SIZE }, CURL_MAX_WRITE_SIZE, INT_MAX, D },
1241 { "request_size", "split a transfer into ranged requests of at most this many bytes (0 = unlimited)", OFFSET(request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
1242 { "initial_request_size", "size (in bytes) of initial requests made during probing / header parsing", OFFSET(initial_request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
1243 { "http_version", "HTTP version to use", OFFSET(http_version), AV_OPT_TYPE_INT, { .i64 = CURL_HTTP_VERSION_NONE }, 0, INT_MAX, D, .unit = "http_version" },
1244 { "auto", "negotiate the best supported version", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_NONE }, 0, 0, D, .unit = "http_version" },
1245 { "1.0", "HTTP/1.0", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_1_0 }, 0, 0, D, .unit = "http_version" },
1246 { "1.1", "HTTP/1.1", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_1_1 }, 0, 0, D, .unit = "http_version" },
1247 { "2", "HTTP/2", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_2 }, 0, 0, D, .unit = "http_version" },
1248 { "2tls", "HTTP/2 over TLS only", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_2TLS }, 0, 0, D, .unit = "http_version" },
1249 { "2-prior-knowledge", "HTTP/2 without an upgrade handshake", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, 0, 0, D, .unit = "http_version" },
1250 { "3", "HTTP/3, fall back to earlier versions", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_3 }, 0, 0, D, .unit = "http_version" },
1251 { "3only", "HTTP/3 only", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_3ONLY }, 0, 0, D, .unit = "http_version" },
1252 { "short_seek_size", "threshold to favor readahead over seek", OFFSET(short_seek_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
1253 { NULL }
1254 };
1255
1256 static const AVClass libcurl_context_class = {
1257 .class_name = "libcurl",
1258 .item_name = av_default_item_name,
1259 .option = options,
1260 .version = LIBAVUTIL_VERSION_INT,
1261 };
1262
1263 const URLProtocol ff_libcurl_protocol = {
1264 .name = "libcurl",
1265 .url_open2 = libcurl_open,
1266 .url_read = libcurl_read,
1267 .url_seek = libcurl_seek,
1268 .url_close = libcurl_close,
1269 .url_get_short_seek = libcurl_get_short_seek,
1270 .priv_data_size = sizeof(CurlContext),
1271 .priv_data_class = &libcurl_context_class,
1272 .flags = URL_PROTOCOL_FLAG_NETWORK,
1273 .default_whitelist = "http,https,libcurl",
1274 };
1275