Skip to content

avformat/libcurl: seeking to (or past) EOF returns an HTTP 416 error instead of AVERROR_EOF #74

Description

@ronag

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():

    FFmpeg/libavformat/libcurl.c

    Lines 1257 to 1305 in fe63f8f

    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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinglibcurllibcurl-based HTTP(S) protocol (libavformat/libcurl.c)prio:mediumupstreamAlso present in upstream FFmpeg

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions