PR #24825 opened by Kacper Michajłow (kasper93) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24825 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24825.patch
From 62af4e891b57d0c454775cace61a0dd0c7f1410f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:15:57 +0200 Subject: [PATCH 01/11] avformat/libcurl: take the open result from the first reply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decide whether the open succeeded from the first reply only, so that a follow-up request that the loop thread has already started cannot fail the open after the first reply turned out usable. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/libcurl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index 983b207722..519fcb6193 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -173,6 +173,7 @@ struct CurlContext { /* Probe result. Set by the loop thread, read by url_open() once probed. */ int probed; + int probe_ok; /* the first reply can be read */ int stream_ok; int seekable; int64_t content_size; @@ -612,6 +613,8 @@ static size_t header_callback(char *ptr, size_t size, size_t nitems, void *userd c->stream_ok = 0; update_status_locked(c, ff_http_averror(status, AVERROR(EIO)), CURLE_OK); } + if (!c->probed) + c->probe_ok = c->stream_ok; c->probed = 1; pthread_cond_broadcast(&c->cond); pthread_mutex_unlock(&c->mutex); @@ -1270,7 +1273,7 @@ static int wait_for_probe(CurlContext *c) curl_cond_wait(c); } if (!ret) { - if (!c->stream_ok) + if (!c->probe_ok) ret = c->status ? c->status : AVERROR(EIO); } pthread_mutex_unlock(&c->mutex); -- 2.52.0 From d47b6abad5bc96ee9289bc9b7ffae61b73b40013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:17:11 +0200 Subject: [PATCH 02/11] avformat/libcurl: stop at a follow-up reply without data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report an error instead of issuing the same request again when a reply that should continue the transfer delivered no data. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/libcurl.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index 519fcb6193..9aa5b807bf 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -747,6 +747,16 @@ static void on_done(CurlContext *c, CURLcode code) if (c->end_off > 0) file_end = FFMIN(file_end, c->end_off - 1); if (c->seekable && c->request_end >= 0 && c->request_end < file_end) { + if (!received) { + av_log(c->h, AV_LOG_ERROR, "Server sent back no data at offset " + "%"PRId64"\n", c->request_start); + c->loop->num_errors++; + pthread_mutex_lock(&c->mutex); + update_status_locked(c, AVERROR(EIO), CURLE_OK); + pthread_cond_broadcast(&c->cond); + pthread_mutex_unlock(&c->mutex); + return; + } c->is_initial = 0; start_request(c); return; -- 2.52.0 From 8922ffef2bd3311b39f3da752858ea931a916d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Sat, 26 Sep 2026 20:28:43 +0200 Subject: [PATCH 03/11] avformat/libcurl: don't issue requests past the end of the file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report EOF without a request when the requested offset is at or past the known file size or the end offset. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/libcurl.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index 9aa5b807bf..06cfca6d91 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -637,6 +637,17 @@ static int xferinfo_callback(void *userdata, curl_off_t dltotal, curl_off_t dlno * thread only. */ static void start_request(CurlContext *c) { + /* At or past the end of the file, report EOF without a request. */ + if (c->probed && c->seekable && + ((c->content_size >= 0 && c->request_start >= c->content_size) || + (c->end_off > 0 && c->request_start >= c->end_off))) { + pthread_mutex_lock(&c->mutex); + update_status_locked(c, AVERROR_EOF, CURLE_OK); + pthread_cond_broadcast(&c->cond); + pthread_mutex_unlock(&c->mutex); + return; + } + if (!c->probed || c->seekable) { int64_t start = c->request_start; char range[48]; -- 2.52.0 From a52ed56f6a9f9ad350c51809bbe552d4ba18b881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Sat, 26 Sep 2026 20:28:44 +0200 Subject: [PATCH 04/11] avformat/libcurl: keep requesting when the file size is unknown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep requesting from where each reply stopped when the file size is unknown ("Content-Range: bytes S-E/*"). Treat a 416 on such a follow-up request as EOF and take the size from its "bytes */N" unless N is past the requested offset. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/libcurl.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index 06cfca6d91..05a47458dc 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -608,6 +608,13 @@ static size_t header_callback(char *ptr, size_t size, size_t nitems, void *userd c->seekable = 0; c->content_size = -1; } + } else if (status == 416 && c->probed && c->content_size < 0) { + /* A 416 on a follow-up request of a file of unknown size is EOF. Take + * the size from the reply unless it is past the requested offset. */ + if (c->hdr_content_total >= 0 && c->hdr_content_total <= c->request_start) + c->content_size = c->hdr_content_total; + c->stream_ok = 0; + update_status_locked(c, AVERROR_EOF, CURLE_OK); } else { c->loop->num_errors++; c->stream_ok = 0; @@ -754,7 +761,8 @@ static void on_done(CurlContext *c, CURLcode code) } if (code == CURLE_OK && c->stream_ok) { - int64_t file_end = c->content_size > 0 ? c->content_size - 1 : -1; + /* Without a known size, continue until the server reports the end. */ + int64_t file_end = c->content_size >= 0 ? c->content_size - 1 : INT64_MAX; if (c->end_off > 0) file_end = FFMIN(file_end, c->end_off - 1); if (c->seekable && c->request_end >= 0 && c->request_end < file_end) { -- 2.52.0 From a3b4cd7f3a5a6e8c54cbec4cefb813c8ee1ec4f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:40:30 +0200 Subject: [PATCH 05/11] avformat/http: keep the file size when a request fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the file size together with the offset whenever opening a connection fails, also after too many redirects, so that the Content-Length of an error or redirect reply does not become the size of the resource. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index b7ae56f723..6eb9a29143 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -434,15 +434,18 @@ static int http_open_cnx(URLContext *h, AVDictionary **options) int ret, conn_attempts = 1, auth_attempts = 0, redirects = 0; int reconnect_delay = 0; int reconnect_delay_total = 0; - uint64_t off; + uint64_t off = s->off; + uint64_t filesize = s->filesize; char *cached; redo: cached = redirect_cache_get(s); if (cached) { - if (redirects++ >= s->max_redirects) - return AVERROR(EIO); + if (redirects++ >= s->max_redirects) { + ret = AVERROR(EIO); + goto fail; + } av_free(s->location); s->location = av_strdup(cached); @@ -515,8 +518,10 @@ redo: s->new_location) { /* url moved, get next */ ffurl_closep(&s->hd); - if (redirects++ >= s->max_redirects) - return AVERROR(EIO); + if (redirects++ >= s->max_redirects) { + ret = AVERROR(EIO); + goto fail; + } if (!s->expires) { s->expires = (s->http_code == 301 || s->http_code == 308) ? INT64_MAX : -1; @@ -541,6 +546,7 @@ redo: fail: s->off = off; + s->filesize = filesize; if (s->hd) ffurl_closep(&s->hd); if (ret < 0) -- 2.52.0 From 93af5b49f8e6f7ed869df836c967d3a08705b827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:03:38 +0200 Subject: [PATCH 06/11] avformat/http: keep the state of the old reply when a seek fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the offset after a failed seek, and restore the range end, chunk state and connection mode as well when reading goes on over the old connection. Refuse a seek on a streamed resource before it moves the offset. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 6eb9a29143..45b52c10e2 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -2204,6 +2204,9 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo HTTPContext *s = h->priv_data; URLContext *old_hd = NULL; uint64_t old_off = s->off; + uint64_t old_range_end = s->range_end; + uint64_t old_chunksize = s->chunksize; + int old_willclose = s->willclose; uint8_t old_buf[BUFFER_SIZE]; int old_buf_size, ret; AVDictionary *options = NULL; @@ -2224,10 +2227,9 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo return AVERROR(EINVAL); if (!force_reconnect && off == s->off) return s->off; - s->off = off; - - if (s->off && h->is_streamed) + if (off && h->is_streamed) return AVERROR(ENOSYS); + s->off = off; /* do not try to make a new connection if seeking past the end of the file */ if (s->end_off || s->filesize != UINT64_MAX) { @@ -2293,8 +2295,11 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo s->buf_ptr = s->buffer; s->buf_end = s->buffer + old_buf_size; s->hd = old_hd; - s->off = old_off; + s->range_end = old_range_end; + s->chunksize = old_chunksize; + s->willclose = old_willclose; } + s->off = old_off; av_dict_free(&options); return ret; } -- 2.52.0 From 8a5b47b70545a92c7c84ffb88d1b5fb0dcfd0d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:44:53 +0200 Subject: [PATCH 07/11] avformat/http: stop at the end offset or the end of the file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take the end of the data to read as the end offset or the file size, whichever comes first, when reading, when telling a broken connection from the end of the data, and when seeking past the end. Reading up to the end offset then ends with EOF instead of an I/O error, and an end offset past the end of the file no longer leads to a request past it. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 45b52c10e2..e7db233803 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1843,6 +1843,11 @@ done: return err; } +static uint64_t requested_end(HTTPContext *s) +{ + return s->end_off ? FFMIN(s->end_off, s->filesize) : s->filesize; +} + static int http_buf_read(URLContext *h, uint8_t *buf, int size) { HTTPContext *s = h->priv_data; @@ -1897,7 +1902,7 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size) memcpy(buf, s->buf_ptr, len); s->buf_ptr += len; } else { - uint64_t file_end = s->end_off ? s->end_off : s->filesize; + uint64_t file_end = requested_end(s); uint64_t target_end = s->range_end ? s->range_end : file_end; if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= file_end) return AVERROR_EOF; @@ -1968,7 +1973,7 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) int conn_attempts = 1; if (!s->hd) - return s->off < s->filesize ? AVERROR(EIO) : AVERROR_EOF; + return s->off < requested_end(s) ? AVERROR(EIO) : AVERROR_EOF; if (s->end_chunked_post && !s->end_header) { err = http_read_header(h); @@ -1985,7 +1990,8 @@ retry: read_ret = http_buf_read(h, buf, size); while (read_ret < 0) { uint64_t target = h->is_streamed ? 0 : s->off; - bool is_premature = s->filesize > 0 && s->off < s->filesize; + uint64_t file_end = requested_end(s); + bool is_premature = file_end > 0 && s->off < file_end; if (read_ret == AVERROR_EXIT) break; @@ -2232,11 +2238,8 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo s->off = off; /* do not try to make a new connection if seeking past the end of the file */ - if (s->end_off || s->filesize != UINT64_MAX) { - uint64_t end_pos = s->end_off ? s->end_off : s->filesize; - if (s->off >= end_pos) - return s->off; - } + if (s->off >= requested_end(s)) + return s->off; /* if the location changed (redirect), revert to the original uri */ if (strcmp(s->uri, s->location)) { -- 2.52.0 From eddbfadc3fc5bbd8d60ca2b48f6d4b3fa5e6a084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 01:45:44 +0200 Subject: [PATCH 08/11] avformat/http: drop the old reply when seeking past the end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close the connection and empty the buffer when a seek past the end is answered without a request, so that neither the buffered bytes nor the connection of the old reply are used at the new offset. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libavformat/http.c b/libavformat/http.c index e7db233803..2702679788 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -2238,8 +2238,12 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo s->off = off; /* do not try to make a new connection if seeking past the end of the file */ - if (s->off >= requested_end(s)) + if (s->off >= requested_end(s)) { + /* nothing of the old reply belongs to this offset */ + ffurl_closep(&s->hd); + s->buf_ptr = s->buf_end = s->buffer; return s->off; + } /* if the location changed (redirect), revert to the original uri */ if (strcmp(s->uri, s->location)) { -- 2.52.0 From f6575357a85c460ec3b7a654e1095c42dc50091d Mon Sep 17 00:00:00 2001 From: Andreas Menze <[email protected]> Date: Fri, 11 Sep 2026 10:35:29 +0200 Subject: [PATCH 09/11] avformat/http: keep complete size for Content-Range "bytes A-B/*" "bytes A-B/*" means the complete length is unknown (RFC 9110 14.4). There were two issues here. strtoull("*") returned 0 and clobbered a known filesize, and the 206 reply's Content-Length was published as s->filesize, so avio_size() reported a partial size and http_buf_read() hit EOF after the first part. Never derive the complete size from a partial reply. Remember it once learned from a 200 Content-Length or a "bytes A-B/N" total and reuse it for later "*" replies. If the complete size was never learned it stays unknown. Fixes #24210 --- doc/protocols.texi | 4 ++++ libavformat/http.c | 56 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/doc/protocols.texi b/doc/protocols.texi index 606ce07f3f..db2646a703 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -565,6 +565,10 @@ Reconnect automatically when disconnected before EOF is hit. @item reconnect_at_eof If set then eof is treated like an error and causes reconnection, this is useful for live / endless streams. +This does not apply to the deliberately established end of a resource whose +complete size is unknown, i.e. when a ranged request past the end is answered +with @code{416} or with a range that does not advance: such an end is reported +as a clean EOF and is not retried. @item reconnect_on_network_error Reconnect automatically in case of TCP/TLS errors during connect. diff --git a/libavformat/http.c b/libavformat/http.c index 2702679788..d0473728eb 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -122,6 +122,8 @@ typedef struct HTTPContext { HTTPAuthState auth_state; /* auth_state.auth_type is also a config option */ HTTPAuthState proxy_auth_state; uint64_t filesize; + uint64_t oc_filesize; /* Complete size (200 Content-Length or Content-Range total) */ + int end_of_resource; /* The end of an unknown-size resource was reached */ int is_akamai; int is_mediagateway; /* A dictionary containing cookies keyed by cookie name */ @@ -171,6 +173,8 @@ typedef struct HTTPContext { /* Temporary during header parsing */ uint64_t filesize_from_content_range; + /* Reply is a partial response with unknown total ("bytes A-B/" + '*'). */ + int content_range_unknown_size; int line_count; /****************** @@ -603,6 +607,8 @@ int ff_http_do_new_request2(URLContext *h, const char *uri, AVDictionary **opts) s->chunkend = 0; s->range_end = 0; s->off = 0; + s->oc_filesize = UINT64_MAX; + s->end_of_resource = 0; s->icy_data_read = 0; av_free(s->location); @@ -796,6 +802,8 @@ static int http_open(URLContext *h, const char *uri, int flags, s->initial_requests = s->seekable != 0 && s->initial_request_size > 0; s->filesize = UINT64_MAX; + s->oc_filesize = UINT64_MAX; + s->end_of_resource = 0; s->location = av_strdup(uri); if (!s->location) @@ -955,6 +963,7 @@ static int check_http_code(URLContext *h, int http_code, const char *end) if (http_code >= 400 && http_code < 600 && (http_code != 401 || s->auth_state.auth_type != HTTP_AUTH_NONE) && (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) { + end += strspn(end, SPACE_CHARS); av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", http_code, end); return ff_http_averror(http_code, AVERROR(EIO)); } @@ -990,8 +999,12 @@ static int parse_content_range(URLContext *h, const char *p) return AVERROR_INVALIDDATA; } } - if ((slash = strchr(p, '/')) && strlen(slash) > 0) - s->filesize_from_content_range = strtoull(slash + 1, NULL, 10); + if ((slash = strchr(p, '/')) && strlen(slash) > 0) { + if (slash[1] != '*') + s->filesize_from_content_range = strtoull(slash + 1, NULL, 10); + else + s->content_range_unknown_size = 1; /* unknown total size */ + } } if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647)) h->is_streamed = 0; /* we _can_ in fact seek */ @@ -1564,6 +1577,7 @@ static int http_read_header(URLContext *h) s->expires = 0; s->chunksize = UINT64_MAX; s->filesize_from_content_range = UINT64_MAX; + s->content_range_unknown_size = 0; for (;;) { int parsed_http_code = 0; @@ -1598,6 +1612,14 @@ static int http_read_header(URLContext *h) // filesize from Content-Range can always be used, even if using chunked Transfer-Encoding if (s->filesize_from_content_range != UINT64_MAX) s->filesize = s->filesize_from_content_range; + else if (s->content_range_unknown_size) + s->filesize = s->oc_filesize; + + /* Remember a concrete complete size for later unknown-total ("*") replies. */ + if (s->filesize_from_content_range != UINT64_MAX) + s->oc_filesize = s->filesize_from_content_range; + else if (s->http_code == 200 && s->filesize != UINT64_MAX) + s->oc_filesize = s->filesize; if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000) h->is_streamed = 1; /* we can in fact _not_ seek */ @@ -1972,6 +1994,9 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) int reconnect_delay_total = 0; int conn_attempts = 1; + if (s->end_of_resource) + return AVERROR_EOF; + if (!s->hd) return s->off < requested_end(s) ? AVERROR(EIO) : AVERROR_EOF; @@ -2003,8 +2028,32 @@ retry: s->initial_requests = 0; /* continue streaming uninterrupted from now on */ read_ret = http_open_cnx(h, &options); av_dict_free(&options); - if (read_ret == 0) + if (read_ret == 0) { + /* Don't request the same offset again. If the complete size is + * known, this is a truncation, which also is not expected. */ + if (s->range_end && s->range_end <= s->off) { + if (s->filesize != UINT64_MAX && s->off < s->filesize) { + av_log(h, AV_LOG_ERROR, "Non-advancing range at " + "%"PRIu64".\n", s->off); + read_ret = AVERROR(EIO); + } else { + s->end_of_resource = 1; + read_ret = AVERROR_EOF; + } + break; + } goto retry; + } + if (read_ret == AVERROR_EXIT) + break; + /* 416 for unknown-size resource is EOF, otherwise it is an error */ + if (read_ret == AVERROR_HTTP_OTHER_4XX && s->http_code == 416 && + s->oc_filesize == UINT64_MAX) { + s->filesize = s->oc_filesize; /* the 416 reply is not a size */ + s->end_of_resource = 1; + read_ret = AVERROR_EOF; + break; + } } if (h->is_streamed && !s->reconnect_streamed) @@ -2236,6 +2285,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo if (off && h->is_streamed) return AVERROR(ENOSYS); s->off = off; + s->end_of_resource = 0; /* do not try to make a new connection if seeking past the end of the file */ if (s->off >= requested_end(s)) { -- 2.52.0 From 20a326d76c4aff86b76439626253ac85d24a2446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 00:29:08 +0200 Subject: [PATCH 10/11] avformat/http: treat a 416 on seek past the end as EOF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the conversion of a 416 reply at the end of a resource of unknown size into a helper and use it for seeks too. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index d0473728eb..3ea1f37b85 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1983,6 +1983,17 @@ static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size) } #endif /* CONFIG_ZLIB */ +/* A 416 reply for a resource of unknown size marks its end. */ +static int http_end_of_resource(HTTPContext *s, int err) +{ + if (err != AVERROR_HTTP_OTHER_4XX || s->http_code != 416 || + s->oc_filesize != UINT64_MAX) + return 0; + s->filesize = s->oc_filesize; /* the 416 reply is not a size */ + s->end_of_resource = 1; + return 1; +} + static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int force_reconnect); static int http_read_stream(URLContext *h, uint8_t *buf, int size) @@ -2046,11 +2057,7 @@ retry: } if (read_ret == AVERROR_EXIT) break; - /* 416 for unknown-size resource is EOF, otherwise it is an error */ - if (read_ret == AVERROR_HTTP_OTHER_4XX && s->http_code == 416 && - s->oc_filesize == UINT64_MAX) { - s->filesize = s->oc_filesize; /* the 416 reply is not a size */ - s->end_of_resource = 1; + if (http_end_of_resource(s, read_ret)) { read_ret = AVERROR_EOF; break; } @@ -2085,6 +2092,10 @@ retry: av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRIu64".\n", target); return read_ret; } + if (s->end_of_resource) { + read_ret = AVERROR_EOF; + break; + } read_ret = http_buf_read(h, buf, size); } @@ -2262,6 +2273,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo uint64_t old_range_end = s->range_end; uint64_t old_chunksize = s->chunksize; int old_willclose = s->willclose; + int old_end_of_resource = s->end_of_resource; uint8_t old_buf[BUFFER_SIZE]; int old_buf_size, ret; AVDictionary *options = NULL; @@ -2332,7 +2344,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo } ret = http_open_cnx(h, &options); - if (ret >= 0) { + if (ret >= 0 || http_end_of_resource(s, ret)) { goto done; } else { /* fall back to normal reconnection */ @@ -2345,7 +2357,8 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo s->hd = NULL; } - if ((ret = http_open_cnx(h, &options)) < 0) { + if ((ret = http_open_cnx(h, &options)) < 0 && + !http_end_of_resource(s, ret)) { /* if it fails, continue on old connection if possible */ if (old_hd) { memcpy(s->buffer, old_buf, old_buf_size); @@ -2357,6 +2370,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo s->willclose = old_willclose; } s->off = old_off; + s->end_of_resource = old_end_of_resource; av_dict_free(&options); return ret; } -- 2.52.0 From 615de714c2ebc014b7924457e3291b6060b0bb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Thu, 1 Oct 2026 00:31:13 +0200 Subject: [PATCH 11/11] avformat/http: take the complete length from a 416 reply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take the complete length from the Content-Range of a 416 reply at the end of a resource of unknown size ("bytes */N") when it does not exceed the refused offset, so that later reads and seeks end at the known size without another request. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/http.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libavformat/http.c b/libavformat/http.c index 3ea1f37b85..a86018753a 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1989,7 +1989,10 @@ static int http_end_of_resource(HTTPContext *s, int err) if (err != AVERROR_HTTP_OTHER_4XX || s->http_code != 416 || s->oc_filesize != UINT64_MAX) return 0; - s->filesize = s->oc_filesize; /* the 416 reply is not a size */ + if (s->filesize_from_content_range != UINT64_MAX && + s->filesize_from_content_range <= s->off) + s->oc_filesize = s->filesize_from_content_range; + s->filesize = s->oc_filesize; s->end_of_resource = 1; return 1; } -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
