FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/libcurl.c
Date: 2026-09-28 11:37:26
Exec Total Coverage
Lines: 3 803 0.4%
Functions: 1 35 2.9%
Branches: 2 502 0.4%

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