FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/libcurl.c
Date: 2026-10-19 20:11:44
Exec Total Coverage
Lines: 3 976 0.3%
Functions: 1 45 2.2%
Branches: 2 705 0.3%

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