Merge commit 'fab8156b2f30666adabe227b3d7712fd193873b1'
authorDerek Buitenhuis <derek.buitenhuis@gmail.com>
Thu, 21 Apr 2016 14:55:09 +0000 (15:55 +0100)
committerDerek Buitenhuis <derek.buitenhuis@gmail.com>
Thu, 21 Apr 2016 14:55:09 +0000 (15:55 +0100)
* commit 'fab8156b2f30666adabe227b3d7712fd193873b1':
  avio: Copy URLContext generic options into child URLContexts

Merged-by: Derek Buitenhuis <derek.buitenhuis@gmail.com>
25 files changed:
1  2 
libavformat/async.c
libavformat/avio.c
libavformat/aviobuf.c
libavformat/cache.c
libavformat/concat.c
libavformat/crypto.c
libavformat/ftp.c
libavformat/gopher.c
libavformat/hlsproto.c
libavformat/http.c
libavformat/icecast.c
libavformat/md5proto.c
libavformat/mmst.c
libavformat/rtmpcrypt.c
libavformat/rtmpproto.c
libavformat/rtpproto.c
libavformat/rtsp.c
libavformat/rtspdec.c
libavformat/sapdec.c
libavformat/sapenc.c
libavformat/smoothstreamingenc.c
libavformat/srtpproto.c
libavformat/subfile.c
libavformat/tls.c
libavformat/url.h

index 0cc6fb0,0000000..54dbd23
mode 100644,000000..100644
--- /dev/null
@@@ -1,699 -1,0 +1,699 @@@
-     ret = ffurl_open_whitelist(&c->inner, arg, flags, &interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist);
 +/*
 + * Input async protocol.
 + * Copyright (c) 2015 Zhang Rui <bbcallen@gmail.com>
 + *
 + * This file is part of FFmpeg.
 + *
 + * FFmpeg is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation; either
 + * version 2.1 of the License, or (at your option) any later version.
 + *
 + * FFmpeg is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with FFmpeg; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + *
 + * Based on libavformat/cache.c by Michael Niedermayer
 + */
 +
 + /**
 + * @TODO
 + *      support timeout
 + *      support work with concatdec, hls
 + */
 +
 +#include "libavutil/avassert.h"
 +#include "libavutil/avstring.h"
 +#include "libavutil/error.h"
 +#include "libavutil/fifo.h"
 +#include "libavutil/log.h"
 +#include "libavutil/opt.h"
 +#include "libavutil/thread.h"
 +#include "url.h"
 +#include <stdint.h>
 +
 +#if HAVE_UNISTD_H
 +#include <unistd.h>
 +#endif
 +
 +#define BUFFER_CAPACITY         (4 * 1024 * 1024)
 +#define READ_BACK_CAPACITY      (4 * 1024 * 1024)
 +#define SHORT_SEEK_THRESHOLD    (256 * 1024)
 +
 +typedef struct RingBuffer
 +{
 +    AVFifoBuffer *fifo;
 +    int           read_back_capacity;
 +
 +    int           read_pos;
 +} RingBuffer;
 +
 +typedef struct Context {
 +    AVClass        *class;
 +    URLContext     *inner;
 +
 +    int             seek_request;
 +    int64_t         seek_pos;
 +    int             seek_whence;
 +    int             seek_completed;
 +    int64_t         seek_ret;
 +
 +    int             inner_io_error;
 +    int             io_error;
 +    int             io_eof_reached;
 +
 +    int64_t         logical_pos;
 +    int64_t         logical_size;
 +    RingBuffer      ring;
 +
 +    pthread_cond_t  cond_wakeup_main;
 +    pthread_cond_t  cond_wakeup_background;
 +    pthread_mutex_t mutex;
 +    pthread_t       async_buffer_thread;
 +
 +    int             abort_request;
 +    AVIOInterruptCB interrupt_callback;
 +} Context;
 +
 +static int ring_init(RingBuffer *ring, unsigned int capacity, int read_back_capacity)
 +{
 +    memset(ring, 0, sizeof(RingBuffer));
 +    ring->fifo = av_fifo_alloc(capacity + read_back_capacity);
 +    if (!ring->fifo)
 +        return AVERROR(ENOMEM);
 +
 +    ring->read_back_capacity = read_back_capacity;
 +    return 0;
 +}
 +
 +static void ring_destroy(RingBuffer *ring)
 +{
 +    av_fifo_freep(&ring->fifo);
 +}
 +
 +static void ring_reset(RingBuffer *ring)
 +{
 +    av_fifo_reset(ring->fifo);
 +    ring->read_pos = 0;
 +}
 +
 +static int ring_size(RingBuffer *ring)
 +{
 +    return av_fifo_size(ring->fifo) - ring->read_pos;
 +}
 +
 +static int ring_space(RingBuffer *ring)
 +{
 +    return av_fifo_space(ring->fifo);
 +}
 +
 +static int ring_generic_read(RingBuffer *ring, void *dest, int buf_size, void (*func)(void*, void*, int))
 +{
 +    int ret;
 +
 +    av_assert2(buf_size <= ring_size(ring));
 +    ret = av_fifo_generic_peek_at(ring->fifo, dest, ring->read_pos, buf_size, func);
 +    ring->read_pos += buf_size;
 +
 +    if (ring->read_pos > ring->read_back_capacity) {
 +        av_fifo_drain(ring->fifo, ring->read_pos - ring->read_back_capacity);
 +        ring->read_pos = ring->read_back_capacity;
 +    }
 +
 +    return ret;
 +}
 +
 +static int ring_generic_write(RingBuffer *ring, void *src, int size, int (*func)(void*, void*, int))
 +{
 +    av_assert2(size <= ring_space(ring));
 +    return av_fifo_generic_write(ring->fifo, src, size, func);
 +}
 +
 +static int ring_size_of_read_back(RingBuffer *ring)
 +{
 +    return ring->read_pos;
 +}
 +
 +static int ring_drain(RingBuffer *ring, int offset)
 +{
 +    av_assert2(offset >= -ring_size_of_read_back(ring));
 +    av_assert2(offset <= -ring_size(ring));
 +    ring->read_pos += offset;
 +    return 0;
 +}
 +
 +static int async_check_interrupt(void *arg)
 +{
 +    URLContext *h   = arg;
 +    Context    *c   = h->priv_data;
 +
 +    if (c->abort_request)
 +        return 1;
 +
 +    if (ff_check_interrupt(&c->interrupt_callback))
 +        c->abort_request = 1;
 +
 +    return c->abort_request;
 +}
 +
 +static int wrapped_url_read(void *src, void *dst, int size)
 +{
 +    URLContext *h   = src;
 +    Context    *c   = h->priv_data;
 +    int         ret;
 +
 +    ret = ffurl_read(c->inner, dst, size);
 +    c->inner_io_error = ret < 0 ? ret : 0;
 +
 +    return ret;
 +}
 +
 +static void *async_buffer_task(void *arg)
 +{
 +    URLContext   *h    = arg;
 +    Context      *c    = h->priv_data;
 +    RingBuffer   *ring = &c->ring;
 +    int           ret  = 0;
 +    int64_t       seek_ret;
 +
 +    while (1) {
 +        int fifo_space, to_copy;
 +
 +        pthread_mutex_lock(&c->mutex);
 +        if (async_check_interrupt(h)) {
 +            c->io_eof_reached = 1;
 +            c->io_error       = AVERROR_EXIT;
 +            pthread_cond_signal(&c->cond_wakeup_main);
 +            pthread_mutex_unlock(&c->mutex);
 +            break;
 +        }
 +
 +        if (c->seek_request) {
 +            seek_ret = ffurl_seek(c->inner, c->seek_pos, c->seek_whence);
 +            if (seek_ret >= 0) {
 +                c->io_eof_reached = 0;
 +                c->io_error       = 0;
 +                ring_reset(ring);
 +            }
 +
 +            c->seek_completed = 1;
 +            c->seek_ret       = seek_ret;
 +            c->seek_request   = 0;
 +
 +
 +            pthread_cond_signal(&c->cond_wakeup_main);
 +            pthread_mutex_unlock(&c->mutex);
 +            continue;
 +        }
 +
 +        fifo_space = ring_space(ring);
 +        if (c->io_eof_reached || fifo_space <= 0) {
 +            pthread_cond_signal(&c->cond_wakeup_main);
 +            pthread_cond_wait(&c->cond_wakeup_background, &c->mutex);
 +            pthread_mutex_unlock(&c->mutex);
 +            continue;
 +        }
 +        pthread_mutex_unlock(&c->mutex);
 +
 +        to_copy = FFMIN(4096, fifo_space);
 +        ret = ring_generic_write(ring, (void *)h, to_copy, wrapped_url_read);
 +
 +        pthread_mutex_lock(&c->mutex);
 +        if (ret <= 0) {
 +            c->io_eof_reached = 1;
 +            if (c->inner_io_error < 0)
 +                c->io_error = c->inner_io_error;
 +        }
 +
 +        pthread_cond_signal(&c->cond_wakeup_main);
 +        pthread_mutex_unlock(&c->mutex);
 +    }
 +
 +    return NULL;
 +}
 +
 +static int async_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
 +{
 +    Context         *c = h->priv_data;
 +    int              ret;
 +    AVIOInterruptCB  interrupt_callback = {.callback = async_check_interrupt, .opaque = h};
 +
 +    av_strstart(arg, "async:", &arg);
 +
 +    ret = ring_init(&c->ring, BUFFER_CAPACITY, READ_BACK_CAPACITY);
 +    if (ret < 0)
 +        goto fifo_fail;
 +
 +    /* wrap interrupt callback */
 +    c->interrupt_callback = h->interrupt_callback;
++    ret = ffurl_open_whitelist(&c->inner, arg, flags, &interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist, h);
 +    if (ret != 0) {
 +        av_log(h, AV_LOG_ERROR, "ffurl_open failed : %s, %s\n", av_err2str(ret), arg);
 +        goto url_fail;
 +    }
 +
 +    c->logical_size = ffurl_size(c->inner);
 +    h->is_streamed  = c->inner->is_streamed;
 +
 +    ret = pthread_mutex_init(&c->mutex, NULL);
 +    if (ret != 0) {
 +        av_log(h, AV_LOG_ERROR, "pthread_mutex_init failed : %s\n", av_err2str(ret));
 +        goto mutex_fail;
 +    }
 +
 +    ret = pthread_cond_init(&c->cond_wakeup_main, NULL);
 +    if (ret != 0) {
 +        av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", av_err2str(ret));
 +        goto cond_wakeup_main_fail;
 +    }
 +
 +    ret = pthread_cond_init(&c->cond_wakeup_background, NULL);
 +    if (ret != 0) {
 +        av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", av_err2str(ret));
 +        goto cond_wakeup_background_fail;
 +    }
 +
 +    ret = pthread_create(&c->async_buffer_thread, NULL, async_buffer_task, h);
 +    if (ret) {
 +        av_log(h, AV_LOG_ERROR, "pthread_create failed : %s\n", av_err2str(ret));
 +        goto thread_fail;
 +    }
 +
 +    return 0;
 +
 +thread_fail:
 +    pthread_cond_destroy(&c->cond_wakeup_background);
 +cond_wakeup_background_fail:
 +    pthread_cond_destroy(&c->cond_wakeup_main);
 +cond_wakeup_main_fail:
 +    pthread_mutex_destroy(&c->mutex);
 +mutex_fail:
 +    ffurl_close(c->inner);
 +url_fail:
 +    ring_destroy(&c->ring);
 +fifo_fail:
 +    return ret;
 +}
 +
 +static int async_close(URLContext *h)
 +{
 +    Context *c = h->priv_data;
 +    int      ret;
 +
 +    pthread_mutex_lock(&c->mutex);
 +    c->abort_request = 1;
 +    pthread_cond_signal(&c->cond_wakeup_background);
 +    pthread_mutex_unlock(&c->mutex);
 +
 +    ret = pthread_join(c->async_buffer_thread, NULL);
 +    if (ret != 0)
 +        av_log(h, AV_LOG_ERROR, "pthread_join(): %s\n", av_err2str(ret));
 +
 +    pthread_cond_destroy(&c->cond_wakeup_background);
 +    pthread_cond_destroy(&c->cond_wakeup_main);
 +    pthread_mutex_destroy(&c->mutex);
 +    ffurl_close(c->inner);
 +    ring_destroy(&c->ring);
 +
 +    return 0;
 +}
 +
 +static int async_read_internal(URLContext *h, void *dest, int size, int read_complete,
 +                               void (*func)(void*, void*, int))
 +{
 +    Context      *c       = h->priv_data;
 +    RingBuffer   *ring    = &c->ring;
 +    int           to_read = size;
 +    int           ret     = 0;
 +
 +    pthread_mutex_lock(&c->mutex);
 +
 +    while (to_read > 0) {
 +        int fifo_size, to_copy;
 +        if (async_check_interrupt(h)) {
 +            ret = AVERROR_EXIT;
 +            break;
 +        }
 +        fifo_size = ring_size(ring);
 +        to_copy   = FFMIN(to_read, fifo_size);
 +        if (to_copy > 0) {
 +            ring_generic_read(ring, dest, to_copy, func);
 +            if (!func)
 +                dest = (uint8_t *)dest + to_copy;
 +            c->logical_pos += to_copy;
 +            to_read        -= to_copy;
 +            ret             = size - to_read;
 +
 +            if (to_read <= 0 || !read_complete)
 +                break;
 +        } else if (c->io_eof_reached) {
 +            if (ret <= 0) {
 +                if (c->io_error)
 +                    ret = c->io_error;
 +                else
 +                    ret = AVERROR_EOF;
 +            }
 +            break;
 +        }
 +        pthread_cond_signal(&c->cond_wakeup_background);
 +        pthread_cond_wait(&c->cond_wakeup_main, &c->mutex);
 +    }
 +
 +    pthread_cond_signal(&c->cond_wakeup_background);
 +    pthread_mutex_unlock(&c->mutex);
 +
 +    return ret;
 +}
 +
 +static int async_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    return async_read_internal(h, buf, size, 0, NULL);
 +}
 +
 +static void fifo_do_not_copy_func(void* dest, void* src, int size) {
 +    // do not copy
 +}
 +
 +static int64_t async_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    Context      *c    = h->priv_data;
 +    RingBuffer   *ring = &c->ring;
 +    int64_t       ret;
 +    int64_t       new_logical_pos;
 +    int fifo_size;
 +    int fifo_size_of_read_back;
 +
 +    if (whence == AVSEEK_SIZE) {
 +        av_log(h, AV_LOG_TRACE, "async_seek: AVSEEK_SIZE: %"PRId64"\n", (int64_t)c->logical_size);
 +        return c->logical_size;
 +    } else if (whence == SEEK_CUR) {
 +        av_log(h, AV_LOG_TRACE, "async_seek: %"PRId64"\n", pos);
 +        new_logical_pos = pos + c->logical_pos;
 +    } else if (whence == SEEK_SET){
 +        av_log(h, AV_LOG_TRACE, "async_seek: %"PRId64"\n", pos);
 +        new_logical_pos = pos;
 +    } else {
 +        return AVERROR(EINVAL);
 +    }
 +    if (new_logical_pos < 0)
 +        return AVERROR(EINVAL);
 +
 +    fifo_size = ring_size(ring);
 +    fifo_size_of_read_back = ring_size_of_read_back(ring);
 +    if (new_logical_pos == c->logical_pos) {
 +        /* current position */
 +        return c->logical_pos;
 +    } else if ((new_logical_pos >= (c->logical_pos - fifo_size_of_read_back)) &&
 +               (new_logical_pos < (c->logical_pos + fifo_size + SHORT_SEEK_THRESHOLD))) {
 +        int pos_delta = (int)(new_logical_pos - c->logical_pos);
 +        /* fast seek */
 +        av_log(h, AV_LOG_TRACE, "async_seek: fask_seek %"PRId64" from %d dist:%d/%d\n",
 +                new_logical_pos, (int)c->logical_pos,
 +                (int)(new_logical_pos - c->logical_pos), fifo_size);
 +
 +        if (pos_delta > 0) {
 +            // fast seek forwards
 +            async_read_internal(h, NULL, pos_delta, 1, fifo_do_not_copy_func);
 +        } else {
 +            // fast seek backwards
 +            ring_drain(ring, pos_delta);
 +            c->logical_pos = new_logical_pos;
 +        }
 +
 +        return c->logical_pos;
 +    } else if (c->logical_size <= 0) {
 +        /* can not seek */
 +        return AVERROR(EINVAL);
 +    } else if (new_logical_pos > c->logical_size) {
 +        /* beyond end */
 +        return AVERROR(EINVAL);
 +    }
 +
 +    pthread_mutex_lock(&c->mutex);
 +
 +    c->seek_request   = 1;
 +    c->seek_pos       = new_logical_pos;
 +    c->seek_whence    = SEEK_SET;
 +    c->seek_completed = 0;
 +    c->seek_ret       = 0;
 +
 +    while (1) {
 +        if (async_check_interrupt(h)) {
 +            ret = AVERROR_EXIT;
 +            break;
 +        }
 +        if (c->seek_completed) {
 +            if (c->seek_ret >= 0)
 +                c->logical_pos  = c->seek_ret;
 +            ret = c->seek_ret;
 +            break;
 +        }
 +        pthread_cond_signal(&c->cond_wakeup_background);
 +        pthread_cond_wait(&c->cond_wakeup_main, &c->mutex);
 +    }
 +
 +    pthread_mutex_unlock(&c->mutex);
 +
 +    return ret;
 +}
 +
 +#define OFFSET(x) offsetof(Context, x)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +
 +static const AVOption options[] = {
 +    {NULL},
 +};
 +
 +#undef D
 +#undef OFFSET
 +
 +static const AVClass async_context_class = {
 +    .class_name = "Async",
 +    .item_name  = av_default_item_name,
 +    .option     = options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
 +const URLProtocol ff_async_protocol = {
 +    .name                = "async",
 +    .url_open2           = async_open,
 +    .url_read            = async_read,
 +    .url_seek            = async_seek,
 +    .url_close           = async_close,
 +    .priv_data_size      = sizeof(Context),
 +    .priv_data_class     = &async_context_class,
 +};
 +
 +#if 0
 +
 +#define TEST_SEEK_POS    (1536)
 +#define TEST_STREAM_SIZE (2048)
 +
 +typedef struct TestContext {
 +    AVClass        *class;
 +    int64_t         logical_pos;
 +    int64_t         logical_size;
 +
 +    /* options */
 +    int             opt_read_error;
 +} TestContext;
 +
 +static int async_test_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
 +{
 +    TestContext *c = h->priv_data;
 +    c->logical_pos  = 0;
 +    c->logical_size = TEST_STREAM_SIZE;
 +    return 0;
 +}
 +
 +static int async_test_close(URLContext *h)
 +{
 +    return 0;
 +}
 +
 +static int async_test_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    TestContext *c = h->priv_data;
 +    int          i;
 +    int          read_len = 0;
 +
 +    if (c->opt_read_error)
 +        return c->opt_read_error;
 +
 +    if (c->logical_pos >= c->logical_size)
 +        return AVERROR_EOF;
 +
 +    for (i = 0; i < size; ++i) {
 +        buf[i] = c->logical_pos & 0xFF;
 +
 +        c->logical_pos++;
 +        read_len++;
 +
 +        if (c->logical_pos >= c->logical_size)
 +            break;
 +    }
 +
 +    return read_len;
 +}
 +
 +static int64_t async_test_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    TestContext *c = h->priv_data;
 +    int64_t      new_logical_pos;
 +
 +    if (whence == AVSEEK_SIZE) {
 +        return c->logical_size;
 +    } else if (whence == SEEK_CUR) {
 +        new_logical_pos = pos + c->logical_pos;
 +    } else if (whence == SEEK_SET){
 +        new_logical_pos = pos;
 +    } else {
 +        return AVERROR(EINVAL);
 +    }
 +    if (new_logical_pos < 0)
 +        return AVERROR(EINVAL);
 +
 +    c->logical_pos = new_logical_pos;
 +    return new_logical_pos;
 +}
 +
 +#define OFFSET(x) offsetof(TestContext, x)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +
 +static const AVOption async_test_options[] = {
 +    { "async-test-read-error",      "cause read fail",
 +        OFFSET(opt_read_error),     AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, .flags = D },
 +    {NULL},
 +};
 +
 +#undef D
 +#undef OFFSET
 +
 +static const AVClass async_test_context_class = {
 +    .class_name = "Async-Test",
 +    .item_name  = av_default_item_name,
 +    .option     = async_test_options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
 +const URLProtocol ff_async_test_protocol = {
 +    .name                = "async-test",
 +    .url_open2           = async_test_open,
 +    .url_read            = async_test_read,
 +    .url_seek            = async_test_seek,
 +    .url_close           = async_test_close,
 +    .priv_data_size      = sizeof(TestContext),
 +    .priv_data_class     = &async_test_context_class,
 +};
 +
 +int main(void)
 +{
 +    URLContext   *h = NULL;
 +    int           i;
 +    int           ret;
 +    int64_t       size;
 +    int64_t       pos;
 +    int64_t       read_len;
 +    unsigned char buf[4096];
 +    AVDictionary *opts = NULL;
 +
 +    ffurl_register_protocol(&ff_async_protocol);
 +    ffurl_register_protocol(&ff_async_test_protocol);
 +
 +    /*
 +     * test normal read
 +     */
 +    ret = ffurl_open(&h, "async:async-test:", AVIO_FLAG_READ, NULL, NULL);
 +    printf("open: %d\n", ret);
 +
 +    size = ffurl_size(h);
 +    printf("size: %"PRId64"\n", size);
 +
 +    pos = ffurl_seek(h, 0, SEEK_CUR);
 +    read_len = 0;
 +    while (1) {
 +        ret = ffurl_read(h, buf, sizeof(buf));
 +        if (ret == AVERROR_EOF) {
 +            printf("read-error: AVERROR_EOF at %"PRId64"\n", ffurl_seek(h, 0, SEEK_CUR));
 +            break;
 +        }
 +        else if (ret == 0)
 +            break;
 +        else if (ret < 0) {
 +            printf("read-error: %d at %"PRId64"\n", ret, ffurl_seek(h, 0, SEEK_CUR));
 +            goto fail;
 +        } else {
 +            for (i = 0; i < ret; ++i) {
 +                if (buf[i] != (pos & 0xFF)) {
 +                    printf("read-mismatch: actual %d, expecting %d, at %"PRId64"\n",
 +                           (int)buf[i], (int)(pos & 0xFF), pos);
 +                    break;
 +                }
 +                pos++;
 +            }
 +        }
 +
 +        read_len += ret;
 +    }
 +    printf("read: %"PRId64"\n", read_len);
 +
 +    /*
 +     * test normal seek
 +     */
 +    ret = ffurl_read(h, buf, 1);
 +    printf("read: %d\n", ret);
 +
 +    pos = ffurl_seek(h, TEST_SEEK_POS, SEEK_SET);
 +    printf("seek: %"PRId64"\n", pos);
 +
 +    read_len = 0;
 +    while (1) {
 +        ret = ffurl_read(h, buf, sizeof(buf));
 +        if (ret == AVERROR_EOF)
 +            break;
 +        else if (ret == 0)
 +            break;
 +        else if (ret < 0) {
 +            printf("read-error: %d at %"PRId64"\n", ret, ffurl_seek(h, 0, SEEK_CUR));
 +            goto fail;
 +        } else {
 +            for (i = 0; i < ret; ++i) {
 +                if (buf[i] != (pos & 0xFF)) {
 +                    printf("read-mismatch: actual %d, expecting %d, at %"PRId64"\n",
 +                           (int)buf[i], (int)(pos & 0xFF), pos);
 +                    break;
 +                }
 +                pos++;
 +            }
 +        }
 +
 +        read_len += ret;
 +    }
 +    printf("read: %"PRId64"\n", read_len);
 +
 +    ret = ffurl_read(h, buf, 1);
 +    printf("read: %d\n", ret);
 +
 +    /*
 +     * test read error
 +     */
 +    ffurl_close(h);
 +    av_dict_set_int(&opts, "async-test-read-error", -10000, 0);
 +    ret = ffurl_open(&h, "async:async-test:", AVIO_FLAG_READ, NULL, &opts);
 +    printf("open: %d\n", ret);
 +
 +    ret = ffurl_read(h, buf, 1);
 +    printf("read: %d\n", ret);
 +
 +fail:
 +    av_dict_free(&opts);
 +    ffurl_close(h);
 +    return 0;
 +}
 +
 +#endif
@@@ -303,15 -172,16 +303,18 @@@ int ffurl_alloc(URLContext **puc, cons
      return AVERROR_PROTOCOL_NOT_FOUND;
  }
  
 -int ffurl_open(URLContext **puc, const char *filename, int flags,
 -               const AVIOInterruptCB *int_cb, AVDictionary **options,
 -               const URLProtocol **protocols,
 -               URLContext *parent)
 +int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
 +                         const AVIOInterruptCB *int_cb, AVDictionary **options,
-                          const char *whitelist, const char* blacklist)
++                         const char *whitelist, const char* blacklist,
++                         URLContext *parent)
  {
 -    int ret = ffurl_alloc(puc, filename, flags, int_cb, protocols);
 -    if (ret)
 +    AVDictionary *tmp_opts = NULL;
 +    AVDictionaryEntry *e;
 +    int ret = ffurl_alloc(puc, filename, flags, int_cb);
 +    if (ret < 0)
          return ret;
+     if (parent)
+         av_opt_copy(*puc, parent);
      if (options &&
          (ret = av_opt_set_dict(*puc, options)) < 0)
          goto fail;
@@@ -348,13 -197,6 +351,13 @@@ fail
      return ret;
  }
  
-                                 int_cb, options, NULL, NULL);
 +int ffurl_open(URLContext **puc, const char *filename, int flags,
 +               const AVIOInterruptCB *int_cb, AVDictionary **options)
 +{
 +    return ffurl_open_whitelist(puc, filename, flags,
++                                int_cb, options, NULL, NULL, NULL);
 +}
 +
  static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                           int size, int size_min,
                                           int (*transfer_func)(URLContext *h,
@@@ -989,9 -888,29 +989,9 @@@ int ffio_open_whitelist(AVIOContext **s
      URLContext *h;
      int err;
  
-     err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist);
 -    if (options) {
 -        e = av_dict_get(*options, "protocol_whitelist", NULL, 0);
 -        if (e)
 -            proto_whitelist = e->value;
 -        e = av_dict_get(*options, "protocol_blacklist", NULL, 0);
 -        if (e)
 -            proto_blacklist = e->value;
 -    }
 -
 -    protocols = ffurl_get_protocols(proto_whitelist, proto_blacklist);
 -    if (!protocols)
 -        return AVERROR(ENOMEM);
 -
 -    err = ffurl_open(&h, filename, flags, int_cb, options, protocols, NULL);
 -    if (err < 0) {
 -        av_freep(&protocols);
++    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
 +    if (err < 0)
          return err;
 -    }
 -
      err = ffio_fdopen(s, h);
      if (err < 0) {
          ffurl_close(h);
index 25b28d5,0000000..6aabca2
mode 100644,000000..100644
--- /dev/null
@@@ -1,330 -1,0 +1,330 @@@
-                                 options, h->protocol_whitelist, h->protocol_blacklist);
 +/*
 + * Input cache protocol.
 + * Copyright (c) 2011,2014 Michael Niedermayer
 + *
 + * This file is part of FFmpeg.
 + *
 + * FFmpeg is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation; either
 + * version 2.1 of the License, or (at your option) any later version.
 + *
 + * FFmpeg is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with FFmpeg; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + *
 + * Based on file.c by Fabrice Bellard
 + */
 +
 +/**
 + * @TODO
 + *      support keeping files
 + *      support filling with a background thread
 + */
 +
 +#include "libavutil/avassert.h"
 +#include "libavutil/avstring.h"
 +#include "libavutil/internal.h"
 +#include "libavutil/opt.h"
 +#include "libavutil/tree.h"
 +#include "avformat.h"
 +#include <fcntl.h>
 +#if HAVE_IO_H
 +#include <io.h>
 +#endif
 +#if HAVE_UNISTD_H
 +#include <unistd.h>
 +#endif
 +#include <sys/stat.h>
 +#include <stdlib.h>
 +#include "os_support.h"
 +#include "url.h"
 +
 +typedef struct CacheEntry {
 +    int64_t logical_pos;
 +    int64_t physical_pos;
 +    int size;
 +} CacheEntry;
 +
 +typedef struct Context {
 +    AVClass *class;
 +    int fd;
 +    struct AVTreeNode *root;
 +    int64_t logical_pos;
 +    int64_t cache_pos;
 +    int64_t inner_pos;
 +    int64_t end;
 +    int is_true_eof;
 +    URLContext *inner;
 +    int64_t cache_hit, cache_miss;
 +    int read_ahead_limit;
 +} Context;
 +
 +static int cmp(const void *key, const void *node)
 +{
 +    return FFDIFFSIGN(*(const int64_t *)key, ((const CacheEntry *) node)->logical_pos);
 +}
 +
 +static int cache_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
 +{
 +    char *buffername;
 +    Context *c= h->priv_data;
 +
 +    av_strstart(arg, "cache:", &arg);
 +
 +    c->fd = avpriv_tempfile("ffcache", &buffername, 0, h);
 +    if (c->fd < 0){
 +        av_log(h, AV_LOG_ERROR, "Failed to create tempfile\n");
 +        return c->fd;
 +    }
 +
 +    unlink(buffername);
 +    av_freep(&buffername);
 +
 +    return ffurl_open_whitelist(&c->inner, arg, flags, &h->interrupt_callback,
++                                options, h->protocol_whitelist, h->protocol_blacklist, h);
 +}
 +
 +static int add_entry(URLContext *h, const unsigned char *buf, int size)
 +{
 +    Context *c= h->priv_data;
 +    int64_t pos = -1;
 +    int ret;
 +    CacheEntry *entry = NULL, *next[2] = {NULL, NULL};
 +    CacheEntry *entry_ret;
 +    struct AVTreeNode *node = NULL;
 +
 +    //FIXME avoid lseek
 +    pos = lseek(c->fd, 0, SEEK_END);
 +    if (pos < 0) {
 +        ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "seek in cache failed\n");
 +        goto fail;
 +    }
 +    c->cache_pos = pos;
 +
 +    ret = write(c->fd, buf, size);
 +    if (ret < 0) {
 +        ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "write in cache failed\n");
 +        goto fail;
 +    }
 +    c->cache_pos += ret;
 +
 +    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
 +
 +    if (!entry)
 +        entry = next[0];
 +
 +    if (!entry ||
 +        entry->logical_pos  + entry->size != c->logical_pos ||
 +        entry->physical_pos + entry->size != pos
 +    ) {
 +        entry = av_malloc(sizeof(*entry));
 +        node = av_tree_node_alloc();
 +        if (!entry || !node) {
 +            ret = AVERROR(ENOMEM);
 +            goto fail;
 +        }
 +        entry->logical_pos = c->logical_pos;
 +        entry->physical_pos = pos;
 +        entry->size = ret;
 +
 +        entry_ret = av_tree_insert(&c->root, entry, cmp, &node);
 +        if (entry_ret && entry_ret != entry) {
 +            ret = -1;
 +            av_log(h, AV_LOG_ERROR, "av_tree_insert failed\n");
 +            goto fail;
 +        }
 +    } else
 +        entry->size += ret;
 +
 +    return 0;
 +fail:
 +    //we could truncate the file to pos here if pos >=0 but ftruncate isn't available in VS so
 +    //for simplicty we just leave the file a bit larger
 +    av_free(entry);
 +    av_free(node);
 +    return ret;
 +}
 +
 +static int cache_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    Context *c= h->priv_data;
 +    CacheEntry *entry, *next[2] = {NULL, NULL};
 +    int64_t r;
 +
 +    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
 +
 +    if (!entry)
 +        entry = next[0];
 +
 +    if (entry) {
 +        int64_t in_block_pos = c->logical_pos - entry->logical_pos;
 +        av_assert0(entry->logical_pos <= c->logical_pos);
 +        if (in_block_pos < entry->size) {
 +            int64_t physical_target = entry->physical_pos + in_block_pos;
 +
 +            if (c->cache_pos != physical_target) {
 +                r = lseek(c->fd, physical_target, SEEK_SET);
 +            } else
 +                r = c->cache_pos;
 +
 +            if (r >= 0) {
 +                c->cache_pos = r;
 +                r = read(c->fd, buf, FFMIN(size, entry->size - in_block_pos));
 +            }
 +
 +            if (r > 0) {
 +                c->cache_pos += r;
 +                c->logical_pos += r;
 +                c->cache_hit ++;
 +                return r;
 +            }
 +        }
 +    }
 +
 +    // Cache miss or some kind of fault with the cache
 +
 +    if (c->logical_pos != c->inner_pos) {
 +        r = ffurl_seek(c->inner, c->logical_pos, SEEK_SET);
 +        if (r<0) {
 +            av_log(h, AV_LOG_ERROR, "Failed to perform internal seek\n");
 +            return r;
 +        }
 +        c->inner_pos = r;
 +    }
 +
 +    r = ffurl_read(c->inner, buf, size);
 +    if (r == 0 && size>0) {
 +        c->is_true_eof = 1;
 +        av_assert0(c->end >= c->logical_pos);
 +    }
 +    if (r<=0)
 +        return r;
 +    c->inner_pos += r;
 +
 +    c->cache_miss ++;
 +
 +    add_entry(h, buf, r);
 +    c->logical_pos += r;
 +    c->end = FFMAX(c->end, c->logical_pos);
 +
 +    return r;
 +}
 +
 +static int64_t cache_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    Context *c= h->priv_data;
 +    int64_t ret;
 +
 +    if (whence == AVSEEK_SIZE) {
 +        pos= ffurl_seek(c->inner, pos, whence);
 +        if(pos <= 0){
 +            pos= ffurl_seek(c->inner, -1, SEEK_END);
 +            if (ffurl_seek(c->inner, c->inner_pos, SEEK_SET) < 0)
 +                av_log(h, AV_LOG_ERROR, "Inner protocol failed to seekback end : %"PRId64"\n", pos);
 +        }
 +        if (pos > 0)
 +            c->is_true_eof = 1;
 +        c->end = FFMAX(c->end, pos);
 +        return pos;
 +    }
 +
 +    if (whence == SEEK_CUR) {
 +        whence = SEEK_SET;
 +        pos += c->logical_pos;
 +    } else if (whence == SEEK_END && c->is_true_eof) {
 +resolve_eof:
 +        whence = SEEK_SET;
 +        pos += c->end;
 +    }
 +
 +    if (whence == SEEK_SET && pos >= 0 && pos < c->end) {
 +        //Seems within filesize, assume it will not fail.
 +        c->logical_pos = pos;
 +        return pos;
 +    }
 +
 +    //cache miss
 +    ret= ffurl_seek(c->inner, pos, whence);
 +    if ((whence == SEEK_SET && pos >= c->logical_pos ||
 +         whence == SEEK_END && pos <= 0) && ret < 0) {
 +        if (   (whence == SEEK_SET && c->read_ahead_limit >= pos - c->logical_pos)
 +            || c->read_ahead_limit < 0) {
 +            uint8_t tmp[32768];
 +            while (c->logical_pos < pos || whence == SEEK_END) {
 +                int size = sizeof(tmp);
 +                if (whence == SEEK_SET)
 +                    size = FFMIN(sizeof(tmp), pos - c->logical_pos);
 +                ret = cache_read(h, tmp, size);
 +                if (ret == 0 && whence == SEEK_END) {
 +                    av_assert0(c->is_true_eof);
 +                    goto resolve_eof;
 +                }
 +                if (ret < 0) {
 +                    return ret;
 +                }
 +            }
 +            return c->logical_pos;
 +        }
 +    }
 +
 +    if (ret >= 0) {
 +        c->logical_pos = ret;
 +        c->end = FFMAX(c->end, ret);
 +    }
 +
 +    return ret;
 +}
 +
 +static int enu_free(void *opaque, void *elem)
 +{
 +    av_free(elem);
 +    return 0;
 +}
 +
 +static int cache_close(URLContext *h)
 +{
 +    Context *c= h->priv_data;
 +
 +    av_log(h, AV_LOG_INFO, "Statistics, cache hits:%"PRId64" cache misses:%"PRId64"\n",
 +           c->cache_hit, c->cache_miss);
 +
 +    close(c->fd);
 +    ffurl_close(c->inner);
 +    av_tree_enumerate(c->root, NULL, NULL, enu_free);
 +    av_tree_destroy(c->root);
 +
 +    return 0;
 +}
 +
 +#define OFFSET(x) offsetof(Context, x)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +
 +static const AVOption options[] = {
 +    { "read_ahead_limit", "Amount in bytes that may be read ahead when seeking isn't supported, -1 for unlimited", OFFSET(read_ahead_limit), AV_OPT_TYPE_INT, { .i64 = 65536 }, -1, INT_MAX, D },
 +    {NULL},
 +};
 +
 +static const AVClass cache_context_class = {
 +    .class_name = "Cache",
 +    .item_name  = av_default_item_name,
 +    .option     = options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
 +const URLProtocol ff_cache_protocol = {
 +    .name                = "cache",
 +    .url_open2           = cache_open,
 +    .url_read            = cache_read,
 +    .url_seek            = cache_seek,
 +    .url_close           = cache_close,
 +    .priv_data_size      = sizeof(Context),
 +    .priv_data_class     = &cache_context_class,
 +};
@@@ -97,9 -94,8 +97,9 @@@ static av_cold int concat_open(URLConte
          uri += len + strspn(uri + len, AV_CAT_SEPARATOR);
  
          /* creating URLContext */
 -        if ((err = ffurl_open(&uc, node_uri, flags,
 -                              &h->interrupt_callback, NULL, h->protocols, h)) < 0)
 +        err = ffurl_open_whitelist(&uc, node_uri, flags,
-                                    &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist);
++                                   &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist, h);
 +        if (err < 0)
              break;
  
          /* creating size */
@@@ -117,55 -72,28 +117,55 @@@ static int crypto_open2(URLContext *h, 
          goto err;
      }
  
 -    if (c->keylen < BLOCKSIZE || c->ivlen < BLOCKSIZE) {
 -        av_log(h, AV_LOG_ERROR, "Key or IV not set\n");
 -        ret = AVERROR(EINVAL);
 -        goto err;
 +    if (flags & AVIO_FLAG_READ) {
 +        if ((ret = set_aes_arg(c, &c->decrypt_key, &c->decrypt_keylen,
 +                               c->key, c->keylen, "decryption key")) < 0)
 +            goto err;
 +        if ((ret = set_aes_arg(c, &c->decrypt_iv, &c->decrypt_ivlen,
 +                               c->iv, c->ivlen, "decryption IV")) < 0)
 +            goto err;
      }
 +
      if (flags & AVIO_FLAG_WRITE) {
 -        av_log(h, AV_LOG_ERROR, "Only decryption is supported currently\n");
 -        ret = AVERROR(ENOSYS);
 -        goto err;
 +        if ((ret = set_aes_arg(c, &c->encrypt_key, &c->encrypt_keylen,
 +                               c->key, c->keylen, "encryption key")) < 0)
 +        if (ret < 0)
 +            goto err;
 +        if ((ret = set_aes_arg(c, &c->encrypt_iv, &c->encrypt_ivlen,
 +                               c->iv, c->ivlen, "encryption IV")) < 0)
 +            goto err;
      }
 -    if ((ret = ffurl_open(&c->hd, nested_url, AVIO_FLAG_READ,
 -                          &h->interrupt_callback, NULL, h->protocols, h)) < 0) {
 -        av_log(h, AV_LOG_ERROR, "Unable to open input\n");
 +
 +    if ((ret = ffurl_open_whitelist(&c->hd, nested_url, flags,
 +                                    &h->interrupt_callback, options,
-                                     h->protocol_whitelist, h->protocol_blacklist)) < 0) {
++                                    h->protocol_whitelist, h->protocol_blacklist, h)) < 0) {
 +        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url);
          goto err;
      }
 -    c->aes = av_aes_alloc();
 -    if (!c->aes) {
 -        ret = AVERROR(ENOMEM);
 -        goto err;
 +
 +    if (flags & AVIO_FLAG_READ) {
 +        c->aes_decrypt = av_aes_alloc();
 +        if (!c->aes_decrypt) {
 +            ret = AVERROR(ENOMEM);
 +            goto err;
 +        }
 +        ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE*8, 1);
 +        if (ret < 0)
 +            goto err;
      }
  
 -    av_aes_init(c->aes, c->key, 128, 1);
 +    if (flags & AVIO_FLAG_WRITE) {
 +        c->aes_encrypt = av_aes_alloc();
 +        if (!c->aes_encrypt) {
 +            ret = AVERROR(ENOMEM);
 +            goto err;
 +        }
 +        ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE*8, 0);
 +        if (ret < 0)
 +            goto err;
 +    }
 +
 +    c->pad_len = 0;
  
      h->is_streamed = 1;
  
index b9fee24,0000000..0663b47
mode 100644,000000..100644
--- /dev/null
@@@ -1,1124 -1,0 +1,1124 @@@
-                                    h->protocol_whitelist, h->protocol_blacklist);
 +/*
 + * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com>
 + *
 + * This file is part of FFmpeg.
 + *
 + * FFmpeg is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation; either
 + * version 2.1 of the License, or (at your option) any later version.
 + *
 + * FFmpeg is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with FFmpeg; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + */
 +
 +#include "libavutil/avstring.h"
 +#include "libavutil/internal.h"
 +#include "libavutil/parseutils.h"
 +#include "avformat.h"
 +#include "internal.h"
 +#include "url.h"
 +#include "libavutil/opt.h"
 +#include "libavutil/bprint.h"
 +
 +#define CONTROL_BUFFER_SIZE 1024
 +#define DIR_BUFFER_SIZE 4096
 +
 +typedef enum {
 +    UNKNOWN,
 +    READY,
 +    DOWNLOADING,
 +    UPLOADING,
 +    LISTING_DIR,
 +    DISCONNECTED
 +} FTPState;
 +
 +typedef enum {
 +    UNKNOWN_METHOD,
 +    NLST,
 +    MLSD
 +} FTPListingMethod;
 +
 +typedef struct {
 +    const AVClass *class;
 +    URLContext *conn_control;                    /**< Control connection */
 +    URLContext *conn_data;                       /**< Data connection, NULL when not connected */
 +    uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
 +    uint8_t *control_buf_ptr, *control_buf_end;
 +    int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
 +    int server_control_port;                     /**< Control connection port, default is 21 */
 +    char *hostname;                              /**< Server address. */
 +    char *user;                                  /**< Server user */
 +    char *password;                              /**< Server user's password */
 +    char *path;                                  /**< Path to resource on server. */
 +    int64_t filesize;                            /**< Size of file on server, -1 on error. */
 +    int64_t position;                            /**< Current position, calculated. */
 +    int rw_timeout;                              /**< Network timeout. */
 +    const char *anonymous_password;              /**< Password to be used for anonymous user. An email should be used. */
 +    int write_seekable;                          /**< Control seekability, 0 = disable, 1 = enable. */
 +    FTPState state;                              /**< State of data connection */
 +    FTPListingMethod listing_method;             /**< Called listing method */
 +    char *features;                              /**< List of server's features represented as raw response */
 +    char *dir_buffer;
 +    size_t dir_buffer_size;
 +    size_t dir_buffer_offset;
 +    int utf8;
 +} FTPContext;
 +
 +#define OFFSET(x) offsetof(FTPContext, x)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +#define E AV_OPT_FLAG_ENCODING_PARAM
 +static const AVOption options[] = {
 +    {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
 +    {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
 +    {"ftp-anonymous-password", "password for anonymous login. E-mail address should be used.", OFFSET(anonymous_password), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
 +    {NULL}
 +};
 +
 +static const AVClass ftp_context_class = {
 +    .class_name     = "ftp",
 +    .item_name      = av_default_item_name,
 +    .option         = options,
 +    .version        = LIBAVUTIL_VERSION_INT,
 +};
 +
 +static int ftp_close(URLContext *h);
 +
 +static int ftp_getc(FTPContext *s)
 +{
 +    int len;
 +    if (s->control_buf_ptr >= s->control_buf_end) {
 +        len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
 +        if (len < 0) {
 +            return len;
 +        } else if (!len) {
 +            return -1;
 +        } else {
 +            s->control_buf_ptr = s->control_buffer;
 +            s->control_buf_end = s->control_buffer + len;
 +        }
 +    }
 +    return *s->control_buf_ptr++;
 +}
 +
 +static int ftp_get_line(FTPContext *s, char *line, int line_size)
 +{
 +    int ch;
 +    char *q = line;
 +
 +    for (;;) {
 +        ch = ftp_getc(s);
 +        if (ch < 0) {
 +            return ch;
 +        }
 +        if (ch == '\n') {
 +            /* process line */
 +            if (q > line && q[-1] == '\r')
 +                q--;
 +            *q = '\0';
 +            return 0;
 +        } else {
 +            if ((q - line) < line_size - 1)
 +                *q++ = ch;
 +        }
 +    }
 +}
 +
 +/*
 + * This routine returns ftp server response code.
 + * Server may send more than one response for a certain command.
 + * First expected code is returned.
 + */
 +static int ftp_status(FTPContext *s, char **line, const int response_codes[])
 +{
 +    int err, i, dash = 0, result = 0, code_found = 0, linesize;
 +    char buf[CONTROL_BUFFER_SIZE];
 +    AVBPrint line_buffer;
 +
 +    if (line)
 +        av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
 +
 +    while (!code_found || dash) {
 +        if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
 +            if (line)
 +                av_bprint_finalize(&line_buffer, NULL);
 +            return err;
 +        }
 +
 +        av_log(s, AV_LOG_DEBUG, "%s\n", buf);
 +
 +        linesize = strlen(buf);
 +        err = 0;
 +        if (linesize >= 3) {
 +            for (i = 0; i < 3; ++i) {
 +                if (buf[i] < '0' || buf[i] > '9') {
 +                    err = 0;
 +                    break;
 +                }
 +                err *= 10;
 +                err += buf[i] - '0';
 +            }
 +        }
 +
 +        if (!code_found) {
 +            if (err >= 500) {
 +                code_found = 1;
 +                result = err;
 +            } else
 +                for (i = 0; response_codes[i]; ++i) {
 +                    if (err == response_codes[i]) {
 +                        code_found = 1;
 +                        result = err;
 +                        break;
 +                    }
 +                }
 +        }
 +        if (code_found) {
 +            if (line)
 +                av_bprintf(&line_buffer, "%s\r\n", buf);
 +            if (linesize >= 4) {
 +                if (!dash && buf[3] == '-')
 +                    dash = err;
 +                else if (err == dash && buf[3] == ' ')
 +                    dash = 0;
 +            }
 +        }
 +    }
 +
 +    if (line)
 +        av_bprint_finalize(&line_buffer, line);
 +    return result;
 +}
 +
 +static int ftp_send_command(FTPContext *s, const char *command,
 +                            const int response_codes[], char **response)
 +{
 +    int err;
 +
 +    ff_dlog(s, "%s", command);
 +
 +    if (response)
 +        *response = NULL;
 +
 +    if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
 +        return err;
 +    if (!err)
 +        return -1;
 +
 +    /* return status */
 +    if (response_codes) {
 +        return ftp_status(s, response, response_codes);
 +    }
 +    return 0;
 +}
 +
 +static void ftp_close_data_connection(FTPContext *s)
 +{
 +    ffurl_closep(&s->conn_data);
 +    s->position = 0;
 +    s->state = DISCONNECTED;
 +}
 +
 +static void ftp_close_both_connections(FTPContext *s)
 +{
 +    ffurl_closep(&s->conn_control);
 +    ftp_close_data_connection(s);
 +}
 +
 +static int ftp_auth(FTPContext *s)
 +{
 +    char buf[CONTROL_BUFFER_SIZE];
 +    int err;
 +    static const int user_codes[] = {331, 230, 0};
 +    static const int pass_codes[] = {230, 0};
 +
 +    snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
 +    err = ftp_send_command(s, buf, user_codes, NULL);
 +    if (err == 331) {
 +        if (s->password) {
 +            snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
 +            err = ftp_send_command(s, buf, pass_codes, NULL);
 +        } else
 +            return AVERROR(EACCES);
 +    }
 +    if (err != 230)
 +        return AVERROR(EACCES);
 +
 +    return 0;
 +}
 +
 +static int ftp_passive_mode_epsv(FTPContext *s)
 +{
 +    char *res = NULL, *start = NULL, *end = NULL;
 +    int i;
 +    static const char d = '|';
 +    static const char *command = "EPSV\r\n";
 +    static const int epsv_codes[] = {229, 0};
 +
 +    if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
 +        goto fail;
 +
 +    for (i = 0; res[i]; ++i) {
 +        if (res[i] == '(') {
 +            start = res + i + 1;
 +        } else if (res[i] == ')') {
 +            end = res + i;
 +            break;
 +        }
 +    }
 +    if (!start || !end)
 +        goto fail;
 +
 +    *end = '\0';
 +    if (strlen(start) < 5)
 +        goto fail;
 +    if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
 +        goto fail;
 +    start += 3;
 +    end[-1] = '\0';
 +
 +    s->server_data_port = atoi(start);
 +    ff_dlog(s, "Server data port: %d\n", s->server_data_port);
 +
 +    av_free(res);
 +    return 0;
 +
 +  fail:
 +    av_free(res);
 +    s->server_data_port = -1;
 +    return AVERROR(ENOSYS);
 +}
 +
 +static int ftp_passive_mode(FTPContext *s)
 +{
 +    char *res = NULL, *start = NULL, *end = NULL;
 +    int i;
 +    static const char *command = "PASV\r\n";
 +    static const int pasv_codes[] = {227, 0};
 +
 +    if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
 +        goto fail;
 +
 +    for (i = 0; res[i]; ++i) {
 +        if (res[i] == '(') {
 +            start = res + i + 1;
 +        } else if (res[i] == ')') {
 +            end = res + i;
 +            break;
 +        }
 +    }
 +    if (!start || !end)
 +        goto fail;
 +
 +    *end  = '\0';
 +    /* skip ip */
 +    if (!av_strtok(start, ",", &end)) goto fail;
 +    if (!av_strtok(end, ",", &end)) goto fail;
 +    if (!av_strtok(end, ",", &end)) goto fail;
 +    if (!av_strtok(end, ",", &end)) goto fail;
 +
 +    /* parse port number */
 +    start = av_strtok(end, ",", &end);
 +    if (!start) goto fail;
 +    s->server_data_port = atoi(start) * 256;
 +    start = av_strtok(end, ",", &end);
 +    if (!start) goto fail;
 +    s->server_data_port += atoi(start);
 +    ff_dlog(s, "Server data port: %d\n", s->server_data_port);
 +
 +    av_free(res);
 +    return 0;
 +
 +  fail:
 +    av_free(res);
 +    s->server_data_port = -1;
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_current_dir(FTPContext *s)
 +{
 +    char *res = NULL, *start = NULL, *end = NULL;
 +    int i;
 +    static const char *command = "PWD\r\n";
 +    static const int pwd_codes[] = {257, 0};
 +
 +    if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
 +        goto fail;
 +
 +    for (i = 0; res[i]; ++i) {
 +        if (res[i] == '"') {
 +            if (!start) {
 +                start = res + i + 1;
 +                continue;
 +            }
 +            end = res + i;
 +            break;
 +        }
 +    }
 +
 +    if (!end)
 +        goto fail;
 +
 +    *end = '\0';
 +    s->path = av_strdup(start);
 +
 +    av_free(res);
 +
 +    if (!s->path)
 +        return AVERROR(ENOMEM);
 +    return 0;
 +
 +  fail:
 +    av_free(res);
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_file_size(FTPContext *s)
 +{
 +    char command[CONTROL_BUFFER_SIZE];
 +    char *res = NULL;
 +    static const int size_codes[] = {213, 0};
 +
 +    snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
 +    if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
 +        s->filesize = strtoll(&res[4], NULL, 10);
 +    } else {
 +        s->filesize = -1;
 +        av_free(res);
 +        return AVERROR(EIO);
 +    }
 +
 +    av_free(res);
 +    return 0;
 +}
 +
 +static int ftp_retrieve(FTPContext *s)
 +{
 +    char command[CONTROL_BUFFER_SIZE];
 +    static const int retr_codes[] = {150, 125, 0};
 +    int resp_code;
 +
 +    snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
 +    resp_code = ftp_send_command(s, command, retr_codes, NULL);
 +    if (resp_code != 125 && resp_code != 150)
 +        return AVERROR(EIO);
 +
 +    s->state = DOWNLOADING;
 +
 +    return 0;
 +}
 +
 +static int ftp_store(FTPContext *s)
 +{
 +    char command[CONTROL_BUFFER_SIZE];
 +    static const int stor_codes[] = {150, 125, 0};
 +    int resp_code;
 +
 +    snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
 +    resp_code = ftp_send_command(s, command, stor_codes, NULL);
 +    if (resp_code != 125 && resp_code != 150)
 +        return AVERROR(EIO);
 +
 +    s->state = UPLOADING;
 +
 +    return 0;
 +}
 +
 +static int ftp_type(FTPContext *s)
 +{
 +    static const char *command = "TYPE I\r\n";
 +    static const int type_codes[] = {200, 0};
 +
 +    if (ftp_send_command(s, command, type_codes, NULL) != 200)
 +        return AVERROR(EIO);
 +
 +    return 0;
 +}
 +
 +static int ftp_restart(FTPContext *s, int64_t pos)
 +{
 +    char command[CONTROL_BUFFER_SIZE];
 +    static const int rest_codes[] = {350, 0};
 +
 +    snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
 +    if (ftp_send_command(s, command, rest_codes, NULL) != 350)
 +        return AVERROR(EIO);
 +
 +    return 0;
 +}
 +
 +static int ftp_set_dir(FTPContext *s)
 +{
 +    static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */
 +    char command[MAX_URL_SIZE];
 +
 +    snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
 +    if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
 +        return AVERROR(EIO);
 +    return 0;
 +}
 +
 +static int ftp_list_mlsd(FTPContext *s)
 +{
 +    static const char *command = "MLSD\r\n";
 +    static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */
 +
 +    if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
 +        return AVERROR(ENOSYS);
 +    s->listing_method = MLSD;
 +    return 0;
 +}
 +
 +static int ftp_list_nlst(FTPContext *s)
 +{
 +    static const char *command = "NLST\r\n";
 +    static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
 +
 +    if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
 +        return AVERROR(ENOSYS);
 +    s->listing_method = NLST;
 +    return 0;
 +}
 +
 +static int ftp_has_feature(FTPContext *s, const char *feature_name);
 +
 +static int ftp_list(FTPContext *s)
 +{
 +    int ret;
 +    s->state = LISTING_DIR;
 +
 +    if ((ret = ftp_list_mlsd(s)) < 0)
 +        ret = ftp_list_nlst(s);
 +
 +    return ret;
 +}
 +
 +static int ftp_has_feature(FTPContext *s, const char *feature_name)
 +{
 +    if (!s->features)
 +        return 0;
 +
 +    return av_stristr(s->features, feature_name) != NULL;
 +}
 +
 +static int ftp_features(FTPContext *s)
 +{
 +    static const char *feat_command        = "FEAT\r\n";
 +    static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
 +    static const int feat_codes[] = {211, 0};
 +    static const int opts_codes[] = {200, 451, 0};
 +
 +    av_freep(&s->features);
 +    if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
 +        av_freep(&s->features);
 +    }
 +
 +    if (ftp_has_feature(s, "UTF8")) {
 +        if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
 +            s->utf8 = 1;
 +    }
 +
 +    return 0;
 +}
 +
 +static int ftp_connect_control_connection(URLContext *h)
 +{
 +    char buf[CONTROL_BUFFER_SIZE], *response = NULL;
 +    int err;
 +    AVDictionary *opts = NULL;
 +    FTPContext *s = h->priv_data;
 +    static const int connect_codes[] = {220, 0};
 +
 +    if (!s->conn_control) {
 +        ff_url_join(buf, sizeof(buf), "tcp", NULL,
 +                    s->hostname, s->server_control_port, NULL);
 +        if (s->rw_timeout != -1) {
 +            av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
 +        } /* if option is not given, don't pass it and let tcp use its own default */
 +        err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
 +                                   &h->interrupt_callback, &opts,
-                                    h->protocol_whitelist, h->protocol_blacklist);
++                                   h->protocol_whitelist, h->protocol_blacklist, h);
 +        av_dict_free(&opts);
 +        if (err < 0) {
 +            av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
 +            return err;
 +        }
 +
 +        /* check if server is ready */
 +        if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
 +            av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
 +            return AVERROR(EACCES);
 +        }
 +
 +        if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
 +            av_log(h, AV_LOG_WARNING, "Pure-FTPd server is used as an output protocol. It is known issue this implementation may produce incorrect content and it cannot be fixed at this moment.");
 +        }
 +        av_free(response);
 +
 +        if ((err = ftp_auth(s)) < 0) {
 +            av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
 +            return err;
 +        }
 +
 +        if ((err = ftp_type(s)) < 0) {
 +            av_log(h, AV_LOG_ERROR, "Set content type failed\n");
 +            return err;
 +        }
 +
 +        ftp_features(s);
 +    }
 +    return 0;
 +}
 +
 +static int ftp_connect_data_connection(URLContext *h)
 +{
 +    int err;
 +    char buf[CONTROL_BUFFER_SIZE];
 +    AVDictionary *opts = NULL;
 +    FTPContext *s = h->priv_data;
 +
 +    if (!s->conn_data) {
 +        /* Enter passive mode */
 +        if (ftp_passive_mode_epsv(s) < 0) {
 +            /* Use PASV as fallback */
 +            if ((err = ftp_passive_mode(s)) < 0)
 +                return err;
 +        }
 +        /* Open data connection */
 +        ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
 +        if (s->rw_timeout != -1) {
 +            av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
 +        } /* if option is not given, don't pass it and let tcp use its own default */
 +        err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
 +                                   &h->interrupt_callback, &opts,
++                                   h->protocol_whitelist, h->protocol_blacklist, h);
 +        av_dict_free(&opts);
 +        if (err < 0)
 +            return err;
 +
 +        if (s->position)
 +            if ((err = ftp_restart(s, s->position)) < 0)
 +                return err;
 +    }
 +    s->state = READY;
 +    return 0;
 +}
 +
 +static int ftp_abort(URLContext *h)
 +{
 +    static const char *command = "ABOR\r\n";
 +    int err;
 +    static const int abor_codes[] = {225, 226, 0};
 +    FTPContext *s = h->priv_data;
 +
 +    /* According to RCF 959:
 +       "ABOR command tells the server to abort the previous FTP
 +       service command and any associated transfer of data."
 +
 +       There are FTP server implementations that don't response
 +       to any commands during data transfer in passive mode (including ABOR).
 +
 +       This implementation closes data connection by force.
 +    */
 +
 +    if (ftp_send_command(s, command, NULL, NULL) < 0) {
 +        ftp_close_both_connections(s);
 +        if ((err = ftp_connect_control_connection(h)) < 0) {
 +            av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
 +            return err;
 +        }
 +    } else {
 +        ftp_close_data_connection(s);
 +        if (ftp_status(s, NULL, abor_codes) < 225) {
 +            /* wu-ftpd also closes control connection after data connection closing */
 +            ffurl_closep(&s->conn_control);
 +            if ((err = ftp_connect_control_connection(h)) < 0) {
 +                av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
 +                return err;
 +            }
 +        }
 +    }
 +
 +    return 0;
 +}
 +
 +static int ftp_connect(URLContext *h, const char *url)
 +{
 +    char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
 +    const char *tok_user = NULL, *tok_pass = NULL;
 +    char *end = NULL, *newpath = NULL;
 +    int err;
 +    FTPContext *s = h->priv_data;
 +
 +    s->state = DISCONNECTED;
 +    s->listing_method = UNKNOWN_METHOD;
 +    s->filesize = -1;
 +    s->position = 0;
 +    s->features = NULL;
 +
 +    av_url_split(proto, sizeof(proto),
 +                 credencials, sizeof(credencials),
 +                 hostname, sizeof(hostname),
 +                 &s->server_control_port,
 +                 path, sizeof(path),
 +                 url);
 +
 +    tok_user = av_strtok(credencials, ":", &end);
 +    tok_pass = av_strtok(end, ":", &end);
 +    if (!tok_user) {
 +        tok_user = "anonymous";
 +        tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
 +    }
 +    s->user = av_strdup(tok_user);
 +    s->password = av_strdup(tok_pass);
 +    s->hostname = av_strdup(hostname);
 +    if (!s->hostname || !s->user || (tok_pass && !s->password)) {
 +        return AVERROR(ENOMEM);
 +    }
 +
 +    if (s->server_control_port < 0 || s->server_control_port > 65535)
 +        s->server_control_port = 21;
 +
 +    if ((err = ftp_connect_control_connection(h)) < 0)
 +        return err;
 +
 +    if ((err = ftp_current_dir(s)) < 0)
 +        return err;
 +
 +    newpath = av_append_path_component(s->path, path);
 +    if (!newpath)
 +        return AVERROR(ENOMEM);
 +    av_free(s->path);
 +    s->path = newpath;
 +
 +    return 0;
 +}
 +
 +static int ftp_open(URLContext *h, const char *url, int flags)
 +{
 +    FTPContext *s = h->priv_data;
 +    int err;
 +
 +    ff_dlog(h, "ftp protocol open\n");
 +
 +    if ((err = ftp_connect(h, url)) < 0)
 +        goto fail;
 +
 +    if (ftp_restart(s, 0) < 0) {
 +        h->is_streamed = 1;
 +    } else {
 +        if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
 +            h->is_streamed = 1;
 +        if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
 +            h->is_streamed = 1;
 +    }
 +
 +    return 0;
 +
 +  fail:
 +    av_log(h, AV_LOG_ERROR, "FTP open failed\n");
 +    ftp_close(h);
 +    return err;
 +}
 +
 +static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    FTPContext *s = h->priv_data;
 +    int err;
 +    int64_t new_pos, fake_pos;
 +
 +    ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
 +
 +    switch(whence) {
 +    case AVSEEK_SIZE:
 +        return s->filesize;
 +    case SEEK_SET:
 +        new_pos = pos;
 +        break;
 +    case SEEK_CUR:
 +        new_pos = s->position + pos;
 +        break;
 +    case SEEK_END:
 +        if (s->filesize < 0)
 +            return AVERROR(EIO);
 +        new_pos = s->filesize + pos;
 +        break;
 +    default:
 +        return AVERROR(EINVAL);
 +    }
 +
 +    if (h->is_streamed)
 +        return AVERROR(EIO);
 +
 +    if (new_pos < 0) {
 +        av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
 +        return AVERROR(EINVAL);
 +    }
 +
 +    fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
 +    if (fake_pos != s->position) {
 +        if ((err = ftp_abort(h)) < 0)
 +            return err;
 +        s->position = fake_pos;
 +    }
 +    return new_pos;
 +}
 +
 +static int ftp_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    FTPContext *s = h->priv_data;
 +    int read, err, retry_done = 0;
 +
 +    ff_dlog(h, "ftp protocol read %d bytes\n", size);
 +  retry:
 +    if (s->state == DISCONNECTED) {
 +        /* optimization */
 +        if (s->position >= s->filesize)
 +            return 0;
 +        if ((err = ftp_connect_data_connection(h)) < 0)
 +            return err;
 +    }
 +    if (s->state == READY) {
 +        if (s->position >= s->filesize)
 +            return 0;
 +        if ((err = ftp_retrieve(s)) < 0)
 +            return err;
 +    }
 +    if (s->conn_data && s->state == DOWNLOADING) {
 +        read = ffurl_read(s->conn_data, buf, size);
 +        if (read >= 0) {
 +            s->position += read;
 +            if (s->position >= s->filesize) {
 +                /* server will terminate, but keep current position to avoid madness */
 +                /* save position to restart from it */
 +                int64_t pos = s->position;
 +                if (ftp_abort(h) < 0) {
 +                    s->position = pos;
 +                    return AVERROR(EIO);
 +                }
 +                s->position = pos;
 +            }
 +        }
 +        if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
 +            /* Server closed connection. Probably due to inactivity */
 +            int64_t pos = s->position;
 +            av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
 +            if ((err = ftp_abort(h)) < 0)
 +                return err;
 +            if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
 +                av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
 +                return err;
 +            }
 +            if (!retry_done) {
 +                retry_done = 1;
 +                goto retry;
 +            }
 +        }
 +        return read;
 +    }
 +
 +    av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_write(URLContext *h, const unsigned char *buf, int size)
 +{
 +    int err;
 +    FTPContext *s = h->priv_data;
 +    int written;
 +
 +    ff_dlog(h, "ftp protocol write %d bytes\n", size);
 +
 +    if (s->state == DISCONNECTED) {
 +        if ((err = ftp_connect_data_connection(h)) < 0)
 +            return err;
 +    }
 +    if (s->state == READY) {
 +        if ((err = ftp_store(s)) < 0)
 +            return err;
 +    }
 +    if (s->conn_data && s->state == UPLOADING) {
 +        written = ffurl_write(s->conn_data, buf, size);
 +        if (written > 0) {
 +            s->position += written;
 +            s->filesize = FFMAX(s->filesize, s->position);
 +        }
 +        return written;
 +    }
 +
 +    av_log(h, AV_LOG_ERROR, "FTP write failed\n");
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_close(URLContext *h)
 +{
 +    FTPContext *s = h->priv_data;
 +
 +    ff_dlog(h, "ftp protocol close\n");
 +
 +    ftp_close_both_connections(s);
 +    av_freep(&s->user);
 +    av_freep(&s->password);
 +    av_freep(&s->hostname);
 +    av_freep(&s->path);
 +    av_freep(&s->features);
 +
 +    return 0;
 +}
 +
 +static int ftp_get_file_handle(URLContext *h)
 +{
 +    FTPContext *s = h->priv_data;
 +
 +    ff_dlog(h, "ftp protocol get_file_handle\n");
 +
 +    if (s->conn_data)
 +        return ffurl_get_file_handle(s->conn_data);
 +
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_shutdown(URLContext *h, int flags)
 +{
 +    FTPContext *s = h->priv_data;
 +
 +    ff_dlog(h, "ftp protocol shutdown\n");
 +
 +    if (s->conn_data)
 +        return ffurl_shutdown(s->conn_data, flags);
 +
 +    return AVERROR(EIO);
 +}
 +
 +static int ftp_open_dir(URLContext *h)
 +{
 +    FTPContext *s = h->priv_data;
 +    int ret;
 +
 +    if ((ret = ftp_connect(h, h->filename)) < 0)
 +        goto fail;
 +    if ((ret = ftp_set_dir(s)) < 0)
 +        goto fail;
 +    if ((ret = ftp_connect_data_connection(h)) < 0)
 +        goto fail;
 +    if ((ret = ftp_list(s)) < 0)
 +        goto fail;
 +    s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
 +    if (!s->dir_buffer) {
 +        ret = AVERROR(ENOMEM);
 +        goto fail;
 +    }
 +    s->dir_buffer[0] = 0;
 +    if (s->conn_data && s->state == LISTING_DIR)
 +        return 0;
 +  fail:
 +    ffurl_closep(&s->conn_control);
 +    ffurl_closep(&s->conn_data);
 +    return ret;
 +}
 +
 +static int64_t ftp_parse_date(const char *date)
 +{
 +    struct tm tv;
 +    memset(&tv, 0, sizeof(struct tm));
 +    av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
 +    return INT64_C(1000000) * av_timegm(&tv);
 +}
 +
 +static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
 +{
 +    next->name = av_strdup(line);
 +    return 0;
 +}
 +
 +static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
 +{
 +    char *fact, *value;
 +    ff_dlog(NULL, "%s\n", mlsd);
 +    while(fact = av_strtok(mlsd, ";", &mlsd)) {
 +        if (fact[0] == ' ') {
 +            next->name = av_strdup(&fact[1]);
 +            continue;
 +        }
 +        fact = av_strtok(fact, "=", &value);
 +        if (!av_strcasecmp(fact, "type")) {
 +            if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
 +                return 1;
 +            if (!av_strcasecmp(value, "dir"))
 +                next->type = AVIO_ENTRY_DIRECTORY;
 +            else if (!av_strcasecmp(value, "file"))
 +                next->type = AVIO_ENTRY_FILE;
 +            else if (!av_strcasecmp(value, "OS.unix=slink:"))
 +                next->type = AVIO_ENTRY_SYMBOLIC_LINK;
 +        } else if (!av_strcasecmp(fact, "modify")) {
 +            next->modification_timestamp = ftp_parse_date(value);
 +        } else if (!av_strcasecmp(fact, "UNIX.mode")) {
 +            next->filemode = strtoumax(value, NULL, 8);
 +        } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
 +            next->user_id = strtoumax(value, NULL, 10);
 +        else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
 +            next->group_id = strtoumax(value, NULL, 10);
 +        else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
 +            next->size = strtoll(value, NULL, 10);
 +    }
 +    return 0;
 +}
 +
 +/**
 + * @return 0 on success, negative on error, positive on entry to discard.
 + */
 +static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
 +{
 +    FTPContext *s = h->priv_data;
 +
 +    switch (s->listing_method) {
 +    case MLSD:
 +        return ftp_parse_entry_mlsd(line, next);
 +    case NLST:
 +        return ftp_parse_entry_nlst(line, next);
 +    case UNKNOWN_METHOD:
 +    default:
 +        return -1;
 +    }
 +}
 +
 +static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
 +{
 +    FTPContext *s = h->priv_data;
 +    char *start, *found;
 +    int ret, retried;
 +
 +    do {
 +        retried = 0;
 +        start = s->dir_buffer + s->dir_buffer_offset;
 +        while (!(found = strstr(start, "\n"))) {
 +            if (retried)
 +                return AVERROR(EIO);
 +            s->dir_buffer_size -= s->dir_buffer_offset;
 +            s->dir_buffer_offset = 0;
 +            if (s->dir_buffer_size)
 +                memmove(s->dir_buffer, start, s->dir_buffer_size);
 +            ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
 +            if (ret < 0)
 +                return ret;
 +            if (!ret) {
 +                *next = NULL;
 +                return 0;
 +            }
 +            s->dir_buffer_size += ret;
 +            s->dir_buffer[s->dir_buffer_size] = 0;
 +            start = s->dir_buffer;
 +            retried = 1;
 +        }
 +        s->dir_buffer_offset += (found + 1 - start);
 +        found[0] = 0;
 +        if (found > start && found[-1] == '\r')
 +            found[-1] = 0;
 +
 +        *next = ff_alloc_dir_entry();
 +        if (!*next)
 +            return AVERROR(ENOMEM);
 +        (*next)->utf8 = s->utf8;
 +        ret = ftp_parse_entry(h, start, *next);
 +        if (ret) {
 +            avio_free_directory_entry(next);
 +            if (ret < 0)
 +                return ret;
 +        }
 +    } while (ret > 0);
 +    return 0;
 +}
 +
 +static int ftp_close_dir(URLContext *h)
 +{
 +    FTPContext *s = h->priv_data;
 +    av_freep(&s->dir_buffer);
 +    ffurl_closep(&s->conn_control);
 +    ffurl_closep(&s->conn_data);
 +    return 0;
 +}
 +
 +static int ftp_delete(URLContext *h)
 +{
 +    FTPContext *s = h->priv_data;
 +    char command[MAX_URL_SIZE];
 +    static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
 +    static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
 +    int ret;
 +
 +    if ((ret = ftp_connect(h, h->filename)) < 0)
 +        goto cleanup;
 +
 +    snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
 +    if (ftp_send_command(s, command, del_codes, NULL) == 250) {
 +        ret = 0;
 +        goto cleanup;
 +    }
 +
 +    snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
 +    if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
 +        ret = 0;
 +    else
 +        ret = AVERROR(EIO);
 +
 +cleanup:
 +    ftp_close(h);
 +    return ret;
 +}
 +
 +static int ftp_move(URLContext *h_src, URLContext *h_dst)
 +{
 +    FTPContext *s = h_src->priv_data;
 +    char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
 +    static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
 +    static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
 +    int ret;
 +
 +    if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
 +        goto cleanup;
 +
 +    snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
 +    if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
 +        ret = AVERROR(EIO);
 +        goto cleanup;
 +    }
 +
 +    av_url_split(0, 0, 0, 0, 0, 0, 0,
 +                 path, sizeof(path),
 +                 h_dst->filename);
 +    snprintf(command, sizeof(command), "RNTO %s\r\n", path);
 +    if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
 +        ret = 0;
 +    else
 +        ret = AVERROR(EIO);
 +
 +cleanup:
 +    ftp_close(h_src);
 +    return ret;
 +}
 +
 +const URLProtocol ff_ftp_protocol = {
 +    .name                = "ftp",
 +    .url_open            = ftp_open,
 +    .url_read            = ftp_read,
 +    .url_write           = ftp_write,
 +    .url_seek            = ftp_seek,
 +    .url_close           = ftp_close,
 +    .url_get_file_handle = ftp_get_file_handle,
 +    .url_shutdown        = ftp_shutdown,
 +    .priv_data_size      = sizeof(FTPContext),
 +    .priv_data_class     = &ftp_context_class,
 +    .url_open_dir        = ftp_open_dir,
 +    .url_read_dir        = ftp_read_dir,
 +    .url_close_dir       = ftp_close_dir,
 +    .url_delete          = ftp_delete,
 +    .url_move            = ftp_move,
 +    .flags               = URL_PROTOCOL_FLAG_NETWORK,
 +    .default_whitelist   = "tcp",
 +};
@@@ -93,8 -93,8 +93,8 @@@ static int gopher_open(URLContext *h, c
      ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
  
      s->hd = NULL;
 -    err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE,
 -                     &h->interrupt_callback, NULL, h->protocols, h);
 +    err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,
-                                &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist);
++                               &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist, h);
      if (err < 0)
          goto fail;
  
@@@ -305,9 -303,8 +305,9 @@@ retry
      }
      url = s->segments[s->cur_seq_no - s->start_seq_no]->url,
      av_log(h, AV_LOG_DEBUG, "opening %s\n", url);
 -    ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ,
 -                     &h->interrupt_callback, NULL, h->protocols, h);
 +    ret = ffurl_open_whitelist(&s->seg_hd, url, AVIO_FLAG_READ,
 +                               &h->interrupt_callback, NULL,
-                                h->protocol_whitelist, h->protocol_blacklist);
++                               h->protocol_whitelist, h->protocol_blacklist, h);
      if (ret < 0) {
          if (ff_check_interrupt(&h->interrupt_callback))
              return AVERROR_EXIT;
@@@ -219,9 -180,8 +219,9 @@@ static int http_open_cnx_internal(URLCo
      ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
  
      if (!s->hd) {
 -        err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE,
 -                         &h->interrupt_callback, options, h->protocols, h);
 +        err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,
 +                                   &h->interrupt_callback, options,
-                                    h->protocol_whitelist, h->protocol_blacklist);
++                                   h->protocol_whitelist, h->protocol_blacklist, h);
          if (err < 0)
              return err;
      }
@@@ -309,166 -270,6 +309,166 @@@ int ff_http_do_new_request(URLContext *
      return ret;
  }
  
-                                     h->protocol_whitelist, h->protocol_blacklist
 +int ff_http_averror(int status_code, int default_averror)
 +{
 +    switch (status_code) {
 +        case 400: return AVERROR_HTTP_BAD_REQUEST;
 +        case 401: return AVERROR_HTTP_UNAUTHORIZED;
 +        case 403: return AVERROR_HTTP_FORBIDDEN;
 +        case 404: return AVERROR_HTTP_NOT_FOUND;
 +        default: break;
 +    }
 +    if (status_code >= 400 && status_code <= 499)
 +        return AVERROR_HTTP_OTHER_4XX;
 +    else if (status_code >= 500)
 +        return AVERROR_HTTP_SERVER_ERROR;
 +    else
 +        return default_averror;
 +}
 +
 +static int http_write_reply(URLContext* h, int status_code)
 +{
 +    int ret, body = 0, reply_code, message_len;
 +    const char *reply_text, *content_type;
 +    HTTPContext *s = h->priv_data;
 +    char message[BUFFER_SIZE];
 +    content_type = "text/plain";
 +
 +    if (status_code < 0)
 +        body = 1;
 +    switch (status_code) {
 +    case AVERROR_HTTP_BAD_REQUEST:
 +    case 400:
 +        reply_code = 400;
 +        reply_text = "Bad Request";
 +        break;
 +    case AVERROR_HTTP_FORBIDDEN:
 +    case 403:
 +        reply_code = 403;
 +        reply_text = "Forbidden";
 +        break;
 +    case AVERROR_HTTP_NOT_FOUND:
 +    case 404:
 +        reply_code = 404;
 +        reply_text = "Not Found";
 +        break;
 +    case 200:
 +        reply_code = 200;
 +        reply_text = "OK";
 +        content_type = "application/octet-stream";
 +        break;
 +    case AVERROR_HTTP_SERVER_ERROR:
 +    case 500:
 +        reply_code = 500;
 +        reply_text = "Internal server error";
 +        break;
 +    default:
 +        return AVERROR(EINVAL);
 +    }
 +    if (body) {
 +        s->chunked_post = 0;
 +        message_len = snprintf(message, sizeof(message),
 +                 "HTTP/1.1 %03d %s\r\n"
 +                 "Content-Type: %s\r\n"
 +                 "Content-Length: %"SIZE_SPECIFIER"\r\n"
 +                 "\r\n"
 +                 "%03d %s\r\n",
 +                 reply_code,
 +                 reply_text,
 +                 content_type,
 +                 strlen(reply_text) + 6, // 3 digit status code + space + \r\n
 +                 reply_code,
 +                 reply_text);
 +    } else {
 +        s->chunked_post = 1;
 +        message_len = snprintf(message, sizeof(message),
 +                 "HTTP/1.1 %03d %s\r\n"
 +                 "Content-Type: %s\r\n"
 +                 "Transfer-Encoding: chunked\r\n"
 +                 "\r\n",
 +                 reply_code,
 +                 reply_text,
 +                 content_type);
 +    }
 +    av_log(h, AV_LOG_TRACE, "HTTP reply header: \n%s----\n", message);
 +    if ((ret = ffurl_write(s->hd, message, message_len)) < 0)
 +        return ret;
 +    return 0;
 +}
 +
 +static void handle_http_errors(URLContext *h, int error)
 +{
 +    av_assert0(error < 0);
 +    http_write_reply(h, error);
 +}
 +
 +static int http_handshake(URLContext *c)
 +{
 +    int ret, err, new_location;
 +    HTTPContext *ch = c->priv_data;
 +    URLContext *cl = ch->hd;
 +    switch (ch->handshake_step) {
 +    case LOWER_PROTO:
 +        av_log(c, AV_LOG_TRACE, "Lower protocol\n");
 +        if ((ret = ffurl_handshake(cl)) > 0)
 +            return 2 + ret;
 +        if (ret < 0)
 +            return ret;
 +        ch->handshake_step = READ_HEADERS;
 +        ch->is_connected_server = 1;
 +        return 2;
 +    case READ_HEADERS:
 +        av_log(c, AV_LOG_TRACE, "Read headers\n");
 +        if ((err = http_read_header(c, &new_location)) < 0) {
 +            handle_http_errors(c, err);
 +            return err;
 +        }
 +        ch->handshake_step = WRITE_REPLY_HEADERS;
 +        return 1;
 +    case WRITE_REPLY_HEADERS:
 +        av_log(c, AV_LOG_TRACE, "Reply code: %d\n", ch->reply_code);
 +        if ((err = http_write_reply(c, ch->reply_code)) < 0)
 +            return err;
 +        ch->handshake_step = FINISH;
 +        return 1;
 +    case FINISH:
 +        return 0;
 +    }
 +    // this should never be reached.
 +    return AVERROR(EINVAL);
 +}
 +
 +static int http_listen(URLContext *h, const char *uri, int flags,
 +                       AVDictionary **options) {
 +    HTTPContext *s = h->priv_data;
 +    int ret;
 +    char hostname[1024], proto[10];
 +    char lower_url[100];
 +    const char *lower_proto = "tcp";
 +    int port;
 +    av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
 +                 NULL, 0, uri);
 +    if (!strcmp(proto, "https"))
 +        lower_proto = "tls";
 +    ff_url_join(lower_url, sizeof(lower_url), lower_proto, NULL, hostname, port,
 +                NULL);
 +    if ((ret = av_dict_set_int(options, "listen", s->listen, 0)) < 0)
 +        goto fail;
 +    if ((ret = ffurl_open_whitelist(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
 +                                    &h->interrupt_callback, options,
++                                    h->protocol_whitelist, h->protocol_blacklist, h
 +                                   )) < 0)
 +        goto fail;
 +    s->handshake_step = LOWER_PROTO;
 +    if (s->listen == HTTP_SINGLE) { /* single client */
 +        s->reply_code = 200;
 +        while ((ret = http_handshake(h)) > 0);
 +    }
 +fail:
 +    av_dict_free(&s->chained_options);
 +    return ret;
 +}
 +
  static int http_open(URLContext *h, const char *uri, int flags,
                       AVDictionary **options)
  {
@@@ -1580,9 -1078,8 +1580,9 @@@ static int http_proxy_open(URLContext *
      ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port,
                  NULL);
  redo:
 -    ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
 -                     &h->interrupt_callback, NULL, h->protocols, h);
 +    ret = ffurl_open_whitelist(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
 +                               &h->interrupt_callback, NULL,
-                                h->protocol_whitelist, h->protocol_blacklist);
++                               h->protocol_whitelist, h->protocol_blacklist, h);
      if (ret < 0)
          return ret;
  
@@@ -164,10 -176,11 +164,10 @@@ static int icecast_open(URLContext *h, 
      // Build new URI for passing to http protocol
      ff_url_join(h_url, sizeof(h_url), "http", auth, host, port, "%s", path);
      // Finally open http proto handler
 -    ret = ffurl_open(&s->hd, h_url, AVIO_FLAG_READ_WRITE, NULL, &opt_dict,
 -                     h->protocols, h);
 +    ret = ffurl_open_whitelist(&s->hd, h_url, AVIO_FLAG_READ_WRITE, NULL,
-                                &opt_dict, h->protocol_whitelist, h->protocol_blacklist);
++                               &opt_dict, h->protocol_whitelist, h->protocol_blacklist, h);
  
  cleanup:
 -    // Free variables
      av_freep(&user);
      av_freep(&headers);
      av_dict_free(&opt_dict);
@@@ -69,9 -69,9 +69,9 @@@ static int md5_close(URLContext *h
      av_strstart(filename, "md5:", &filename);
  
      if (*filename) {
 -        err = ffurl_open(&out, filename, AVIO_FLAG_WRITE,
 -                         &h->interrupt_callback, NULL,
 -                         h->protocols, h);
 +        err = ffurl_open_whitelist(&out, filename, AVIO_FLAG_WRITE,
 +                                   &h->interrupt_callback, NULL,
-                                    h->protocol_whitelist, h->protocol_blacklist);
++                                   h->protocol_whitelist, h->protocol_blacklist, h);
          if (err)
              return err;
          err = ffurl_write(out, buf, i*2+1);
@@@ -528,9 -519,8 +528,9 @@@ static int mms_open(URLContext *h, cons
  
      // establish tcp connection.
      ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, mmst->host, port, NULL);
 -    err = ffurl_open(&mms->mms_hd, tcpname, AVIO_FLAG_READ_WRITE,
 -                     &h->interrupt_callback, NULL, h->protocols, h);
 +    err = ffurl_open_whitelist(&mms->mms_hd, tcpname, AVIO_FLAG_READ_WRITE,
 +                               &h->interrupt_callback, NULL,
-                                h->protocol_whitelist, h->protocol_blacklist);
++                               h->protocol_whitelist, h->protocol_blacklist, h);
      if (err)
          goto fail;
  
@@@ -264,9 -264,8 +264,9 @@@ static int rtmpe_open(URLContext *h, co
      }
  
      /* open the tcp or ffrtmphttp connection */
 -    if ((ret = ffurl_open(&rt->stream, url, AVIO_FLAG_READ_WRITE,
 -                          &h->interrupt_callback, NULL, h->protocols, h)) < 0) {
 +    if ((ret = ffurl_open_whitelist(&rt->stream, url, AVIO_FLAG_READ_WRITE,
 +                                    &h->interrupt_callback, NULL,
-                                     h->protocol_whitelist, h->protocol_blacklist)) < 0) {
++                                    h->protocol_whitelist, h->protocol_blacklist, h)) < 0) {
          rtmpe_close(h);
          return ret;
      }
@@@ -1118,9 -1118,8 +1118,9 @@@ static int rtmp_calc_swfhash(URLContex
      int ret = 0;
  
      /* Get the SWF player file. */
 -    if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ,
 -                          &s->interrupt_callback, NULL, s->protocols, s)) < 0) {
 +    if ((ret = ffurl_open_whitelist(&stream, rt->swfverify, AVIO_FLAG_READ,
 +                                    &s->interrupt_callback, NULL,
-                                     s->protocol_whitelist, s->protocol_blacklist)) < 0) {
++                                    s->protocol_whitelist, s->protocol_blacklist, s)) < 0) {
          av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify);
          goto fail;
      }
@@@ -2648,9 -2640,8 +2648,9 @@@ static int rtmp_open(URLContext *s, con
      }
  
  reconnect:
 -    if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
 -                          &s->interrupt_callback, &opts, s->protocols, s)) < 0) {
 +    if ((ret = ffurl_open_whitelist(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
 +                                    &s->interrupt_callback, &opts,
-                                     s->protocol_whitelist, s->protocol_blacklist)) < 0) {
++                                    s->protocol_whitelist, s->protocol_blacklist, s)) < 0) {
          av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
          goto fail;
      }
@@@ -377,40 -366,19 +377,40 @@@ static int rtp_open(URLContext *h, cons
          }
      }
  
 -    build_udp_url(s, buf, sizeof(buf),
 -                  hostname, rtp_port, s->local_rtpport, sources, block);
 -    if (ffurl_open(&s->rtp_hd, buf, flags, &h->interrupt_callback, NULL,
 -                   h->protocols, h) < 0)
 -        goto fail;
 -    if (s->local_rtpport >= 0 && s->local_rtcpport < 0)
 -        s->local_rtcpport = ff_udp_get_local_port(s->rtp_hd) + 1;
 -
 -    build_udp_url(s, buf, sizeof(buf),
 -                  hostname, s->rtcp_port, s->local_rtcpport, sources, block);
 -    if (ffurl_open(&s->rtcp_hd, buf, flags, &h->interrupt_callback, NULL,
 -                   h->protocols, h) < 0)
 -        goto fail;
 +    for (i = 0; i < max_retry_count; i++) {
 +        build_udp_url(s, buf, sizeof(buf),
 +                      hostname, rtp_port, s->local_rtpport,
 +                      sources, block);
 +        if (ffurl_open_whitelist(&s->rtp_hd, buf, flags, &h->interrupt_callback,
-                                  NULL, h->protocol_whitelist, h->protocol_blacklist) < 0)
++                                 NULL, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
 +            goto fail;
 +        s->local_rtpport = ff_udp_get_local_port(s->rtp_hd);
 +        if(s->local_rtpport == 65535) {
 +            s->local_rtpport = -1;
 +            continue;
 +        }
 +        rtcpflags = flags | AVIO_FLAG_WRITE;
 +        if (s->local_rtcpport < 0) {
 +            s->local_rtcpport = s->local_rtpport + 1;
 +            build_udp_url(s, buf, sizeof(buf),
 +                          hostname, s->rtcp_port, s->local_rtcpport,
 +                          sources, block);
 +            if (ffurl_open_whitelist(&s->rtcp_hd, buf, rtcpflags,
 +                                     &h->interrupt_callback, NULL,
-                                      h->protocol_whitelist, h->protocol_blacklist) < 0) {
++                                     h->protocol_whitelist, h->protocol_blacklist, h) < 0) {
 +                s->local_rtpport = s->local_rtcpport = -1;
 +                continue;
 +            }
 +            break;
 +        }
 +        build_udp_url(s, buf, sizeof(buf),
 +                      hostname, s->rtcp_port, s->local_rtcpport,
 +                      sources, block);
 +        if (ffurl_open_whitelist(&s->rtcp_hd, buf, rtcpflags, &h->interrupt_callback,
-                                  NULL, h->protocol_whitelist, h->protocol_blacklist) < 0)
++                                 NULL, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
 +            goto fail;
 +        break;
 +    }
  
      /* just to ease handle access. XXX: need to suppress direct handle
         access */
@@@ -1468,8 -1465,8 +1468,8 @@@ int ff_rtsp_make_setup_request(AVFormat
                              "?localport=%d", j);
                  /* we will use two ports per rtp stream (rtp and rtcp) */
                  j += 2;
 -                err = ffurl_open(&rtsp_st->rtp_handle, buf, AVIO_FLAG_READ_WRITE,
 -                                 &s->interrupt_callback, &opts, rt->protocols, NULL);
 +                err = ffurl_open_whitelist(&rtsp_st->rtp_handle, buf, AVIO_FLAG_READ_WRITE,
-                                  &s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist);
++                                 &s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
  
                  av_dict_free(&opts);
  
                          namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST);
              ff_url_join(url, sizeof(url), "rtp", NULL, namebuf,
                          port, "%s", optbuf);
 -            if (ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
 -                           &s->interrupt_callback, NULL, rt->protocols, NULL) < 0) {
 +            if (ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
-                            &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist) < 0) {
++                           &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL) < 0) {
                  err = AVERROR_INVALIDDATA;
                  goto fail;
              }
@@@ -1795,14 -1800,12 +1795,14 @@@ redirect
              goto fail;
          }
      } else {
 +        int ret;
          /* open the tcp connection */
          ff_url_join(tcpname, sizeof(tcpname), lower_rtsp_proto, NULL,
 -                    host, port, NULL);
 -        if (ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
 -                       &s->interrupt_callback, NULL, rt->protocols, NULL) < 0) {
 -            err = AVERROR(EIO);
 +                    host, port,
 +                    "?timeout=%d", rt->stimeout);
 +        if ((ret = ffurl_open_whitelist(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
-                        &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist)) < 0) {
++                       &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL)) < 0) {
 +            err = ret;
              goto fail;
          }
          rt->rtsp_hd_out = rt->rtsp_hd;
@@@ -2316,8 -2310,8 +2316,8 @@@ static int sdp_read_header(AVFormatCont
              append_source_addrs(url, sizeof(url), "block",
                                  rtsp_st->nb_exclude_source_addrs,
                                  rtsp_st->exclude_source_addrs);
 -            err = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
 -                             &s->interrupt_callback, &opts, rt->protocols, NULL);
 +            err = ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ,
-                            &s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist);
++                           &s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
  
              av_dict_free(&opts);
  
@@@ -2386,8 -2380,15 +2386,8 @@@ static int rtp_read_header(AVFormatCont
      if (!ff_network_init())
          return AVERROR(EIO);
  
 -    if (!rt->protocols) {
 -        rt->protocols = ffurl_get_protocols(s->protocol_whitelist,
 -                                            s->protocol_blacklist);
 -        if (!rt->protocols)
 -            return AVERROR(ENOMEM);
 -    }
 -
 -    ret = ffurl_open(&in, s->filename, AVIO_FLAG_READ,
 -                     &s->interrupt_callback, NULL, rt->protocols, NULL);
 +    ret = ffurl_open_whitelist(&in, s->filename, AVIO_FLAG_READ,
-                      &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist);
++                     &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL);
      if (ret)
          goto fail;
  
@@@ -294,9 -294,8 +294,9 @@@ static int rtsp_read_setup(AVFormatCont
              av_dict_set(&opts, "buffer_size", buf, 0);
              ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
              av_log(s, AV_LOG_TRACE, "Opening: %s", url);
 -            ret = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
 -                             &s->interrupt_callback, &opts, rt->protocols, NULL);
 +            ret = ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
 +                                       &s->interrupt_callback, &opts,
-                                        s->protocol_whitelist, s->protocol_blacklist);
++                                       s->protocol_whitelist, s->protocol_blacklist, NULL);
              av_dict_free(&opts);
              if (ret)
                  localport += 2;
@@@ -663,9 -666,8 +663,9 @@@ static int rtsp_listen(AVFormatContext 
      ff_url_join(tcpname, sizeof(tcpname), lower_proto, NULL, host, port,
                  "?listen&listen_timeout=%d", rt->initial_timeout * 1000);
  
 -    if (ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
 -                         &s->interrupt_callback, NULL, rt->protocols, NULL)) {
 +    if (ret = ffurl_open_whitelist(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
 +                                   &s->interrupt_callback, NULL,
-                                    s->protocol_whitelist, s->protocol_blacklist)) {
++                                   s->protocol_whitelist, s->protocol_blacklist, NULL)) {
          av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n");
          return ret;
      }
@@@ -83,11 -85,17 +83,11 @@@ static int sap_read_header(AVFormatCont
          av_strlcpy(host, "224.2.127.254", sizeof(host));
      }
  
 -    sap->protocols = ffurl_get_protocols(s->protocol_whitelist,
 -                                         s->protocol_blacklist);
 -    if (!sap->protocols) {
 -        ret = AVERROR(ENOMEM);
 -        goto fail;
 -    }
 -
      ff_url_join(url, sizeof(url), "udp", NULL, host, port, "?localport=%d",
                  port);
 -    ret = ffurl_open(&sap->ann_fd, url, AVIO_FLAG_READ,
 -                     &s->interrupt_callback, NULL, sap->protocols, NULL);
 +    ret = ffurl_open_whitelist(&sap->ann_fd, url, AVIO_FLAG_READ,
 +                               &s->interrupt_callback, NULL,
-                                s->protocol_whitelist, s->protocol_blacklist);
++                               s->protocol_whitelist, s->protocol_blacklist, NULL);
      if (ret)
          goto fail;
  
@@@ -149,9 -159,8 +149,9 @@@ static int sap_write_header(AVFormatCon
                      "?ttl=%d", ttl);
          if (!same_port)
              base_port += 2;
 -        ret = ffurl_open(&fd, url, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL,
 -                         sap->protocols, NULL);
 +        ret = ffurl_open_whitelist(&fd, url, AVIO_FLAG_WRITE,
 +                                   &s->interrupt_callback, NULL,
-                                    s->protocol_whitelist, s->protocol_blacklist);
++                                   s->protocol_whitelist, s->protocol_blacklist, NULL);
          if (ret) {
              ret = AVERROR(EIO);
              goto fail;
  
      ff_url_join(url, sizeof(url), "udp", NULL, announce_addr, port,
                  "?ttl=%d&connect=1", ttl);
 -    ret = ffurl_open(&sap->ann_fd, url, AVIO_FLAG_WRITE,
 -                     &s->interrupt_callback, NULL, sap->protocols, NULL);
 +    ret = ffurl_open_whitelist(&sap->ann_fd, url, AVIO_FLAG_WRITE,
 +                               &s->interrupt_callback, NULL,
-                                s->protocol_whitelist, s->protocol_blacklist);
++                               s->protocol_whitelist, s->protocol_blacklist, NULL);
      if (ret) {
          ret = AVERROR(EIO);
          goto fail;
@@@ -122,8 -125,8 +122,8 @@@ static int64_t ism_seek(void *opaque, i
              AVDictionary *opts = NULL;
              os->tail_out = os->out;
              av_dict_set(&opts, "truncate", "0", 0);
 -            ret = ffurl_open(&os->out, frag->file, AVIO_FLAG_WRITE, &os->ctx->interrupt_callback, &opts,
 -                             os->protocols, NULL);
 +            ret = ffurl_open_whitelist(&os->out, frag->file, AVIO_FLAG_WRITE,
-                                        &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist);
++                                       &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist, NULL);
              av_dict_free(&opts);
              if (ret < 0) {
                  os->out = os->tail_out;
                  return ret;
              }
              av_dict_set(&opts, "truncate", "0", 0);
 -            ffurl_open(&os->out2, frag->infofile, AVIO_FLAG_WRITE, &os->ctx->interrupt_callback, &opts,
 -                       os->protocols, NULL);
 +            ffurl_open_whitelist(&os->out2, frag->infofile, AVIO_FLAG_WRITE,
-                                  &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist);
++                                 &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist, NULL);
              av_dict_free(&opts);
              ffurl_seek(os->out, offset - frag->start_pos, SEEK_SET);
              if (os->out2)
@@@ -526,7 -540,8 +526,7 @@@ static int ism_flush(AVFormatContext *s
              continue;
  
          snprintf(filename, sizeof(filename), "%s/temp", os->dirname);
-         ret = ffurl_open_whitelist(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist);
 -        ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL,
 -                         c->protocols, NULL);
++        ret = ffurl_open_whitelist(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL);
          if (ret < 0)
              break;
          os->cur_start_pos = os->tail_pos;
@@@ -80,8 -80,8 +80,8 @@@ static int srtp_open(URLContext *h, con
      av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &rtp_port,
                   path, sizeof(path), uri);
      ff_url_join(buf, sizeof(buf), "rtp", NULL, hostname, rtp_port, "%s", path);
 -    if ((ret = ffurl_open(&s->rtp_hd, buf, flags, &h->interrupt_callback, NULL,
 -                          h->protocols, h)) < 0)
 +    if ((ret = ffurl_open_whitelist(&s->rtp_hd, buf, flags, &h->interrupt_callback,
-                                     NULL, h->protocol_whitelist, h->protocol_blacklist)) < 0)
++                                    NULL, h->protocol_whitelist, h->protocol_blacklist, h)) < 0)
          goto fail;
  
      h->max_packet_size = FFMIN(s->rtp_hd->max_packet_size,
index fdd328a,0000000..fa971e1
mode 100644,000000..100644
--- /dev/null
@@@ -1,149 -1,0 +1,149 @@@
-                                options, h->protocol_whitelist, h->protocol_blacklist);
 +/*
 + * Copyright (c) 2014 Nicolas George
 + *
 + * This file is part of FFmpeg.
 + *
 + * FFmpeg is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either
 + * version 2.1 of the License, or (at your option) any later version.
 + *
 + * FFmpeg is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public License
 + * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + */
 +
 +#include "libavutil/avassert.h"
 +#include "libavutil/avstring.h"
 +#include "libavutil/opt.h"
 +#include "avformat.h"
 +#include "url.h"
 +
 +typedef struct SubfileContext {
 +    const AVClass *class;
 +    URLContext *h;
 +    int64_t start;
 +    int64_t end;
 +    int64_t pos;
 +} SubfileContext;
 +
 +#define OFFSET(field) offsetof(SubfileContext, field)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +
 +static const AVOption subfile_options[] = {
 +    { "start", "start offset", OFFSET(start), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
 +    { "end",   "end offset",   OFFSET(end),   AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
 +    { NULL }
 +};
 +
 +#undef OFFSET
 +#undef D
 +
 +static const AVClass subfile_class = {
 +    .class_name = "subfile",
 +    .item_name  = av_default_item_name,
 +    .option     = subfile_options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
 +static int slave_seek(URLContext *h)
 +{
 +    SubfileContext *c = h->priv_data;
 +    int64_t ret;
 +
 +    if ((ret = ffurl_seek(c->h, c->pos, SEEK_SET)) != c->pos) {
 +        if (ret >= 0)
 +            ret = AVERROR_BUG;
 +        av_log(h, AV_LOG_ERROR, "Impossible to seek in file: %s\n",
 +               av_err2str(ret));
 +        return ret;
 +    }
 +    return 0;
 +}
 +
 +static int subfile_open(URLContext *h, const char *filename, int flags,
 +                        AVDictionary **options)
 +{
 +    SubfileContext *c = h->priv_data;
 +    int ret;
 +
 +    if (c->end <= c->start) {
 +        av_log(h, AV_LOG_ERROR, "end before start\n");
 +        return AVERROR(EINVAL);
 +    }
 +    av_strstart(filename, "subfile:", &filename);
 +    ret = ffurl_open_whitelist(&c->h, filename, flags, &h->interrupt_callback,
++                               options, h->protocol_whitelist, h->protocol_blacklist, h);
 +    if (ret < 0)
 +        return ret;
 +    c->pos = c->start;
 +    if ((ret = slave_seek(h)) < 0) {
 +        ffurl_close(c->h);
 +        return ret;
 +    }
 +    return 0;
 +}
 +
 +static int subfile_close(URLContext *h)
 +{
 +    SubfileContext *c = h->priv_data;
 +    return ffurl_close(c->h);
 +}
 +
 +static int subfile_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    SubfileContext *c = h->priv_data;
 +    int64_t rest = c->end - c->pos;
 +    int ret;
 +
 +    if (rest <= 0)
 +        return 0;
 +    size = FFMIN(size, rest);
 +    ret = ffurl_read(c->h, buf, size);
 +    if (ret >= 0)
 +        c->pos += ret;
 +    return ret;
 +}
 +
 +static int64_t subfile_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    SubfileContext *c = h->priv_data;
 +    int64_t new_pos = -1;
 +    int ret;
 +
 +    if (whence == AVSEEK_SIZE)
 +        return c->end - c->start;
 +    switch (whence) {
 +    case SEEK_SET:
 +        new_pos = c->start + pos;
 +        break;
 +    case SEEK_CUR:
 +        new_pos += pos;
 +        break;
 +    case SEEK_END:
 +        new_pos = c->end + c->pos;
 +        break;
 +    }
 +    if (new_pos < c->start)
 +        return AVERROR(EINVAL);
 +    c->pos = new_pos;
 +    if ((ret = slave_seek(h)) < 0)
 +        return ret;
 +    return c->pos - c->start;
 +}
 +
 +const URLProtocol ff_subfile_protocol = {
 +    .name                = "subfile",
 +    .url_open2           = subfile_open,
 +    .url_read            = subfile_read,
 +    .url_seek            = subfile_seek,
 +    .url_close           = subfile_close,
 +    .priv_data_size      = sizeof(SubfileContext),
 +    .priv_data_class     = &subfile_class,
 +    .default_whitelist   = "file",
 +};
@@@ -104,7 -75,6 +104,7 @@@ int ff_tls_open_underlying(TLSShared *c
                      proxy_port, "/%s", dest);
      }
  
 -    return ffurl_open(&c->tcp, buf, AVIO_FLAG_READ_WRITE,
 -                      &parent->interrupt_callback, options, parent->protocols, parent);
 +    return ffurl_open_whitelist(&c->tcp, buf, AVIO_FLAG_READ_WRITE,
 +                                &parent->interrupt_callback, options,
-                                 parent->protocol_whitelist, parent->protocol_blacklist);
++                                parent->protocol_whitelist, parent->protocol_blacklist, parent);
  }
@@@ -136,38 -134,17 +136,41 @@@ int ffurl_connect(URLContext *uc, AVDic
   * @param options  A dictionary filled with protocol-private options. On return
   * this parameter will be destroyed and replaced with a dict containing options
   * that were not found. May be NULL.
 - * @param protocols a NULL-terminate list of protocols available for use by
 - *                  this context and its children. The caller must ensure this
 - *                  list remains valid until the context is closed.
+  * @param parent An enclosing URLContext, whose generic options should
+  *               be applied to this URLContext as well.
 - * @return 0 in case of success, a negative value corresponding to an
 + * @return >= 0 in case of success, a negative value corresponding to an
   * AVERROR code in case of failure
   */
 -int ffurl_open(URLContext **puc, const char *filename, int flags,
 +int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
                 const AVIOInterruptCB *int_cb, AVDictionary **options,
-                const char *whitelist, const char* blacklist);
 -               const URLProtocol **protocols, URLContext *parent);
++               const char *whitelist, const char* blacklist,
++               URLContext *parent);
 +
 +int ffurl_open(URLContext **puc, const char *filename, int flags,
 +               const AVIOInterruptCB *int_cb, AVDictionary **options);
 +
 +/**
 + * Accept an URLContext c on an URLContext s
 + *
 + * @param  s server context
 + * @param  c client context, must be unallocated.
 + * @return >= 0 on success, ff_neterrno() on failure.
 + */
 +int ffurl_accept(URLContext *s, URLContext **c);
 +
 +/**
 + * Perform one step of the protocol handshake to accept a new client.
 + * See avio_handshake() for details.
 + * Implementations should try to return decreasing values.
 + * If the protocol uses an underlying protocol, the underlying handshake is
 + * usually the first step, and the return value can be:
 + * (largest value for this protocol) + (return value from other protocol)
 + *
 + * @param  c the client context
 + * @return >= 0 on success or a negative value corresponding
 + *         to an AVERROR code on failure
 + */
 +int ffurl_handshake(URLContext *c);
  
  /**
   * Read up to size bytes from the resource accessed by h, and store