Summary
libcurl_seek() only rejects negative targets. A seek to newpos >= content_size dispatches CMD_SEEK, which sends Range: bytes=<size>-. The server answers 416, header_callback() maps it through ff_http_averror(), and the next read returns AVERROR_HTTP_RANGE_NOT_SATISFIABLE, which avio latches in pb->error. Seeking to EOF is routine (avio_seek(pb, 0, SEEK_END), demuxers probing trailers or index tables).
Native http.c short-circuits exactly this case (/* do not try to make a new connection if seeking past the end of the file */, http.c:2259-2263).
With end_offset set and a target >= end_offset, start_request() even builds an inverted range (start > end), which servers either reject or ignore. If they ignore it, the reply trips the "unexpected reply offset" check.
Location
libcurl_seek():
|
static int64_t libcurl_seek(URLContext *h, int64_t pos, int whence) |
|
{ |
|
CurlContext *c = h->priv_data; |
|
int64_t newpos; |
|
|
|
pthread_mutex_lock(&c->mutex); |
|
const int64_t content_size = c->content_size; |
|
const int seekable = c->seekable; |
|
pthread_mutex_unlock(&c->mutex); |
|
|
|
if (whence == AVSEEK_SIZE) |
|
return content_size >= 0 ? content_size : AVERROR(ENOSYS); |
|
|
|
if (!seekable) |
|
return AVERROR(ENOSYS); |
|
|
|
switch (whence) { |
|
case SEEK_SET: |
|
newpos = pos; |
|
break; |
|
case SEEK_CUR: |
|
if (pos > INT64_MAX - c->logical_pos) |
|
return AVERROR(ERANGE); |
|
newpos = c->logical_pos + pos; |
|
break; |
|
case SEEK_END: |
|
if (content_size < 0) |
|
return AVERROR(ENOSYS); |
|
if (pos > INT64_MAX - content_size) |
|
return AVERROR(ERANGE); |
|
newpos = content_size + pos; |
|
break; |
|
default: |
|
return AVERROR(EINVAL); |
|
} |
|
if (newpos < 0) |
|
return AVERROR(EINVAL); |
|
|
|
if (newpos == c->logical_pos) |
|
return newpos; |
|
|
|
/* Restart the transfer at the new offset. Any failure of the new request |
|
* surfaces on the following url_read(). */ |
|
curl_dispatch(c->loop, CMD_SEEK, c, newpos, 1); |
|
c->logical_pos = newpos; |
|
c->retry_count = 0; |
|
|
|
return newpos; |
|
} |
CMD_SEEK:
|
case CMD_SEEK: |
|
if (c->active && test_short_seek(c)) { |
|
c->seek_queued = 1; |
|
} else if (c->active) { |
|
curl_multi_remove_handle(loop->multi, c->easy); |
|
c->active = 0; |
|
} |
|
pthread_mutex_lock(&c->mutex); |
|
av_fifo_reset2(c->fifo); |
|
const int was_paused = c->paused; |
|
c->paused = 0; |
|
c->status = 0; |
|
pthread_mutex_unlock(&c->mutex); |
|
c->request_start = cmd->pos; |
|
c->request_received = 0; |
|
if (!c->seek_queued) |
|
start_request(c); |
|
else if (was_paused) |
|
curl_easy_pause(c->easy, CURLPAUSE_CONT); |
|
break; |
start_request() range computation:
|
if (!c->probed || c->seekable) { |
|
int64_t start = c->request_start; |
|
char range[48]; |
|
int64_t request_size = c->request_size; |
|
if (c->is_initial && c->initial_request_size > 0) |
|
request_size = c->initial_request_size; |
|
if (request_size > 0 || c->end_off > 0) { |
|
int64_t end = INT64_MAX; |
|
if (request_size > 0 && start <= INT64_MAX - request_size) |
|
end = start + request_size - 1; |
|
if (c->content_size > 0) |
|
end = FFMIN(end, c->content_size - 1); |
|
if (c->end_off > 0) |
|
end = FFMIN(end, c->end_off - 1); |
|
snprintf(range, sizeof(range), "%"PRId64"-%"PRId64, start, end); |
|
} else { |
|
snprintf(range, sizeof(range), "%"PRId64"-", start); |
|
} |
Reproduction
1 MiB resource with a known size (harness from #71):
$ t libcurl:http://127.0.0.1:18080/file --seek 1048576
size: 1048576 seekable: 1
seek(1048576): 1048576
read: -909194488 (Server returned 416 Range Not Satisfiable) pb->error=-909194488
Expected: AVERROR_EOF, with pb->error not set.
Suggested fix
In CMD_SEEK, when the effective end (content_size, clamped by end_off) is known and pos >= end, remove the active transfer, reset the FIFO and set status = AVERROR_EOF without starting a request. The next forward read then returns EOF, and a later seek backwards clears it as usual.
Summary
libcurl_seek()only rejects negative targets. A seek tonewpos >= content_sizedispatchesCMD_SEEK, which sendsRange: bytes=<size>-. The server answers416,header_callback()maps it throughff_http_averror(), and the next read returnsAVERROR_HTTP_RANGE_NOT_SATISFIABLE, which avio latches inpb->error. Seeking to EOF is routine (avio_seek(pb, 0, SEEK_END), demuxers probing trailers or index tables).Native http.c short-circuits exactly this case (
/* do not try to make a new connection if seeking past the end of the file */, http.c:2259-2263).With
end_offsetset and a target>= end_offset,start_request()even builds an inverted range (start > end), which servers either reject or ignore. If they ignore it, the reply trips the "unexpected reply offset" check.Location
libcurl_seek():FFmpeg/libavformat/libcurl.c
Lines 1257 to 1305 in fe63f8f
CMD_SEEK:FFmpeg/libavformat/libcurl.c
Lines 633 to 652 in fe63f8f
start_request()range computation:FFmpeg/libavformat/libcurl.c
Lines 441 to 458 in fe63f8f
Reproduction
1 MiB resource with a known size (harness from #71):
Expected:
AVERROR_EOF, withpb->errornot set.Suggested fix
In
CMD_SEEK, when the effective end (content_size, clamped byend_off) is known andpos >= end, remove the active transfer, reset the FIFO and setstatus = AVERROR_EOFwithout starting a request. The next forward read then returns EOF, and a later seek backwards clears it as usual.