Merge commit '2758cdedfb7ac61f8b5e4861f99218b6fd43491d'
authorDerek Buitenhuis <derek.buitenhuis@gmail.com>
Mon, 29 Feb 2016 16:50:39 +0000 (16:50 +0000)
committerDerek Buitenhuis <derek.buitenhuis@gmail.com>
Mon, 29 Feb 2016 16:51:10 +0000 (16:51 +0000)
This commit also disables the async fate test, because it
used internal APIs in a non-kosher way, which no longer
exists.

* commit '2758cdedfb7ac61f8b5e4861f99218b6fd43491d':
  lavf: reorganize URLProtocols

Merged-by: Derek Buitenhuis <derek.buitenhuis@gmail.com>
39 files changed:
1  2 
configure
libavformat/Makefile
libavformat/allformats.c
libavformat/async.c
libavformat/avio.c
libavformat/bluray.c
libavformat/cache.c
libavformat/concat.c
libavformat/crypto.c
libavformat/data_uri.c
libavformat/file.c
libavformat/ftp.c
libavformat/gopher.c
libavformat/hlsproto.c
libavformat/http.c
libavformat/icecast.c
libavformat/librtmp.c
libavformat/libsmbclient.c
libavformat/libssh.c
libavformat/md5proto.c
libavformat/mmsh.c
libavformat/mmst.c
libavformat/protocols.c
libavformat/rtmpcrypt.c
libavformat/rtmphttp.c
libavformat/rtmpproto.c
libavformat/rtpproto.c
libavformat/sctp.c
libavformat/srtpproto.c
libavformat/subfile.c
libavformat/tcp.c
libavformat/tls_gnutls.c
libavformat/tls_openssl.c
libavformat/tls_schannel.c
libavformat/tls_securetransport.c
libavformat/udp.c
libavformat/unix.c
libavformat/url.h
tests/fate/libavformat.mak

diff --cc configure
Simple merge
@@@ -18,7 -17,7 +18,8 @@@ OBJS = allformats.o         
         mux.o                \
         options.o            \
         os_support.o         \
 +       qtpalette.o          \
+        protocols.o          \
         riff.o               \
         sdp.o                \
         url.o                \
@@@ -349,75 -257,4 +342,27 @@@ void av_register_all(void
      REGISTER_DEMUXER (XWMA,             xwma);
      REGISTER_DEMUXER (YOP,              yop);
      REGISTER_MUXDEMUX(YUV4MPEGPIPE,     yuv4mpegpipe);
-     /* protocols */
-     REGISTER_PROTOCOL(ASYNC,            async);
-     REGISTER_PROTOCOL(BLURAY,           bluray);
-     REGISTER_PROTOCOL(CACHE,            cache);
-     REGISTER_PROTOCOL(CONCAT,           concat);
-     REGISTER_PROTOCOL(CRYPTO,           crypto);
-     REGISTER_PROTOCOL(DATA,             data);
-     REGISTER_PROTOCOL(FFRTMPCRYPT,      ffrtmpcrypt);
-     REGISTER_PROTOCOL(FFRTMPHTTP,       ffrtmphttp);
-     REGISTER_PROTOCOL(FILE,             file);
-     REGISTER_PROTOCOL(FTP,              ftp);
-     REGISTER_PROTOCOL(GOPHER,           gopher);
-     REGISTER_PROTOCOL(HLS,              hls);
-     REGISTER_PROTOCOL(HTTP,             http);
-     REGISTER_PROTOCOL(HTTPPROXY,        httpproxy);
-     REGISTER_PROTOCOL(HTTPS,            https);
-     REGISTER_PROTOCOL(ICECAST,          icecast);
-     REGISTER_PROTOCOL(MMSH,             mmsh);
-     REGISTER_PROTOCOL(MMST,             mmst);
-     REGISTER_PROTOCOL(MD5,              md5);
-     REGISTER_PROTOCOL(PIPE,             pipe);
-     REGISTER_PROTOCOL(RTMP,             rtmp);
-     REGISTER_PROTOCOL(RTMPE,            rtmpe);
-     REGISTER_PROTOCOL(RTMPS,            rtmps);
-     REGISTER_PROTOCOL(RTMPT,            rtmpt);
-     REGISTER_PROTOCOL(RTMPTE,           rtmpte);
-     REGISTER_PROTOCOL(RTMPTS,           rtmpts);
-     REGISTER_PROTOCOL(RTP,              rtp);
-     REGISTER_PROTOCOL(SCTP,             sctp);
-     REGISTER_PROTOCOL(SRTP,             srtp);
-     REGISTER_PROTOCOL(SUBFILE,          subfile);
-     REGISTER_PROTOCOL(TCP,              tcp);
-     REGISTER_PROTOCOL(TLS_SCHANNEL,     tls_schannel);
-     REGISTER_PROTOCOL(TLS_SECURETRANSPORT, tls_securetransport);
-     REGISTER_PROTOCOL(TLS_GNUTLS,       tls_gnutls);
-     REGISTER_PROTOCOL(TLS_OPENSSL,      tls_openssl);
-     REGISTER_PROTOCOL(UDP,              udp);
-     REGISTER_PROTOCOL(UDPLITE,          udplite);
-     REGISTER_PROTOCOL(UNIX,             unix);
 +
 +    /* image demuxers */
 +    REGISTER_DEMUXER (IMAGE_BMP_PIPE,        image_bmp_pipe);
 +    REGISTER_DEMUXER (IMAGE_DDS_PIPE,        image_dds_pipe);
 +    REGISTER_DEMUXER (IMAGE_DPX_PIPE,        image_dpx_pipe);
 +    REGISTER_DEMUXER (IMAGE_EXR_PIPE,        image_exr_pipe);
 +    REGISTER_DEMUXER (IMAGE_J2K_PIPE,        image_j2k_pipe);
 +    REGISTER_DEMUXER (IMAGE_JPEG_PIPE,       image_jpeg_pipe);
 +    REGISTER_DEMUXER (IMAGE_JPEGLS_PIPE,     image_jpegls_pipe);
 +    REGISTER_DEMUXER (IMAGE_PCX_PIPE,        image_pcx_pipe);
 +    REGISTER_DEMUXER (IMAGE_PICTOR_PIPE,     image_pictor_pipe);
 +    REGISTER_DEMUXER (IMAGE_PNG_PIPE,        image_png_pipe);
 +    REGISTER_DEMUXER (IMAGE_QDRAW_PIPE,      image_qdraw_pipe);
 +    REGISTER_DEMUXER (IMAGE_SGI_PIPE,        image_sgi_pipe);
 +    REGISTER_DEMUXER (IMAGE_SUNRAST_PIPE,    image_sunrast_pipe);
 +    REGISTER_DEMUXER (IMAGE_TIFF_PIPE,       image_tiff_pipe);
 +    REGISTER_DEMUXER (IMAGE_WEBP_PIPE,       image_webp_pipe);
 +
-     REGISTER_PROTOCOL(LIBRTMP,          librtmp);
-     REGISTER_PROTOCOL(LIBRTMPE,         librtmpe);
-     REGISTER_PROTOCOL(LIBRTMPS,         librtmps);
-     REGISTER_PROTOCOL(LIBRTMPT,         librtmpt);
-     REGISTER_PROTOCOL(LIBRTMPTE,        librtmpte);
-     REGISTER_PROTOCOL(LIBSSH,           libssh);
-     REGISTER_PROTOCOL(LIBSMBCLIENT,     libsmbclient);
 +    /* external libraries */
 +    REGISTER_MUXER   (CHROMAPRINT,      chromaprint);
 +    REGISTER_DEMUXER (LIBGME,           libgme);
 +    REGISTER_DEMUXER (LIBMODPLUG,       libmodplug);
 +    REGISTER_MUXDEMUX(LIBNUT,           libnut);
  }
index 138ef13,0000000..a835292
mode 100644,000000..100644
--- /dev/null
@@@ -1,699 -1,0 +1,699 @@@
- URLProtocol ff_async_protocol = {
 +/*
 + * 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);
 +    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,
 +};
 +
- #ifdef TEST
++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,
 +};
 +
- URLProtocol ff_async_test_protocol = {
++#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
@@@ -280,15 -169,14 +269,16 @@@ int ffurl_handshake(URLContext *c
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
      "0123456789+-."
  
- static struct URLProtocol *url_find_protocol(const char *filename)
 -int ffurl_alloc(URLContext **puc, const char *filename, int flags,
 -                const AVIOInterruptCB *int_cb)
++static const struct URLProtocol *url_find_protocol(const char *filename)
  {
-     URLProtocol *up = NULL;
++    const URLProtocol *up;
      char proto_str[128], proto_nested[128], *ptr;
      size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
+     int i;
  
 -    if (filename[proto_len] != ':' || is_dos_path(filename))
 +    if (filename[proto_len] != ':' &&
 +        (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
 +        is_dos_path(filename))
          strcpy(proto_str, "file");
      else
          av_strlcpy(proto_str, filename,
      if ((ptr = strchr(proto_nested, '+')))
          *ptr = '\0';
  
-     while (up = ffurl_protocol_next(up)) {
+     for (i = 0; ff_url_protocols[i]; i++) {
 -        const URLProtocol *up = ff_url_protocols[i];
++        up = ff_url_protocols[i];
          if (!strcmp(proto_str, up->name))
 -            return url_alloc_for_protocol(puc, up, filename, flags, int_cb);
 +            break;
          if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
              !strcmp(proto_nested, up->name))
 -            return url_alloc_for_protocol(puc, up, filename, flags, int_cb);
 +            break;
      }
-     URLProtocol *p = NULL;
-     if (!first_protocol) {
-         av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. "
-                                      "Missing call to av_register_all()?\n");
-     }
 +
 +    return up;
 +}
 +
 +int ffurl_alloc(URLContext **puc, const char *filename, int flags,
 +                const AVIOInterruptCB *int_cb)
 +{
++    const URLProtocol *p = NULL;
 +
 +    p = url_find_protocol(filename);
 +    if (p)
 +       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
 +
      *puc = NULL;
 +    if (av_strstart(filename, "https:", NULL))
 +        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
 +                                     "openssl, gnutls,\n"
 +                                     "or securetransport enabled.\n");
      return AVERROR_PROTOCOL_NOT_FOUND;
  }
  
@@@ -476,19 -307,6 +462,19 @@@ int ffurl_closep(URLContext **hh
      return ret;
  }
  
-     URLProtocol *p = url_find_protocol(url);
 +int ffurl_close(URLContext *h)
 +{
 +    return ffurl_closep(&h);
 +}
 +
 +
 +const char *avio_find_protocol_name(const char *url)
 +{
++    const URLProtocol *p = url_find_protocol(url);
 +
 +    return p ? p->name : NULL;
 +}
 +
  int avio_check(const char *url, int flags)
  {
      URLContext *h;
index d2c57aa,0000000..9282bf9
mode 100644,000000..100644
--- /dev/null
@@@ -1,235 -1,0 +1,235 @@@
- URLProtocol ff_bluray_protocol = {
 +/*
 + * BluRay (libbluray) protocol
 + *
 + * Copyright (c) 2012 Petri Hintukainen <phintuka <at> users.sourceforge.net>
 + *
 + * 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 <libbluray/bluray.h>
 +
 +#include "libavutil/avstring.h"
 +#include "libavformat/avformat.h"
 +#include "libavformat/url.h"
 +#include "libavutil/opt.h"
 +
 +#define BLURAY_PROTO_PREFIX     "bluray:"
 +#define MIN_PLAYLIST_LENGTH     180     /* 3 min */
 +
 +typedef struct {
 +    const AVClass *class;
 +
 +    BLURAY *bd;
 +
 +    int playlist;
 +    int angle;
 +    int chapter;
 +    /*int region;*/
 +} BlurayContext;
 +
 +#define OFFSET(x) offsetof(BlurayContext, x)
 +static const AVOption options[] = {
 +{"playlist", "", OFFSET(playlist), AV_OPT_TYPE_INT, { .i64=-1 }, -1,  99999, AV_OPT_FLAG_DECODING_PARAM },
 +{"angle",    "", OFFSET(angle),    AV_OPT_TYPE_INT, { .i64=0 },   0,   0xfe, AV_OPT_FLAG_DECODING_PARAM },
 +{"chapter",  "", OFFSET(chapter),  AV_OPT_TYPE_INT, { .i64=1 },   1, 0xfffe, AV_OPT_FLAG_DECODING_PARAM },
 +/*{"region",   "bluray player region code (1 = region A, 2 = region B, 4 = region C)", OFFSET(region), AV_OPT_TYPE_INT, { .i64=0 }, 0, 3, AV_OPT_FLAG_DECODING_PARAM },*/
 +{NULL}
 +};
 +
 +static const AVClass bluray_context_class = {
 +    .class_name     = "bluray",
 +    .item_name      = av_default_item_name,
 +    .option         = options,
 +    .version        = LIBAVUTIL_VERSION_INT,
 +};
 +
 +
 +static int check_disc_info(URLContext *h)
 +{
 +    BlurayContext *bd = h->priv_data;
 +    const BLURAY_DISC_INFO *disc_info;
 +
 +    disc_info = bd_get_disc_info(bd->bd);
 +    if (!disc_info) {
 +        av_log(h, AV_LOG_ERROR, "bd_get_disc_info() failed\n");
 +        return -1;
 +    }
 +
 +    if (!disc_info->bluray_detected) {
 +        av_log(h, AV_LOG_ERROR, "BluRay disc not detected\n");
 +        return -1;
 +    }
 +
 +    /* AACS */
 +    if (disc_info->aacs_detected && !disc_info->aacs_handled) {
 +        if (!disc_info->libaacs_detected) {
 +            av_log(h, AV_LOG_ERROR,
 +                   "Media stream encrypted with AACS, install and configure libaacs\n");
 +        } else {
 +            av_log(h, AV_LOG_ERROR, "Your libaacs can't decrypt this media\n");
 +        }
 +        return -1;
 +    }
 +
 +    /* BD+ */
 +    if (disc_info->bdplus_detected && !disc_info->bdplus_handled) {
 +        /*
 +        if (!disc_info->libbdplus_detected) {
 +            av_log(h, AV_LOG_ERROR,
 +                   "Media stream encrypted with BD+, install and configure libbdplus");
 +        } else {
 +        */
 +            av_log(h, AV_LOG_ERROR, "Unable to decrypt BD+ encrypted media\n");
 +        /*}*/
 +        return -1;
 +    }
 +
 +    return 0;
 +}
 +
 +static int bluray_close(URLContext *h)
 +{
 +    BlurayContext *bd = h->priv_data;
 +    if (bd->bd) {
 +        bd_close(bd->bd);
 +    }
 +
 +    return 0;
 +}
 +
 +static int bluray_open(URLContext *h, const char *path, int flags)
 +{
 +    BlurayContext *bd = h->priv_data;
 +    int num_title_idx;
 +    const char *diskname = path;
 +
 +    av_strstart(path, BLURAY_PROTO_PREFIX, &diskname);
 +
 +    bd->bd = bd_open(diskname, NULL);
 +    if (!bd->bd) {
 +        av_log(h, AV_LOG_ERROR, "bd_open() failed\n");
 +        return AVERROR(EIO);
 +    }
 +
 +    /* check if disc can be played */
 +    if (check_disc_info(h) < 0) {
 +        return AVERROR(EIO);
 +    }
 +
 +    /* setup player registers */
 +    /* region code has no effect without menus
 +    if (bd->region > 0 && bd->region < 5) {
 +        av_log(h, AV_LOG_INFO, "setting region code to %d (%c)\n", bd->region, 'A' + (bd->region - 1));
 +        bd_set_player_setting(bd->bd, BLURAY_PLAYER_SETTING_REGION_CODE, bd->region);
 +    }
 +    */
 +
 +    /* load title list */
 +    num_title_idx = bd_get_titles(bd->bd, TITLES_RELEVANT, MIN_PLAYLIST_LENGTH);
 +    av_log(h, AV_LOG_INFO, "%d usable playlists:\n", num_title_idx);
 +    if (num_title_idx < 1) {
 +        return AVERROR(EIO);
 +    }
 +
 +    /* if playlist was not given, select longest playlist */
 +    if (bd->playlist < 0) {
 +        uint64_t duration = 0;
 +        int i;
 +        for (i = 0; i < num_title_idx; i++) {
 +            BLURAY_TITLE_INFO *info = bd_get_title_info(bd->bd, i, 0);
 +
 +            av_log(h, AV_LOG_INFO, "playlist %05d.mpls (%d:%02d:%02d)\n",
 +                   info->playlist,
 +                   ((int)(info->duration / 90000) / 3600),
 +                   ((int)(info->duration / 90000) % 3600) / 60,
 +                   ((int)(info->duration / 90000) % 60));
 +
 +            if (info->duration > duration) {
 +                bd->playlist = info->playlist;
 +                duration = info->duration;
 +            }
 +
 +            bd_free_title_info(info);
 +        }
 +        av_log(h, AV_LOG_INFO, "selected %05d.mpls\n", bd->playlist);
 +    }
 +
 +    /* select playlist */
 +    if (bd_select_playlist(bd->bd, bd->playlist) <= 0) {
 +        av_log(h, AV_LOG_ERROR, "bd_select_playlist(%05d.mpls) failed\n", bd->playlist);
 +        return AVERROR(EIO);
 +    }
 +
 +    /* select angle */
 +    if (bd->angle >= 0) {
 +        bd_select_angle(bd->bd, bd->angle);
 +    }
 +
 +    /* select chapter */
 +    if (bd->chapter > 1) {
 +        bd_seek_chapter(bd->bd, bd->chapter - 1);
 +    }
 +
 +    return 0;
 +}
 +
 +static int bluray_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    BlurayContext *bd = h->priv_data;
 +    int len;
 +
 +    if (!bd || !bd->bd) {
 +        return AVERROR(EFAULT);
 +    }
 +
 +    len = bd_read(bd->bd, buf, size);
 +
 +    return len;
 +}
 +
 +static int64_t bluray_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    BlurayContext *bd = h->priv_data;
 +
 +    if (!bd || !bd->bd) {
 +        return AVERROR(EFAULT);
 +    }
 +
 +    switch (whence) {
 +    case SEEK_SET:
 +    case SEEK_CUR:
 +    case SEEK_END:
 +        return bd_seek(bd->bd, pos);
 +
 +    case AVSEEK_SIZE:
 +        return bd_get_title_size(bd->bd);
 +    }
 +
 +    av_log(h, AV_LOG_ERROR, "Unsupported whence operation %d\n", whence);
 +    return AVERROR(EINVAL);
 +}
 +
 +
++const URLProtocol ff_bluray_protocol = {
 +    .name            = "bluray",
 +    .url_close       = bluray_close,
 +    .url_open        = bluray_open,
 +    .url_read        = bluray_read,
 +    .url_seek        = bluray_seek,
 +    .priv_data_size  = sizeof(BlurayContext),
 +    .priv_data_class = &bluray_context_class,
 +};
index 8e8b9e8,0000000..2889e3b
mode 100644,000000..100644
--- /dev/null
@@@ -1,323 -1,0 +1,323 @@@
- URLProtocol ff_cache_protocol = {
 +/*
 + * 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/file.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 = av_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);
 +}
 +
 +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 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_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,
 +};
Simple merge
@@@ -284,11 -159,10 +284,11 @@@ static int crypto_close(URLContext *h
      return 0;
  }
  
- URLProtocol ff_crypto_protocol = {
const URLProtocol ff_crypto_protocol = {
      .name            = "crypto",
 -    .url_open        = crypto_open,
 +    .url_open2       = crypto_open2,
      .url_read        = crypto_read,
 +    .url_write       = crypto_write,
      .url_close       = crypto_close,
      .priv_data_size  = sizeof(CryptoContext),
      .priv_data_class = &crypto_class,
index 1598b99,0000000..1863830
mode 100644,000000..100644
--- /dev/null
@@@ -1,118 -1,0 +1,118 @@@
- URLProtocol ff_data_protocol = {
 +/*
 + * Copyright (c) 2012 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 <string.h>
 +#include "libavutil/avstring.h"
 +#include "libavutil/base64.h"
 +#include "url.h"
 +
 +typedef struct {
 +    const uint8_t *data;
 +    void *tofree;
 +    size_t size;
 +    size_t pos;
 +} DataContext;
 +
 +static av_cold int data_open(URLContext *h, const char *uri, int flags)
 +{
 +    DataContext *dc = h->priv_data;
 +    const char *data, *opt, *next;
 +    char *ddata;
 +    int ret, base64 = 0;
 +    size_t in_size;
 +
 +    /* data:content/type[;base64],payload */
 +
 +    av_strstart(uri, "data:", &uri);
 +    data = strchr(uri, ',');
 +    if (!data) {
 +        av_log(h, AV_LOG_ERROR, "No ',' delimiter in URI\n");
 +        return AVERROR(EINVAL);
 +    }
 +    opt = uri;
 +    while (opt < data) {
 +        next = av_x_if_null(memchr(opt, ';', data - opt), data);
 +        if (opt == uri) {
 +            if (!memchr(opt, '/', next - opt)) { /* basic validity check */
 +                av_log(h, AV_LOG_ERROR, "Invalid content-type '%.*s'\n",
 +                       (int)(next - opt), opt);
 +                return AVERROR(EINVAL);
 +            }
 +            av_log(h, AV_LOG_VERBOSE, "Content-type: %.*s\n",
 +                   (int)(next - opt), opt);
 +        } else {
 +            if (!av_strncasecmp(opt, "base64", next - opt)) {
 +                base64 = 1;
 +            } else {
 +                av_log(h, AV_LOG_VERBOSE, "Ignoring option '%.*s'\n",
 +                       (int)(next - opt), opt);
 +            }
 +        }
 +        opt = next + 1;
 +    }
 +
 +    data++;
 +    in_size = strlen(data);
 +    if (base64) {
 +        size_t out_size = 3 * (in_size / 4) + 1;
 +
 +        if (out_size > INT_MAX || !(ddata = av_malloc(out_size)))
 +            return AVERROR(ENOMEM);
 +        if ((ret = av_base64_decode(ddata, data, out_size)) < 0) {
 +            av_free(ddata);
 +            av_log(h, AV_LOG_ERROR, "Invalid base64 in URI\n");
 +            return ret;
 +        }
 +        dc->data = dc->tofree = ddata;
 +        dc->size = ret;
 +    } else {
 +        dc->data = data;
 +        dc->size = in_size;
 +    }
 +    return 0;
 +}
 +
 +static av_cold int data_close(URLContext *h)
 +{
 +    DataContext *dc = h->priv_data;
 +
 +    av_freep(&dc->tofree);
 +    return 0;
 +}
 +
 +static int data_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    DataContext *dc = h->priv_data;
 +
 +    if (dc->pos >= dc->size)
 +        return AVERROR_EOF;
 +    size = FFMIN(size, dc->size - dc->pos);
 +    memcpy(buf, dc->data + dc->pos, size);
 +    dc->pos += size;
 +    return size;
 +}
 +
++const URLProtocol ff_data_protocol = {
 +    .name           = "data",
 +    .url_open       = data_open,
 +    .url_close      = data_close,
 +    .url_read       = data_read,
 +    .priv_data_size = sizeof(DataContext),
 +};
@@@ -245,91 -144,7 +245,91 @@@ static int file_close(URLContext *h
      return close(c->fd);
  }
  
- URLProtocol ff_file_protocol = {
 +static int file_open_dir(URLContext *h)
 +{
 +#if HAVE_LSTAT
 +    FileContext *c = h->priv_data;
 +
 +    c->dir = opendir(h->filename);
 +    if (!c->dir)
 +        return AVERROR(errno);
 +
 +    return 0;
 +#else
 +    return AVERROR(ENOSYS);
 +#endif /* HAVE_LSTAT */
 +}
 +
 +static int file_read_dir(URLContext *h, AVIODirEntry **next)
 +{
 +#if HAVE_LSTAT
 +    FileContext *c = h->priv_data;
 +    struct dirent *dir;
 +    char *fullpath = NULL;
 +
 +    *next = ff_alloc_dir_entry();
 +    if (!*next)
 +        return AVERROR(ENOMEM);
 +    do {
 +        errno = 0;
 +        dir = readdir(c->dir);
 +        if (!dir) {
 +            av_freep(next);
 +            return AVERROR(errno);
 +        }
 +    } while (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."));
 +
 +    fullpath = av_append_path_component(h->filename, dir->d_name);
 +    if (fullpath) {
 +        struct stat st;
 +        if (!lstat(fullpath, &st)) {
 +            if (S_ISDIR(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_DIRECTORY;
 +            else if (S_ISFIFO(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_NAMED_PIPE;
 +            else if (S_ISCHR(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_CHARACTER_DEVICE;
 +            else if (S_ISBLK(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_BLOCK_DEVICE;
 +            else if (S_ISLNK(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_SYMBOLIC_LINK;
 +            else if (S_ISSOCK(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_SOCKET;
 +            else if (S_ISREG(st.st_mode))
 +                (*next)->type = AVIO_ENTRY_FILE;
 +            else
 +                (*next)->type = AVIO_ENTRY_UNKNOWN;
 +
 +            (*next)->group_id = st.st_gid;
 +            (*next)->user_id = st.st_uid;
 +            (*next)->size = st.st_size;
 +            (*next)->filemode = st.st_mode & 0777;
 +            (*next)->modification_timestamp = INT64_C(1000000) * st.st_mtime;
 +            (*next)->access_timestamp =  INT64_C(1000000) * st.st_atime;
 +            (*next)->status_change_timestamp = INT64_C(1000000) * st.st_ctime;
 +        }
 +        av_free(fullpath);
 +    }
 +
 +    (*next)->name = av_strdup(dir->d_name);
 +    return 0;
 +#else
 +    return AVERROR(ENOSYS);
 +#endif /* HAVE_LSTAT */
 +}
 +
 +static int file_close_dir(URLContext *h)
 +{
 +#if HAVE_LSTAT
 +    FileContext *c = h->priv_data;
 +    closedir(c->dir);
 +    return 0;
 +#else
 +    return AVERROR(ENOSYS);
 +#endif /* HAVE_LSTAT */
 +}
 +
+ const URLProtocol ff_file_protocol = {
      .name                = "file",
      .url_open            = file_open,
      .url_read            = file_read,
index c2a60f6,0000000..50b28e2
mode 100644,000000..100644
--- /dev/null
@@@ -1,1120 -1,0 +1,1120 @@@
- URLProtocol ff_ftp_protocol = {
 +/*
 + * 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, 0};
 +
 +    snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
 +    if (ftp_send_command(s, command, retr_codes, NULL) != 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, 0};
 +
 +    snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
 +    if (ftp_send_command(s, command, stor_codes, NULL) != 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);
 +        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);
 +        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",
 +};
Simple merge
Simple merge
@@@ -1508,11 -1013,9 +1508,11 @@@ static const AVClass flavor ## _context
  #if CONFIG_HTTP_PROTOCOL
  HTTP_CLASS(http);
  
- URLProtocol ff_http_protocol = {
const URLProtocol ff_http_protocol = {
      .name                = "http",
      .url_open2           = http_open,
 +    .url_accept          = http_accept,
 +    .url_handshake       = http_handshake,
      .url_read            = http_read,
      .url_write           = http_write,
      .url_seek            = http_seek,
Simple merge
Simple merge
index 84fef7f,0000000..b68cd8b
mode 100644,000000..100644
--- /dev/null
@@@ -1,383 -1,0 +1,383 @@@
- URLProtocol ff_libsmbclient_protocol = {
 +/*
 + * Copyright (c) 2014 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 <libsmbclient.h>
 +#include "libavutil/avstring.h"
 +#include "libavutil/opt.h"
 +#include "avformat.h"
 +#include "internal.h"
 +#include "url.h"
 +
 +typedef struct {
 +    const AVClass *class;
 +    SMBCCTX *ctx;
 +    int dh;
 +    int fd;
 +    int64_t filesize;
 +    int trunc;
 +    int timeout;
 +    char *workgroup;
 +} LIBSMBContext;
 +
 +static void libsmbc_get_auth_data(SMBCCTX *c, const char *server, const char *share,
 +                                  char *workgroup, int workgroup_len,
 +                                  char *username, int username_len,
 +                                  char *password, int password_len)
 +{
 +    /* Do nothing yet. Credentials are passed via url.
 +     * Callback must exists, there might be a segmentation fault otherwise. */
 +}
 +
 +static av_cold int libsmbc_connect(URLContext *h)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +
 +    libsmbc->ctx = smbc_new_context();
 +    if (!libsmbc->ctx) {
 +        int ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Cannot create context: %s.\n", strerror(errno));
 +        return ret;
 +    }
 +    if (!smbc_init_context(libsmbc->ctx)) {
 +        int ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Cannot initialize context: %s.\n", strerror(errno));
 +        return ret;
 +    }
 +    smbc_set_context(libsmbc->ctx);
 +
 +    smbc_setOptionUserData(libsmbc->ctx, h);
 +    smbc_setFunctionAuthDataWithContext(libsmbc->ctx, libsmbc_get_auth_data);
 +
 +    if (libsmbc->timeout != -1)
 +        smbc_setTimeout(libsmbc->ctx, libsmbc->timeout);
 +    if (libsmbc->workgroup)
 +        smbc_setWorkgroup(libsmbc->ctx, libsmbc->workgroup);
 +
 +    if (smbc_init(NULL, 0) < 0) {
 +        int ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Initialization failed: %s\n", strerror(errno));
 +        return ret;
 +    }
 +    return 0;
 +}
 +
 +static av_cold int libsmbc_close(URLContext *h)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    if (libsmbc->fd >= 0) {
 +        smbc_close(libsmbc->fd);
 +        libsmbc->fd = -1;
 +    }
 +    if (libsmbc->ctx) {
 +        smbc_free_context(libsmbc->ctx, 1);
 +        libsmbc->ctx = NULL;
 +    }
 +    return 0;
 +}
 +
 +static av_cold int libsmbc_open(URLContext *h, const char *url, int flags)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int access, ret;
 +    struct stat st;
 +
 +    libsmbc->fd = -1;
 +    libsmbc->filesize = -1;
 +
 +    if ((ret = libsmbc_connect(h)) < 0)
 +        goto fail;
 +
 +    if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) {
 +        access = O_CREAT | O_RDWR;
 +        if (libsmbc->trunc)
 +            access |= O_TRUNC;
 +    } else if (flags & AVIO_FLAG_WRITE) {
 +        access = O_CREAT | O_WRONLY;
 +        if (libsmbc->trunc)
 +            access |= O_TRUNC;
 +    } else
 +        access = O_RDONLY;
 +
 +    /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
 +    if ((libsmbc->fd = smbc_open(url, access, 0666)) < 0) {
 +        ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "File open failed: %s\n", strerror(errno));
 +        goto fail;
 +    }
 +
 +    if (smbc_fstat(libsmbc->fd, &st) < 0)
 +        av_log(h, AV_LOG_WARNING, "Cannot stat file: %s\n", strerror(errno));
 +    else
 +        libsmbc->filesize = st.st_size;
 +
 +    return 0;
 +  fail:
 +    libsmbc_close(h);
 +    return ret;
 +}
 +
 +static int64_t libsmbc_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int64_t newpos;
 +
 +    if (whence == AVSEEK_SIZE) {
 +        if (libsmbc->filesize == -1) {
 +            av_log(h, AV_LOG_ERROR, "Error during seeking: filesize is unknown.\n");
 +            return AVERROR(EIO);
 +        } else
 +            return libsmbc->filesize;
 +    }
 +
 +    if ((newpos = smbc_lseek(libsmbc->fd, pos, whence)) < 0) {
 +        int err = errno;
 +        av_log(h, AV_LOG_ERROR, "Error during seeking: %s\n", strerror(err));
 +        return AVERROR(err);
 +    }
 +
 +    return newpos;
 +}
 +
 +static int libsmbc_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int bytes_read;
 +
 +    if ((bytes_read = smbc_read(libsmbc->fd, buf, size)) < 0) {
 +        int ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Read error: %s\n", strerror(errno));
 +        return ret;
 +    }
 +
 +    return bytes_read;
 +}
 +
 +static int libsmbc_write(URLContext *h, const unsigned char *buf, int size)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int bytes_written;
 +
 +    if ((bytes_written = smbc_write(libsmbc->fd, buf, size)) < 0) {
 +        int ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Write error: %s\n", strerror(errno));
 +        return ret;
 +    }
 +
 +    return bytes_written;
 +}
 +
 +static int libsmbc_open_dir(URLContext *h)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int ret;
 +
 +    if ((ret = libsmbc_connect(h)) < 0)
 +        goto fail;
 +
 +    if ((libsmbc->dh = smbc_opendir(h->filename)) < 0) {
 +        ret = AVERROR(errno);
 +        av_log(h, AV_LOG_ERROR, "Error opening dir: %s\n", strerror(errno));
 +        goto fail;
 +    }
 +
 +    return 0;
 +
 +  fail:
 +    libsmbc_close(h);
 +    return ret;
 +}
 +
 +static int libsmbc_read_dir(URLContext *h, AVIODirEntry **next)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    AVIODirEntry *entry;
 +    struct smbc_dirent *dirent = NULL;
 +    char *url = NULL;
 +    int skip_entry;
 +
 +    *next = entry = ff_alloc_dir_entry();
 +    if (!entry)
 +        return AVERROR(ENOMEM);
 +
 +    do {
 +        skip_entry = 0;
 +        dirent = smbc_readdir(libsmbc->dh);
 +        if (!dirent) {
 +            av_freep(next);
 +            return 0;
 +        }
 +        switch (dirent->smbc_type) {
 +        case SMBC_DIR:
 +            entry->type = AVIO_ENTRY_DIRECTORY;
 +            break;
 +        case SMBC_FILE:
 +            entry->type = AVIO_ENTRY_FILE;
 +            break;
 +        case SMBC_FILE_SHARE:
 +            entry->type = AVIO_ENTRY_SHARE;
 +            break;
 +        case SMBC_SERVER:
 +            entry->type = AVIO_ENTRY_SERVER;
 +            break;
 +        case SMBC_WORKGROUP:
 +            entry->type = AVIO_ENTRY_WORKGROUP;
 +            break;
 +        case SMBC_COMMS_SHARE:
 +        case SMBC_IPC_SHARE:
 +        case SMBC_PRINTER_SHARE:
 +            skip_entry = 1;
 +            break;
 +        case SMBC_LINK:
 +        default:
 +            entry->type = AVIO_ENTRY_UNKNOWN;
 +            break;
 +        }
 +    } while (skip_entry || !strcmp(dirent->name, ".") ||
 +             !strcmp(dirent->name, ".."));
 +
 +    entry->name = av_strdup(dirent->name);
 +    if (!entry->name) {
 +        av_freep(next);
 +        return AVERROR(ENOMEM);
 +    }
 +
 +    url = av_append_path_component(h->filename, dirent->name);
 +    if (url) {
 +        struct stat st;
 +        if (!smbc_stat(url, &st)) {
 +            entry->group_id = st.st_gid;
 +            entry->user_id = st.st_uid;
 +            entry->size = st.st_size;
 +            entry->filemode = st.st_mode & 0777;
 +            entry->modification_timestamp = INT64_C(1000000) * st.st_mtime;
 +            entry->access_timestamp =  INT64_C(1000000) * st.st_atime;
 +            entry->status_change_timestamp = INT64_C(1000000) * st.st_ctime;
 +        }
 +        av_free(url);
 +    }
 +
 +    return 0;
 +}
 +
 +static int libsmbc_close_dir(URLContext *h)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    if (libsmbc->dh >= 0) {
 +        smbc_closedir(libsmbc->dh);
 +        libsmbc->dh = -1;
 +    }
 +    libsmbc_close(h);
 +    return 0;
 +}
 +
 +static int libsmbc_delete(URLContext *h)
 +{
 +    LIBSMBContext *libsmbc = h->priv_data;
 +    int ret;
 +    struct stat st;
 +
 +    if ((ret = libsmbc_connect(h)) < 0)
 +        goto cleanup;
 +
 +    if ((libsmbc->fd = smbc_open(h->filename, O_WRONLY, 0666)) < 0) {
 +        ret = AVERROR(errno);
 +        goto cleanup;
 +    }
 +
 +    if (smbc_fstat(libsmbc->fd, &st) < 0) {
 +        ret = AVERROR(errno);
 +        goto cleanup;
 +    }
 +
 +    smbc_close(libsmbc->fd);
 +    libsmbc->fd = -1;
 +
 +    if (S_ISDIR(st.st_mode)) {
 +        if (smbc_rmdir(h->filename) < 0) {
 +            ret = AVERROR(errno);
 +            goto cleanup;
 +        }
 +    } else {
 +        if (smbc_unlink(h->filename) < 0) {
 +            ret = AVERROR(errno);
 +            goto cleanup;
 +        }
 +    }
 +
 +    ret = 0;
 +
 +cleanup:
 +    libsmbc_close(h);
 +    return ret;
 +}
 +
 +static int libsmbc_move(URLContext *h_src, URLContext *h_dst)
 +{
 +    LIBSMBContext *libsmbc = h_src->priv_data;
 +    int ret;
 +
 +    if ((ret = libsmbc_connect(h_src)) < 0)
 +        goto cleanup;
 +
 +    if ((libsmbc->dh = smbc_rename(h_src->filename, h_dst->filename)) < 0) {
 +        ret = AVERROR(errno);
 +        goto cleanup;
 +    }
 +
 +    ret = 0;
 +
 +cleanup:
 +    libsmbc_close(h_src);
 +    return ret;
 +}
 +
 +#define OFFSET(x) offsetof(LIBSMBContext, x)
 +#define D AV_OPT_FLAG_DECODING_PARAM
 +#define E AV_OPT_FLAG_ENCODING_PARAM
 +static const AVOption options[] = {
 +    {"timeout",   "set timeout in ms of socket I/O operations",    OFFSET(timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
 +    {"truncate",  "truncate existing files on write",              OFFSET(trunc),   AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
 +    {"workgroup", "set the workgroup used for making connections", OFFSET(workgroup), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
 +    {NULL}
 +};
 +
 +static const AVClass libsmbclient_context_class = {
 +    .class_name     = "libsmbc",
 +    .item_name      = av_default_item_name,
 +    .option         = options,
 +    .version        = LIBAVUTIL_VERSION_INT,
 +};
 +
++const URLProtocol ff_libsmbclient_protocol = {
 +    .name                = "smb",
 +    .url_open            = libsmbc_open,
 +    .url_read            = libsmbc_read,
 +    .url_write           = libsmbc_write,
 +    .url_seek            = libsmbc_seek,
 +    .url_close           = libsmbc_close,
 +    .url_delete          = libsmbc_delete,
 +    .url_move            = libsmbc_move,
 +    .url_open_dir        = libsmbc_open_dir,
 +    .url_read_dir        = libsmbc_read_dir,
 +    .url_close_dir       = libsmbc_close_dir,
 +    .priv_data_size      = sizeof(LIBSMBContext),
 +    .priv_data_class     = &libsmbclient_context_class,
 +    .flags               = URL_PROTOCOL_FLAG_NETWORK,
 +};
index 3c056f8,0000000..49e92e7
mode 100644,000000..100644
--- /dev/null
@@@ -1,507 -1,0 +1,507 @@@
- URLProtocol ff_libssh_protocol = {
 +/*
 + * 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 <fcntl.h>
 +#define LIBSSH_STATIC
 +#include <libssh/sftp.h>
 +#include "libavutil/avstring.h"
 +#include "libavutil/opt.h"
 +#include "libavutil/attributes.h"
 +#include "libavformat/avio.h"
 +#include "avformat.h"
 +#include "internal.h"
 +#include "url.h"
 +
 +typedef struct {
 +    const AVClass *class;
 +    ssh_session session;
 +    sftp_session sftp;
 +    sftp_file file;
 +    sftp_dir dir;
 +    int64_t filesize;
 +    int rw_timeout;
 +    int trunc;
 +    char *priv_key;
 +} LIBSSHContext;
 +
 +static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* hostname, unsigned int port)
 +{
 +    static const int verbosity = SSH_LOG_NOLOG;
 +
 +    if (!(libssh->session = ssh_new())) {
 +        av_log(libssh, AV_LOG_ERROR, "SSH session creation failed: %s\n", ssh_get_error(libssh->session));
 +        return AVERROR(ENOMEM);
 +    }
 +    ssh_options_set(libssh->session, SSH_OPTIONS_HOST, hostname);
 +    ssh_options_set(libssh->session, SSH_OPTIONS_PORT, &port);
 +    ssh_options_set(libssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
 +    if (libssh->rw_timeout > 0) {
 +        long timeout = libssh->rw_timeout * 1000;
 +        ssh_options_set(libssh->session, SSH_OPTIONS_TIMEOUT_USEC, &timeout);
 +    }
 +
 +    if (ssh_options_parse_config(libssh->session, NULL) < 0) {
 +        av_log(libssh, AV_LOG_WARNING, "Could not parse the config file.\n");
 +    }
 +
 +    if (ssh_connect(libssh->session) != SSH_OK) {
 +        av_log(libssh, AV_LOG_ERROR, "Connection failed: %s\n", ssh_get_error(libssh->session));
 +        return AVERROR(EIO);
 +    }
 +
 +    return 0;
 +}
 +
 +static av_cold int libssh_authentication(LIBSSHContext *libssh, const char *user, const char *password)
 +{
 +    int authorized = 0;
 +    int auth_methods;
 +
 +    if (user)
 +        ssh_options_set(libssh->session, SSH_OPTIONS_USER, user);
 +
 +    if (ssh_userauth_none(libssh->session, NULL) == SSH_AUTH_SUCCESS)
 +        return 0;
 +
 +    auth_methods = ssh_userauth_list(libssh->session, NULL);
 +
 +    if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
 +        if (libssh->priv_key) {
 +            ssh_string pub_key;
 +            ssh_private_key priv_key;
 +            int type;
 +            if (!ssh_try_publickey_from_file(libssh->session, libssh->priv_key, &pub_key, &type)) {
 +                priv_key = privatekey_from_file(libssh->session, libssh->priv_key, type, password);
 +                if (ssh_userauth_pubkey(libssh->session, NULL, pub_key, priv_key) == SSH_AUTH_SUCCESS) {
 +                    av_log(libssh, AV_LOG_DEBUG, "Authentication successful with selected private key.\n");
 +                    authorized = 1;
 +                }
 +            } else {
 +                av_log(libssh, AV_LOG_DEBUG, "Invalid key is provided.\n");
 +                return AVERROR(EACCES);
 +            }
 +        } else if (ssh_userauth_autopubkey(libssh->session, password) == SSH_AUTH_SUCCESS) {
 +            av_log(libssh, AV_LOG_DEBUG, "Authentication successful with auto selected key.\n");
 +            authorized = 1;
 +        }
 +    }
 +
 +    if (!authorized && (auth_methods & SSH_AUTH_METHOD_PASSWORD)) {
 +        if (ssh_userauth_password(libssh->session, NULL, password) == SSH_AUTH_SUCCESS) {
 +            av_log(libssh, AV_LOG_DEBUG, "Authentication successful with password.\n");
 +            authorized = 1;
 +        }
 +    }
 +
 +    if (!authorized) {
 +        av_log(libssh, AV_LOG_ERROR, "Authentication failed.\n");
 +        return AVERROR(EACCES);
 +    }
 +
 +    return 0;
 +}
 +
 +static av_cold int libssh_create_sftp_session(LIBSSHContext *libssh)
 +{
 +    if (!(libssh->sftp = sftp_new(libssh->session))) {
 +        av_log(libssh, AV_LOG_ERROR, "SFTP session creation failed: %s\n", ssh_get_error(libssh->session));
 +        return AVERROR(ENOMEM);
 +    }
 +
 +    if (sftp_init(libssh->sftp) != SSH_OK) {
 +        av_log(libssh, AV_LOG_ERROR, "Error initializing sftp session: %s\n", ssh_get_error(libssh->session));
 +        return AVERROR(EIO);
 +    }
 +
 +    return 0;
 +}
 +
 +static av_cold int libssh_open_file(LIBSSHContext *libssh, int flags, const char *file)
 +{
 +    int access;
 +
 +    if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) {
 +        access = O_CREAT | O_RDWR;
 +        if (libssh->trunc)
 +            access |= O_TRUNC;
 +    } else if (flags & AVIO_FLAG_WRITE) {
 +        access = O_CREAT | O_WRONLY;
 +        if (libssh->trunc)
 +            access |= O_TRUNC;
 +    } else
 +        access = O_RDONLY;
 +
 +    /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
 +    if (!(libssh->file = sftp_open(libssh->sftp, file, access, 0666))) {
 +        av_log(libssh, AV_LOG_ERROR, "Error opening sftp file: %s\n", ssh_get_error(libssh->session));
 +        return AVERROR(EIO);
 +    }
 +
 +    return 0;
 +}
 +
 +static av_cold void libssh_stat_file(LIBSSHContext *libssh)
 +{
 +    sftp_attributes stat;
 +
 +    if (!(stat = sftp_fstat(libssh->file))) {
 +        av_log(libssh, AV_LOG_WARNING, "Cannot stat remote file.\n");
 +        libssh->filesize = -1;
 +    } else {
 +        libssh->filesize = stat->size;
 +        sftp_attributes_free(stat);
 +    }
 +}
 +
 +static av_cold int libssh_close(URLContext *h)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    if (libssh->file) {
 +        sftp_close(libssh->file);
 +        libssh->file = NULL;
 +    }
 +    if (libssh->sftp) {
 +        sftp_free(libssh->sftp);
 +        libssh->sftp = NULL;
 +    }
 +    if (libssh->session) {
 +        ssh_disconnect(libssh->session);
 +        ssh_free(libssh->session);
 +        libssh->session = NULL;
 +    }
 +    return 0;
 +}
 +
 +static av_cold int libssh_connect(URLContext *h, const char *url, char *path, size_t path_size)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    char proto[10], hostname[1024], credencials[1024];
 +    int port = 22, ret;
 +    const char *user = NULL, *pass = NULL;
 +    char *end = NULL;
 +
 +    av_url_split(proto, sizeof(proto),
 +                 credencials, sizeof(credencials),
 +                 hostname, sizeof(hostname),
 +                 &port,
 +                 path, path_size,
 +                 url);
 +
 +    if (!(*path))
 +        av_strlcpy(path, "/", path_size);
 +
 +    // a port of 0 will use a port from ~/.ssh/config or the default value 22
 +    if (port < 0 || port > 65535)
 +        port = 0;
 +
 +    if ((ret = libssh_create_ssh_session(libssh, hostname, port)) < 0)
 +        return ret;
 +
 +    user = av_strtok(credencials, ":", &end);
 +    pass = av_strtok(end, ":", &end);
 +
 +    if ((ret = libssh_authentication(libssh, user, pass)) < 0)
 +        return ret;
 +
 +    if ((ret = libssh_create_sftp_session(libssh)) < 0)
 +        return ret;
 +
 +    return 0;
 +}
 +
 +static av_cold int libssh_open(URLContext *h, const char *url, int flags)
 +{
 +    int ret;
 +    LIBSSHContext *libssh = h->priv_data;
 +    char path[MAX_URL_SIZE];
 +
 +    if ((ret = libssh_connect(h, url, path, sizeof(path))) < 0)
 +        goto fail;
 +
 +    if ((ret = libssh_open_file(libssh, flags, path)) < 0)
 +        goto fail;
 +
 +    libssh_stat_file(libssh);
 +
 +    return 0;
 +
 +  fail:
 +    libssh_close(h);
 +    return ret;
 +}
 +
 +static int64_t libssh_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    int64_t newpos;
 +
 +    if (libssh->filesize == -1 && (whence == AVSEEK_SIZE || whence == SEEK_END)) {
 +        av_log(h, AV_LOG_ERROR, "Error during seeking.\n");
 +        return AVERROR(EIO);
 +    }
 +
 +    switch(whence) {
 +    case AVSEEK_SIZE:
 +        return libssh->filesize;
 +    case SEEK_SET:
 +        newpos = pos;
 +        break;
 +    case SEEK_CUR:
 +        newpos = sftp_tell64(libssh->file) + pos;
 +        break;
 +    case SEEK_END:
 +        newpos = libssh->filesize + pos;
 +        break;
 +    default:
 +        return AVERROR(EINVAL);
 +    }
 +
 +    if (newpos < 0) {
 +        av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
 +        return AVERROR(EINVAL);
 +    }
 +
 +    if (sftp_seek64(libssh->file, newpos)) {
 +        av_log(h, AV_LOG_ERROR, "Error during seeking.\n");
 +        return AVERROR(EIO);
 +    }
 +
 +    return newpos;
 +}
 +
 +static int libssh_read(URLContext *h, unsigned char *buf, int size)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    int bytes_read;
 +
 +    if ((bytes_read = sftp_read(libssh->file, buf, size)) < 0) {
 +        av_log(libssh, AV_LOG_ERROR, "Read error.\n");
 +        return AVERROR(EIO);
 +    }
 +    return bytes_read;
 +}
 +
 +static int libssh_write(URLContext *h, const unsigned char *buf, int size)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    int bytes_written;
 +
 +    if ((bytes_written = sftp_write(libssh->file, buf, size)) < 0) {
 +        av_log(libssh, AV_LOG_ERROR, "Write error.\n");
 +        return AVERROR(EIO);
 +    }
 +    return bytes_written;
 +}
 +
 +static int libssh_open_dir(URLContext *h)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    int ret;
 +    char path[MAX_URL_SIZE];
 +
 +    if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0)
 +        goto fail;
 +
 +    if (!(libssh->dir = sftp_opendir(libssh->sftp, path))) {
 +        av_log(libssh, AV_LOG_ERROR, "Error opening sftp dir: %s\n", ssh_get_error(libssh->session));
 +        ret = AVERROR(EIO);
 +        goto fail;
 +    }
 +
 +    return 0;
 +
 +  fail:
 +    libssh_close(h);
 +    return ret;
 +}
 +
 +static int libssh_read_dir(URLContext *h, AVIODirEntry **next)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    sftp_attributes attr = NULL;
 +    AVIODirEntry *entry;
 +
 +    *next = entry = ff_alloc_dir_entry();
 +    if (!entry)
 +        return AVERROR(ENOMEM);
 +
 +    do {
 +        if (attr)
 +            sftp_attributes_free(attr);
 +        attr = sftp_readdir(libssh->sftp, libssh->dir);
 +        if (!attr) {
 +            av_freep(next);
 +            if (sftp_dir_eof(libssh->dir))
 +                return 0;
 +            return AVERROR(EIO);
 +        }
 +    } while (!strcmp(attr->name, ".") || !strcmp(attr->name, ".."));
 +
 +    entry->name = av_strdup(attr->name);
 +    entry->group_id = attr->gid;
 +    entry->user_id = attr->uid;
 +    entry->size = attr->size;
 +    entry->access_timestamp = INT64_C(1000000) * attr->atime;
 +    entry->modification_timestamp = INT64_C(1000000) * attr->mtime;
 +    entry->filemode = attr->permissions & 0777;
 +    switch(attr->type) {
 +    case SSH_FILEXFER_TYPE_REGULAR:
 +        entry->type = AVIO_ENTRY_FILE;
 +        break;
 +    case SSH_FILEXFER_TYPE_DIRECTORY:
 +        entry->type = AVIO_ENTRY_DIRECTORY;
 +        break;
 +    case SSH_FILEXFER_TYPE_SYMLINK:
 +        entry->type = AVIO_ENTRY_SYMBOLIC_LINK;
 +        break;
 +    case SSH_FILEXFER_TYPE_SPECIAL:
 +        /* Special type includes: sockets, char devices, block devices and pipes.
 +           It is probably better to return unknown type, to not confuse anybody. */
 +    case SSH_FILEXFER_TYPE_UNKNOWN:
 +    default:
 +        entry->type = AVIO_ENTRY_UNKNOWN;
 +    }
 +    sftp_attributes_free(attr);
 +    return 0;
 +}
 +
 +static int libssh_close_dir(URLContext *h)
 +{
 +    LIBSSHContext *libssh = h->priv_data;
 +    if (libssh->dir)
 +        sftp_closedir(libssh->dir);
 +    libssh->dir = NULL;
 +    libssh_close(h);
 +    return 0;
 +}
 +
 +static int libssh_delete(URLContext *h)
 +{
 +    int ret;
 +    LIBSSHContext *libssh = h->priv_data;
 +    sftp_attributes attr = NULL;
 +    char path[MAX_URL_SIZE];
 +
 +    if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0)
 +        goto cleanup;
 +
 +    if (!(attr = sftp_stat(libssh->sftp, path))) {
 +        ret = AVERROR(sftp_get_error(libssh->sftp));
 +        goto cleanup;
 +    }
 +
 +    if (attr->type == SSH_FILEXFER_TYPE_DIRECTORY) {
 +        if (sftp_rmdir(libssh->sftp, path) < 0) {
 +            ret = AVERROR(sftp_get_error(libssh->sftp));
 +            goto cleanup;
 +        }
 +    } else {
 +        if (sftp_unlink(libssh->sftp, path) < 0) {
 +            ret = AVERROR(sftp_get_error(libssh->sftp));
 +            goto cleanup;
 +        }
 +    }
 +
 +    ret = 0;
 +
 +cleanup:
 +    if (attr)
 +        sftp_attributes_free(attr);
 +    libssh_close(h);
 +    return ret;
 +}
 +
 +static int libssh_move(URLContext *h_src, URLContext *h_dst)
 +{
 +    int ret;
 +    LIBSSHContext *libssh = h_src->priv_data;
 +    char path_src[MAX_URL_SIZE], path_dst[MAX_URL_SIZE];
 +    char hostname_src[1024], hostname_dst[1024];
 +    char credentials_src[1024], credentials_dst[1024];
 +    int port_src = 22, port_dst = 22;
 +
 +    av_url_split(NULL, 0,
 +                 credentials_src, sizeof(credentials_src),
 +                 hostname_src, sizeof(hostname_src),
 +                 &port_src,
 +                 path_src, sizeof(path_src),
 +                 h_src->filename);
 +
 +    av_url_split(NULL, 0,
 +                 credentials_dst, sizeof(credentials_dst),
 +                 hostname_dst, sizeof(hostname_dst),
 +                 &port_dst,
 +                 path_dst, sizeof(path_dst),
 +                 h_dst->filename);
 +
 +    if (strcmp(credentials_src, credentials_dst) ||
 +            strcmp(hostname_src, hostname_dst) ||
 +            port_src != port_dst) {
 +        return AVERROR(EINVAL);
 +    }
 +
 +    if ((ret = libssh_connect(h_src, h_src->filename, path_src, sizeof(path_src))) < 0)
 +        goto cleanup;
 +
 +    if (sftp_rename(libssh->sftp, path_src, path_dst) < 0) {
 +        ret = AVERROR(sftp_get_error(libssh->sftp));
 +        goto cleanup;
 +    }
 +
 +    ret = 0;
 +
 +cleanup:
 +    libssh_close(h_src);
 +    return ret;
 +}
 +
 +#define OFFSET(x) offsetof(LIBSSHContext, 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 },
 +    {"truncate", "Truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
 +    {"private_key", "set path to private key", OFFSET(priv_key), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D|E },
 +    {NULL}
 +};
 +
 +static const AVClass libssh_context_class = {
 +    .class_name     = "libssh",
 +    .item_name      = av_default_item_name,
 +    .option         = options,
 +    .version        = LIBAVUTIL_VERSION_INT,
 +};
 +
++const URLProtocol ff_libssh_protocol = {
 +    .name                = "sftp",
 +    .url_open            = libssh_open,
 +    .url_read            = libssh_read,
 +    .url_write           = libssh_write,
 +    .url_seek            = libssh_seek,
 +    .url_close           = libssh_close,
 +    .url_delete          = libssh_delete,
 +    .url_move            = libssh_move,
 +    .url_open_dir        = libssh_open_dir,
 +    .url_read_dir        = libssh_read_dir,
 +    .url_close_dir       = libssh_close_dir,
 +    .priv_data_size      = sizeof(LIBSSHContext),
 +    .priv_data_class     = &libssh_context_class,
 +    .flags               = URL_PROTOCOL_FLAG_NETWORK,
 +};
Simple merge
@@@ -365,43 -358,7 +365,43 @@@ static int mmsh_read(URLContext *h, uin
      return res;
  }
  
- URLProtocol ff_mmsh_protocol = {
 +static int64_t mmsh_read_seek(URLContext *h, int stream_index,
 +                        int64_t timestamp, int flags)
 +{
 +    MMSHContext *mmsh_old = h->priv_data;
 +    MMSHContext *mmsh     = av_mallocz(sizeof(*mmsh));
 +    int ret;
 +
 +    if (!mmsh)
 +        return AVERROR(ENOMEM);
 +
 +    h->priv_data = mmsh;
 +    ret= mmsh_open_internal(h, mmsh_old->location, 0, FFMAX(timestamp, 0), 0);
 +    if(ret>=0){
 +        h->priv_data = mmsh_old;
 +        mmsh_close(h);
 +        h->priv_data = mmsh;
 +        av_free(mmsh_old);
 +        mmsh->mms.asf_header_read_size = mmsh->mms.asf_header_size;
 +    }else {
 +        h->priv_data = mmsh_old;
 +        av_free(mmsh);
 +    }
 +
 +    return ret;
 +}
 +
 +static int64_t mmsh_seek(URLContext *h, int64_t pos, int whence)
 +{
 +    MMSHContext *mmsh = h->priv_data;
 +    MMSContext *mms   = &mmsh->mms;
 +
 +    if(pos == 0 && whence == SEEK_CUR)
 +        return mms->asf_header_read_size + mms->remaining_in_len + mmsh->chunk_seq * (int64_t)mms->asf_packet_len;
 +    return AVERROR(ENOSYS);
 +}
 +
+ const URLProtocol ff_mmsh_protocol = {
      .name           = "mmsh",
      .url_open       = mmsh_open,
      .url_read       = mmsh_read,
Simple merge
index 0000000,a97aa10..269a63b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,164 +1,208 @@@
 - * This file is part of Libav.
+ /*
 - * Libav is free software; you can redistribute it and/or
++ * This file is part of FFmpeg.
+  *
 - * Libav is distributed in the hope that it will be useful,
++ * 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.
+  *
 - * License along with Libav; if not, write to the Free Software
++ * 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 "config.h"
+ #include "url.h"
++extern const URLProtocol ff_async_protocol;
++extern const URLProtocol ff_bluray_protocol;
++extern const URLProtocol ff_cache_protocol;
+ extern const URLProtocol ff_concat_protocol;
+ extern const URLProtocol ff_crypto_protocol;
++extern const URLProtocol ff_data_protocol;
+ extern const URLProtocol ff_ffrtmpcrypt_protocol;
+ extern const URLProtocol ff_ffrtmphttp_protocol;
+ extern const URLProtocol ff_file_protocol;
++extern const URLProtocol ff_ftp_protocol;
+ extern const URLProtocol ff_gopher_protocol;
+ extern const URLProtocol ff_hls_protocol;
+ extern const URLProtocol ff_http_protocol;
+ extern const URLProtocol ff_httpproxy_protocol;
+ extern const URLProtocol ff_https_protocol;
+ extern const URLProtocol ff_icecast_protocol;
+ extern const URLProtocol ff_mmsh_protocol;
+ extern const URLProtocol ff_mmst_protocol;
+ extern const URLProtocol ff_md5_protocol;
+ extern const URLProtocol ff_pipe_protocol;
+ extern const URLProtocol ff_rtmp_protocol;
+ extern const URLProtocol ff_rtmpe_protocol;
+ extern const URLProtocol ff_rtmps_protocol;
+ extern const URLProtocol ff_rtmpt_protocol;
+ extern const URLProtocol ff_rtmpte_protocol;
+ extern const URLProtocol ff_rtmpts_protocol;
+ extern const URLProtocol ff_rtp_protocol;
+ extern const URLProtocol ff_sctp_protocol;
+ extern const URLProtocol ff_srtp_protocol;
++extern const URLProtocol ff_subfile_protocol;
+ extern const URLProtocol ff_tcp_protocol;
+ extern const URLProtocol ff_tls_gnutls_protocol;
++extern const URLProtocol ff_tls_schannel_protocol;
++extern const URLProtocol ff_tls_securetransport_protocol;
+ extern const URLProtocol ff_tls_openssl_protocol;
+ extern const URLProtocol ff_udp_protocol;
++extern const URLProtocol ff_udplite_protocol;
+ extern const URLProtocol ff_unix_protocol;
+ extern const URLProtocol ff_librtmp_protocol;
+ extern const URLProtocol ff_librtmpe_protocol;
+ extern const URLProtocol ff_librtmps_protocol;
+ extern const URLProtocol ff_librtmpt_protocol;
+ extern const URLProtocol ff_librtmpte_protocol;
++extern const URLProtocol ff_libssh_protocol;
++extern const URLProtocol ff_libsmbclient_protocol;
+ const URLProtocol *ff_url_protocols[] = {
++#if CONFIG_ASYNC_PROTOCOL
++    &ff_async_protocol,
++#endif
++#if CONFIG_BLURAY_PROTOCOL
++    &ff_bluray_protocol,
++#endif
++#if CONFIG_CACHE_PROTOCOL
++    &ff_cache_protocol,
++#endif
+ #if CONFIG_CONCAT_PROTOCOL
+     &ff_concat_protocol,
+ #endif
+ #if CONFIG_CRYPTO_PROTOCOL
+     &ff_crypto_protocol,
+ #endif
++#if CONFIG_DATA_PROTOCOL
++    &ff_data_protocol,
++#endif
+ #if CONFIG_FFRTMPCRYPT_PROTOCOL
+     &ff_ffrtmpcrypt_protocol,
+ #endif
+ #if CONFIG_FFRTMPHTTP_PROTOCOL
+     &ff_ffrtmphttp_protocol,
+ #endif
+ #if CONFIG_FILE_PROTOCOL
+     &ff_file_protocol,
+ #endif
++#if CONFIG_FTP_PROTOCOL
++    &ff_ftp_protocol,
++#endif
+ #if CONFIG_GOPHER_PROTOCOL
+     &ff_gopher_protocol,
+ #endif
+ #if CONFIG_HLS_PROTOCOL
+     &ff_hls_protocol,
+ #endif
+ #if CONFIG_HTTP_PROTOCOL
+     &ff_http_protocol,
+ #endif
+ #if CONFIG_HTTPPROXY_PROTOCOL
+     &ff_httpproxy_protocol,
+ #endif
+ #if CONFIG_HTTPS_PROTOCOL
+     &ff_https_protocol,
+ #endif
+ #if CONFIG_ICECAST_PROTOCOL
+     &ff_icecast_protocol,
+ #endif
+ #if CONFIG_MMSH_PROTOCOL
+     &ff_mmsh_protocol,
+ #endif
+ #if CONFIG_MMST_PROTOCOL
+     &ff_mmst_protocol,
+ #endif
+ #if CONFIG_MD5_PROTOCOL
+     &ff_md5_protocol,
+ #endif
+ #if CONFIG_PIPE_PROTOCOL
+     &ff_pipe_protocol,
+ #endif
+ #if CONFIG_RTMP_PROTOCOL
+     &ff_rtmp_protocol,
+ #endif
+ #if CONFIG_RTMPE_PROTOCOL
+     &ff_rtmpe_protocol,
+ #endif
+ #if CONFIG_RTMPS_PROTOCOL
+     &ff_rtmps_protocol,
+ #endif
+ #if CONFIG_RTMPT_PROTOCOL
+     &ff_rtmpt_protocol,
+ #endif
+ #if CONFIG_RTMPTE_PROTOCOL
+     &ff_rtmpte_protocol,
+ #endif
+ #if CONFIG_RTMPTS_PROTOCOL
+     &ff_rtmpts_protocol,
+ #endif
+ #if CONFIG_RTP_PROTOCOL
+     &ff_rtp_protocol,
+ #endif
+ #if CONFIG_SCTP_PROTOCOL
+     &ff_sctp_protocol,
+ #endif
+ #if CONFIG_SRTP_PROTOCOL
+     &ff_srtp_protocol,
+ #endif
++#if CONFIG_SUBFILE_PROTOCOL
++    &ff_subfile_protocol,
++#endif
+ #if CONFIG_TCP_PROTOCOL
+     &ff_tcp_protocol,
+ #endif
+ #if CONFIG_TLS_GNUTLS_PROTOCOL
+     &ff_tls_gnutls_protocol,
+ #endif
++#if CONFIG_TLS_SCHANNEL_PROTOCOL
++    &ff_tls_schannel_protocol,
++#endif
++#if CONFIG_TLS_SECURETRANSPORT_PROTOCOL
++    &ff_tls_securetransport_protocol,
++#endif
+ #if CONFIG_TLS_OPENSSL_PROTOCOL
+     &ff_tls_openssl_protocol,
+ #endif
+ #if CONFIG_UDP_PROTOCOL
+     &ff_udp_protocol,
+ #endif
++#if CONFIG_UDPLITE_PROTOCOL
++    &ff_udplite_protocol,
++#endif
+ #if CONFIG_UNIX_PROTOCOL
+     &ff_unix_protocol,
+ #endif
+     /* external libraries */
+ #if CONFIG_LIBRTMP_PROTOCOL
+     &ff_librtmp_protocol,
+ #endif
+ #if CONFIG_LIBRTMPE_PROTOCOL
+     &ff_librtmpe_protocol,
+ #endif
+ #if CONFIG_LIBRTMPS_PROTOCOL
+     &ff_librtmps_protocol,
+ #endif
+ #if CONFIG_LIBRTMPT_PROTOCOL
+     &ff_librtmpt_protocol,
+ #endif
+ #if CONFIG_LIBRTMPTE_PROTOCOL
+     &ff_librtmpte_protocol,
+ #endif
++#if CONFIG_LIBSSH_PROTOCOL
++    &ff_libssh_protocol,
++#endif
++#if CONFIG_LIBSMBCLIENT_PROTOCOL
++    &ff_libsmbclient_protocol,
++#endif
+     NULL,
+ };
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 2b53438,0000000..8a3c167
mode 100644,000000..100644
--- /dev/null
@@@ -1,149 -1,0 +1,149 @@@
- URLProtocol ff_subfile_protocol = {
 +/*
 + * 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);
 +    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",
 +};
@@@ -255,10 -208,9 +255,10 @@@ static int tcp_get_file_handle(URLConte
      return s->fd;
  }
  
- URLProtocol ff_tcp_protocol = {
const URLProtocol ff_tcp_protocol = {
      .name                = "tcp",
      .url_open            = tcp_open,
 +    .url_accept          = tcp_accept,
      .url_read            = tcp_read,
      .url_write           = tcp_write,
      .url_close           = tcp_close,
Simple merge
Simple merge
index 85c01a0,0000000..c11b7d4
mode 100644,000000..100644
--- /dev/null
@@@ -1,601 -1,0 +1,601 @@@
- URLProtocol ff_tls_schannel_protocol = {
 +/*
 + * Copyright (c) 2015 Hendrik Leppkes
 + *
 + * 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 the CURL SChannel module */
 +
 +#include "avformat.h"
 +#include "internal.h"
 +#include "network.h"
 +#include "os_support.h"
 +#include "url.h"
 +#include "tls.h"
 +
 +#define SECURITY_WIN32
 +#include <windows.h>
 +#include <security.h>
 +#include <schnlsp.h>
 +
 +#define SCHANNEL_INITIAL_BUFFER_SIZE   4096
 +#define SCHANNEL_FREE_BUFFER_SIZE      1024
 +
 +/* mingw does not define this symbol */
 +#ifndef SECBUFFER_ALERT
 +#define SECBUFFER_ALERT                17
 +#endif
 +
 +typedef struct TLSContext {
 +    const AVClass *class;
 +    TLSShared tls_shared;
 +
 +    CredHandle cred_handle;
 +    TimeStamp cred_timestamp;
 +
 +    CtxtHandle ctxt_handle;
 +    TimeStamp ctxt_timestamp;
 +
 +    ULONG request_flags;
 +    ULONG context_flags;
 +
 +    uint8_t *enc_buf;
 +    int enc_buf_size;
 +    int enc_buf_offset;
 +
 +    uint8_t *dec_buf;
 +    int dec_buf_size;
 +    int dec_buf_offset;
 +
 +    SecPkgContext_StreamSizes sizes;
 +
 +    int connected;
 +    int connection_closed;
 +    int sspi_close_notify;
 +} TLSContext;
 +
 +static void init_sec_buffer(SecBuffer *buffer, unsigned long type,
 +                            void *data, unsigned long size)
 +{
 +    buffer->cbBuffer   = size;
 +    buffer->BufferType = type;
 +    buffer->pvBuffer   = data;
 +}
 +
 +static void init_sec_buffer_desc(SecBufferDesc *desc, SecBuffer *buffers,
 +                                 unsigned long buffer_count)
 +{
 +    desc->ulVersion = SECBUFFER_VERSION;
 +    desc->pBuffers = buffers;
 +    desc->cBuffers = buffer_count;
 +}
 +
 +static int tls_shutdown_client(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    int ret;
 +
 +    if (c->connected) {
 +        SecBufferDesc BuffDesc;
 +        SecBuffer Buffer;
 +        SECURITY_STATUS sspi_ret;
 +        SecBuffer outbuf;
 +        SecBufferDesc outbuf_desc;
 +
 +        DWORD dwshut = SCHANNEL_SHUTDOWN;
 +        init_sec_buffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
 +        init_sec_buffer_desc(&BuffDesc, &Buffer, 1);
 +
 +        sspi_ret = ApplyControlToken(&c->ctxt_handle, &BuffDesc);
 +        if (sspi_ret != SEC_E_OK)
 +            av_log(h, AV_LOG_ERROR, "ApplyControlToken failed\n");
 +
 +        init_sec_buffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer_desc(&outbuf_desc, &outbuf, 1);
 +
 +        sspi_ret = InitializeSecurityContext(&c->cred_handle, &c->ctxt_handle, s->host,
 +                                             c->request_flags, 0, 0, NULL, 0, &c->ctxt_handle,
 +                                             &outbuf_desc, &c->context_flags, &c->ctxt_timestamp);
 +        if (sspi_ret == SEC_E_OK || sspi_ret == SEC_I_CONTEXT_EXPIRED) {
 +            ret = ffurl_write(s->tcp, outbuf.pvBuffer, outbuf.cbBuffer);
 +            FreeContextBuffer(outbuf.pvBuffer);
 +            if (ret < 0 || ret != outbuf.cbBuffer)
 +                av_log(h, AV_LOG_ERROR, "Failed to send close message\n");
 +        }
 +
 +        c->connected = 0;
 +    }
 +    return 0;
 +}
 +
 +static int tls_close(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +
 +    tls_shutdown_client(h);
 +
 +    DeleteSecurityContext(&c->ctxt_handle);
 +    FreeCredentialsHandle(&c->cred_handle);
 +
 +    av_freep(&c->enc_buf);
 +    c->enc_buf_size = c->enc_buf_offset = 0;
 +
 +    av_freep(&c->dec_buf);
 +    c->dec_buf_size = c->dec_buf_offset = 0;
 +
 +    if (c->tls_shared.tcp)
 +        ffurl_close(c->tls_shared.tcp);
 +    return 0;
 +}
 +
 +static int tls_client_handshake_loop(URLContext *h, int initial)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    SECURITY_STATUS sspi_ret;
 +    SecBuffer outbuf[3];
 +    SecBufferDesc outbuf_desc;
 +    SecBuffer inbuf[2];
 +    SecBufferDesc inbuf_desc;
 +    int i, ret = 0, read_data = initial;
 +
 +    if (c->enc_buf == NULL) {
 +        c->enc_buf_offset = 0;
 +        ret = av_reallocp(&c->enc_buf, SCHANNEL_INITIAL_BUFFER_SIZE);
 +        if (ret < 0)
 +            goto fail;
 +        c->enc_buf_size = SCHANNEL_INITIAL_BUFFER_SIZE;
 +    }
 +
 +    if (c->dec_buf == NULL) {
 +        c->dec_buf_offset = 0;
 +        ret = av_reallocp(&c->dec_buf, SCHANNEL_INITIAL_BUFFER_SIZE);
 +        if (ret < 0)
 +            goto fail;
 +        c->dec_buf_size = SCHANNEL_INITIAL_BUFFER_SIZE;
 +    }
 +
 +    while (1) {
 +        if (c->enc_buf_size - c->enc_buf_offset < SCHANNEL_FREE_BUFFER_SIZE) {
 +            c->enc_buf_size = c->enc_buf_offset + SCHANNEL_FREE_BUFFER_SIZE;
 +            ret = av_reallocp(&c->enc_buf, c->enc_buf_size);
 +            if (ret < 0) {
 +                c->enc_buf_size = c->enc_buf_offset = 0;
 +                goto fail;
 +            }
 +        }
 +
 +        if (read_data) {
 +            ret = ffurl_read(c->tls_shared.tcp, c->enc_buf + c->enc_buf_offset,
 +                             c->enc_buf_size - c->enc_buf_offset);
 +            if (ret < 0) {
 +                av_log(h, AV_LOG_ERROR, "Failed to read handshake response\n");
 +                goto fail;
 +            }
 +            c->enc_buf_offset += ret;
 +        }
 +
 +        /* input buffers */
 +        init_sec_buffer(&inbuf[0], SECBUFFER_TOKEN, av_malloc(c->enc_buf_offset), c->enc_buf_offset);
 +        init_sec_buffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer_desc(&inbuf_desc, inbuf, 2);
 +
 +        if (inbuf[0].pvBuffer == NULL) {
 +            av_log(h, AV_LOG_ERROR, "Failed to allocate input buffer\n");
 +            ret = AVERROR(ENOMEM);
 +            goto fail;
 +        }
 +
 +        memcpy(inbuf[0].pvBuffer, c->enc_buf, c->enc_buf_offset);
 +
 +        /* output buffers */
 +        init_sec_buffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
 +        init_sec_buffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
 +        init_sec_buffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer_desc(&outbuf_desc, outbuf, 3);
 +
 +        sspi_ret = InitializeSecurityContext(&c->cred_handle, &c->ctxt_handle, s->host, c->request_flags,
 +                                             0, 0, &inbuf_desc, 0, NULL, &outbuf_desc, &c->context_flags,
 +                                             &c->ctxt_timestamp);
 +        av_freep(&inbuf[0].pvBuffer);
 +
 +        if (sspi_ret == SEC_E_INCOMPLETE_MESSAGE) {
 +            av_log(h, AV_LOG_DEBUG, "Received incomplete handshake, need more data\n");
 +            read_data = 1;
 +            continue;
 +        }
 +
 +        /* remote requests a client certificate - attempt to continue without one anyway */
 +        if (sspi_ret == SEC_I_INCOMPLETE_CREDENTIALS &&
 +            !(c->request_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
 +            av_log(h, AV_LOG_VERBOSE, "Client certificate has been requested, ignoring\n");
 +            c->request_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
 +            read_data = 0;
 +            continue;
 +        }
 +
 +        /* continue handshake */
 +        if (sspi_ret == SEC_I_CONTINUE_NEEDED || sspi_ret == SEC_E_OK) {
 +            for (i = 0; i < 3; i++) {
 +                if (outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
 +                    ret = ffurl_write(c->tls_shared.tcp, outbuf[i].pvBuffer, outbuf[i].cbBuffer);
 +                    if (ret < 0 || ret != outbuf[i].cbBuffer) {
 +                        av_log(h, AV_LOG_VERBOSE, "Failed to send handshake data\n");
 +                        ret = AVERROR(EIO);
 +                        goto fail;
 +                    }
 +                }
 +
 +                if (outbuf[i].pvBuffer != NULL) {
 +                    FreeContextBuffer(outbuf[i].pvBuffer);
 +                    outbuf[i].pvBuffer = NULL;
 +                }
 +            }
 +        } else {
 +            if (sspi_ret == SEC_E_WRONG_PRINCIPAL)
 +                av_log(h, AV_LOG_ERROR, "SNI or certificate check failed\n");
 +            else
 +                av_log(h, AV_LOG_ERROR, "Creating security context failed (0x%lx)\n", sspi_ret);
 +            ret = AVERROR_UNKNOWN;
 +            goto fail;
 +        }
 +
 +        if (inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
 +            if (c->enc_buf_offset > inbuf[1].cbBuffer) {
 +                memmove(c->enc_buf, (c->enc_buf + c->enc_buf_offset) - inbuf[1].cbBuffer,
 +                        inbuf[1].cbBuffer);
 +                c->enc_buf_offset = inbuf[1].cbBuffer;
 +                if (sspi_ret == SEC_I_CONTINUE_NEEDED) {
 +                    read_data = 0;
 +                    continue;
 +                }
 +            }
 +        } else {
 +            c->enc_buf_offset  = 0;
 +        }
 +
 +        if (sspi_ret == SEC_I_CONTINUE_NEEDED) {
 +            read_data = 1;
 +            continue;
 +        }
 +
 +        break;
 +    }
 +
 +    return 0;
 +
 +fail:
 +    /* free any remaining output data */
 +    for (i = 0; i < 3; i++) {
 +        if (outbuf[i].pvBuffer != NULL) {
 +            FreeContextBuffer(outbuf[i].pvBuffer);
 +            outbuf[i].pvBuffer = NULL;
 +        }
 +    }
 +
 +    return ret;
 +}
 +
 +static int tls_client_handshake(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    SecBuffer outbuf;
 +    SecBufferDesc outbuf_desc;
 +    SECURITY_STATUS sspi_ret;
 +    int ret;
 +
 +    init_sec_buffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
 +    init_sec_buffer_desc(&outbuf_desc, &outbuf, 1);
 +
 +    c->request_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
 +                       ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
 +                       ISC_REQ_STREAM;
 +
 +    sspi_ret = InitializeSecurityContext(&c->cred_handle, NULL, s->host, c->request_flags, 0, 0,
 +                                         NULL, 0, &c->ctxt_handle, &outbuf_desc, &c->context_flags,
 +                                         &c->ctxt_timestamp);
 +    if (sspi_ret != SEC_I_CONTINUE_NEEDED) {
 +        av_log(h, AV_LOG_ERROR, "Unable to create initial security context (0x%lx)\n", sspi_ret);
 +        ret = AVERROR_UNKNOWN;
 +        goto fail;
 +    }
 +
 +    ret = ffurl_write(s->tcp, outbuf.pvBuffer, outbuf.cbBuffer);
 +    FreeContextBuffer(outbuf.pvBuffer);
 +    if (ret < 0 || ret != outbuf.cbBuffer) {
 +        av_log(h, AV_LOG_ERROR, "Failed to send initial handshake data\n");
 +        ret = AVERROR(EIO);
 +        goto fail;
 +    }
 +
 +    return tls_client_handshake_loop(h, 1);
 +
 +fail:
 +    DeleteSecurityContext(&c->ctxt_handle);
 +    return ret;
 +}
 +
 +static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    SECURITY_STATUS sspi_ret;
 +    SCHANNEL_CRED schannel_cred = { 0 };
 +    int ret;
 +
 +    if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
 +        goto fail;
 +
 +    if (s->listen) {
 +        av_log(h, AV_LOG_ERROR, "TLS Listen Sockets with SChannel is not implemented.\n");
 +        ret = AVERROR(EINVAL);
 +        goto fail;
 +    }
 +
 +    /* SChannel Options */
 +    schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
 +
 +    if (s->verify)
 +        schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION |
 +                                SCH_CRED_REVOCATION_CHECK_CHAIN;
 +    else
 +        schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
 +                                SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
 +                                SCH_CRED_IGNORE_REVOCATION_OFFLINE;
 +
 +    /* Get credential handle */
 +    sspi_ret = AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME, SECPKG_CRED_OUTBOUND,
 +                                        NULL,  &schannel_cred, NULL, NULL, &c->cred_handle,
 +                                        &c->cred_timestamp);
 +    if (sspi_ret != SEC_E_OK) {
 +        av_log(h, AV_LOG_ERROR, "Unable to acquire security credentials (0x%lx)\n", sspi_ret);
 +        ret = AVERROR_UNKNOWN;
 +        goto fail;
 +    }
 +
 +    ret = tls_client_handshake(h);
 +    if (ret < 0)
 +        goto fail;
 +
 +    c->connected = 1;
 +
 +    return 0;
 +
 +fail:
 +    tls_close(h);
 +    return ret;
 +}
 +
 +static int tls_read(URLContext *h, uint8_t *buf, int len)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    SECURITY_STATUS sspi_ret = SEC_E_OK;
 +    SecBuffer inbuf[4];
 +    SecBufferDesc inbuf_desc;
 +    int size, ret;
 +    int min_enc_buf_size = len + SCHANNEL_FREE_BUFFER_SIZE;
 +
 +    if (len <= c->dec_buf_offset)
 +        goto cleanup;
 +
 +    if (c->sspi_close_notify)
 +        goto cleanup;
 +
 +    if (!c->connection_closed) {
 +        size = c->enc_buf_size - c->enc_buf_offset;
 +        if (size < SCHANNEL_FREE_BUFFER_SIZE || c->enc_buf_size < min_enc_buf_size) {
 +            c->enc_buf_size = c->enc_buf_offset + SCHANNEL_FREE_BUFFER_SIZE;
 +            if (c->enc_buf_size < min_enc_buf_size)
 +                c->enc_buf_size = min_enc_buf_size;
 +            ret = av_reallocp(&c->enc_buf, c->enc_buf_size);
 +            if (ret < 0) {
 +                c->enc_buf_size = c->enc_buf_offset = 0;
 +                return ret;
 +            }
 +        }
 +
 +        ret = ffurl_read(s->tcp, c->enc_buf + c->enc_buf_offset,
 +                         c->enc_buf_size - c->enc_buf_offset);
 +        if (ret < 0) {
 +            av_log(h, AV_LOG_ERROR, "Unable to read from socket\n");
 +            return ret;
 +        } else if (ret == 0)
 +            c->connection_closed = 1;
 +
 +        c->enc_buf_offset += ret;
 +    }
 +
 +    while (c->enc_buf_offset > 0 && sspi_ret == SEC_E_OK && c->dec_buf_offset < len) {
 +        /*  input buffer */
 +        init_sec_buffer(&inbuf[0], SECBUFFER_DATA, c->enc_buf, c->enc_buf_offset);
 +
 +        /* additional buffers for possible output */
 +        init_sec_buffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
 +        init_sec_buffer_desc(&inbuf_desc, inbuf, 4);
 +
 +        sspi_ret = DecryptMessage(&c->ctxt_handle, &inbuf_desc, 0, NULL);
 +        if (sspi_ret == SEC_E_OK || sspi_ret == SEC_I_RENEGOTIATE ||
 +            sspi_ret == SEC_I_CONTEXT_EXPIRED) {
 +            /* handle decrypted data */
 +            if (inbuf[1].BufferType == SECBUFFER_DATA) {
 +                /* grow buffer if needed */
 +                size = inbuf[1].cbBuffer > SCHANNEL_FREE_BUFFER_SIZE ?
 +                       inbuf[1].cbBuffer : SCHANNEL_FREE_BUFFER_SIZE;
 +                if (c->dec_buf_size - c->dec_buf_offset < size || c->dec_buf_size < len)  {
 +                    c->dec_buf_size = c->dec_buf_offset + size;
 +                    if (c->dec_buf_size < len)
 +                        c->dec_buf_size = len;
 +                    ret = av_reallocp(&c->dec_buf, c->dec_buf_size);
 +                    if (ret < 0) {
 +                        c->dec_buf_size = c->dec_buf_offset = 0;
 +                        return ret;
 +                    }
 +                }
 +
 +                /* copy decrypted data to buffer */
 +                size = inbuf[1].cbBuffer;
 +                if (size) {
 +                    memcpy(c->dec_buf + c->dec_buf_offset, inbuf[1].pvBuffer, size);
 +                    c->dec_buf_offset += size;
 +                }
 +            }
 +            if (inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
 +                if (c->enc_buf_offset > inbuf[3].cbBuffer) {
 +                    memmove(c->enc_buf, (c->enc_buf + c->enc_buf_offset) - inbuf[3].cbBuffer,
 +                    inbuf[3].cbBuffer);
 +                    c->enc_buf_offset = inbuf[3].cbBuffer;
 +                }
 +            } else
 +                c->enc_buf_offset = 0;
 +
 +            if (sspi_ret == SEC_I_RENEGOTIATE) {
 +                if (c->enc_buf_offset) {
 +                    av_log(h, AV_LOG_ERROR, "Cannot renegotiate, encrypted data buffer not empty\n");
 +                    ret = AVERROR_UNKNOWN;
 +                    goto cleanup;
 +                }
 +
 +                av_log(h, AV_LOG_VERBOSE, "Re-negotiating security context\n");
 +                ret = tls_client_handshake_loop(h, 0);
 +                if (ret < 0) {
 +                    goto cleanup;
 +                }
 +                sspi_ret = SEC_E_OK;
 +                continue;
 +            } else if (sspi_ret == SEC_I_CONTEXT_EXPIRED) {
 +                c->sspi_close_notify = 1;
 +                if (!c->connection_closed) {
 +                    c->connection_closed = 1;
 +                    av_log(h, AV_LOG_VERBOSE, "Server closed the connection\n");
 +                }
 +                ret = 0;
 +                goto cleanup;
 +            }
 +        } else if (sspi_ret == SEC_E_INCOMPLETE_MESSAGE) {
 +            ret = AVERROR(EAGAIN);
 +            goto cleanup;
 +        } else {
 +            av_log(h, AV_LOG_ERROR, "Unable to decrypt message\n");
 +            ret = AVERROR(EIO);
 +            goto cleanup;
 +        }
 +    }
 +
 +    ret = 0;
 +
 +cleanup:
 +    size = FFMIN(len, c->dec_buf_offset);
 +    if (size) {
 +        memcpy(buf, c->dec_buf, size);
 +        memmove(c->dec_buf, c->dec_buf + size, c->dec_buf_offset - size);
 +        c->dec_buf_offset -= size;
 +
 +        return size;
 +    }
 +
 +    if (ret == 0 && !c->connection_closed)
 +        ret = AVERROR(EAGAIN);
 +
 +    return ret < 0 ? ret : 0;
 +}
 +
 +static int tls_write(URLContext *h, const uint8_t *buf, int len)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    SECURITY_STATUS sspi_ret;
 +    int ret = 0, data_size;
 +    uint8_t *data = NULL;
 +    SecBuffer outbuf[4];
 +    SecBufferDesc outbuf_desc;
 +
 +    if (c->sizes.cbMaximumMessage == 0) {
 +        sspi_ret = QueryContextAttributes(&c->ctxt_handle, SECPKG_ATTR_STREAM_SIZES, &c->sizes);
 +        if (sspi_ret != SEC_E_OK)
 +            return AVERROR_UNKNOWN;
 +    }
 +
 +    /* limit how much data we can consume */
 +    len = FFMIN(len, c->sizes.cbMaximumMessage);
 +
 +    data_size = c->sizes.cbHeader + len + c->sizes.cbTrailer;
 +    data = av_malloc(data_size);
 +    if (data == NULL)
 +        return AVERROR(ENOMEM);
 +
 +    init_sec_buffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
 +                  data, c->sizes.cbHeader);
 +    init_sec_buffer(&outbuf[1], SECBUFFER_DATA,
 +                  data + c->sizes.cbHeader, len);
 +    init_sec_buffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
 +                  data + c->sizes.cbHeader + len,
 +                  c->sizes.cbTrailer);
 +    init_sec_buffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
 +    init_sec_buffer_desc(&outbuf_desc, outbuf, 4);
 +
 +    memcpy(outbuf[1].pvBuffer, buf, len);
 +
 +    sspi_ret = EncryptMessage(&c->ctxt_handle, 0, &outbuf_desc, 0);
 +    if (sspi_ret == SEC_E_OK)  {
 +        len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
 +        ret = ffurl_write(s->tcp, data, len);
 +        if (ret < 0 || ret != len) {
 +            ret = AVERROR(EIO);
 +            av_log(h, AV_LOG_ERROR, "Writing encrypted data to socket failed\n");
 +            goto done;
 +        }
 +    } else {
 +        av_log(h, AV_LOG_ERROR, "Encrypting data failed\n");
 +        if (sspi_ret == SEC_E_INSUFFICIENT_MEMORY)
 +            ret = AVERROR(ENOMEM);
 +        else
 +            ret = AVERROR(EIO);
 +        goto done;
 +    }
 +
 +done:
 +    av_freep(&data);
 +    return ret < 0 ? ret : outbuf[1].cbBuffer;
 +}
 +
 +static const AVOption options[] = {
 +    TLS_COMMON_OPTIONS(TLSContext, tls_shared),
 +    { NULL }
 +};
 +
 +static const AVClass tls_class = {
 +    .class_name = "tls",
 +    .item_name  = av_default_item_name,
 +    .option     = options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
++const URLProtocol ff_tls_schannel_protocol = {
 +    .name           = "tls",
 +    .url_open2      = tls_open,
 +    .url_read       = tls_read,
 +    .url_write      = tls_write,
 +    .url_close      = tls_close,
 +    .priv_data_size = sizeof(TLSContext),
 +    .flags          = URL_PROTOCOL_FLAG_NETWORK,
 +    .priv_data_class = &tls_class,
 +};
index 482771a,0000000..a26b969
mode 100644,000000..100644
--- /dev/null
@@@ -1,399 -1,0 +1,399 @@@
- URLProtocol ff_tls_securetransport_protocol = {
 +/*
 + * Copyright (c) 2015 Rodger Combs
 + *
 + * 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 <errno.h>
 +
 +
 +#include "avformat.h"
 +#include "avio_internal.h"
 +#include "internal.h"
 +#include "network.h"
 +#include "os_support.h"
 +#include "url.h"
 +#include "tls.h"
 +#include "libavcodec/internal.h"
 +#include "libavutil/avstring.h"
 +#include "libavutil/opt.h"
 +#include "libavutil/parseutils.h"
 +
 +#include <Security/Security.h>
 +#include <Security/SecureTransport.h>
 +#include <CoreFoundation/CoreFoundation.h>
 +
 +// We use a private API call here; it's good enough for WebKit.
 +SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);
 +#define ioErr -36
 +
 +typedef struct TLSContext {
 +    const AVClass *class;
 +    TLSShared tls_shared;
 +    SSLContextRef ssl_context;
 +    CFArrayRef ca_array;
 +    int lastErr;
 +} TLSContext;
 +
 +static int print_tls_error(URLContext *h, int ret)
 +{
 +    TLSContext *c = h->priv_data;
 +    switch (ret) {
 +    case errSSLWouldBlock:
 +        break;
 +    case errSSLXCertChainInvalid:
 +        av_log(h, AV_LOG_ERROR, "Invalid certificate chain\n");
 +        return AVERROR(EIO);
 +    case ioErr:
 +        return c->lastErr;
 +    default:
 +        av_log(h, AV_LOG_ERROR, "IO Error: %i\n", ret);
 +        return AVERROR(EIO);
 +    }
 +    return AVERROR(EIO);
 +}
 +
 +static int import_pem(URLContext *h, char *path, CFArrayRef *array)
 +{
 +    AVIOContext *s = NULL;
 +    CFDataRef data = NULL;
 +    int64_t ret = 0;
 +    char *buf = NULL;
 +    SecExternalFormat format = kSecFormatPEMSequence;
 +    SecExternalFormat type = kSecItemTypeAggregate;
 +    CFStringRef pathStr = CFStringCreateWithCString(NULL, path, 0x08000100);
 +    if (!pathStr) {
 +        ret = AVERROR(ENOMEM);
 +        goto end;
 +    }
 +
 +    if ((ret = ffio_open_whitelist(&s, path, AVIO_FLAG_READ,
 +                                   &h->interrupt_callback, NULL,
 +                                   h->protocol_whitelist)) < 0)
 +        goto end;
 +
 +    if ((ret = avio_size(s)) < 0)
 +        goto end;
 +
 +    if (ret == 0) {
 +        ret = AVERROR_INVALIDDATA;
 +        goto end;
 +    }
 +
 +    if (!(buf = av_malloc(ret))) {
 +        ret = AVERROR(ENOMEM);
 +        goto end;
 +    }
 +
 +    if ((ret = avio_read(s, buf, ret)) < 0)
 +        goto end;
 +
 +    data = CFDataCreate(kCFAllocatorDefault, buf, ret);
 +
 +    if (SecItemImport(data, pathStr, &format, &type,
 +                      0, NULL, NULL, array) != noErr || !array) {
 +        ret = AVERROR_UNKNOWN;
 +        goto end;
 +    }
 +
 +    if (CFArrayGetCount(*array) == 0) {
 +        ret = AVERROR_INVALIDDATA;
 +        goto end;
 +    }
 +
 +end:
 +    av_free(buf);
 +    if (pathStr)
 +        CFRelease(pathStr);
 +    if (data)
 +        CFRelease(data);
 +    if (s)
 +        avio_close(s);
 +    return ret;
 +}
 +
 +static int load_ca(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +    int ret = 0;
 +    CFArrayRef array = NULL;
 +
 +    if ((ret = import_pem(h, c->tls_shared.ca_file, &array)) < 0)
 +        goto end;
 +
 +    if (!(c->ca_array = CFRetain(array))) {
 +        ret = AVERROR(ENOMEM);
 +        goto end;
 +    }
 +
 +end:
 +    if (array)
 +        CFRelease(array);
 +    return ret;
 +}
 +
 +static int load_cert(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +    int ret = 0;
 +    CFArrayRef certArray = NULL;
 +    CFArrayRef keyArray = NULL;
 +    SecIdentityRef id = NULL;
 +    CFMutableArrayRef outArray = NULL;
 +
 +    if ((ret = import_pem(h, c->tls_shared.cert_file, &certArray)) < 0)
 +        goto end;
 +
 +    if ((ret = import_pem(h, c->tls_shared.key_file, &keyArray)) < 0)
 +        goto end;
 +
 +    if (!(id = SecIdentityCreate(kCFAllocatorDefault,
 +                                 (SecCertificateRef)CFArrayGetValueAtIndex(certArray, 0),
 +                                 (SecKeyRef)CFArrayGetValueAtIndex(keyArray, 0)))) {
 +        ret = AVERROR_UNKNOWN;
 +        goto end;
 +    }
 +
 +    if (!(outArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certArray))) {
 +        ret = AVERROR(ENOMEM);
 +        goto end;
 +    }
 +
 +    CFArraySetValueAtIndex(outArray, 0, id);
 +
 +    SSLSetCertificate(c->ssl_context, outArray);
 +
 +end:
 +    if (certArray)
 +        CFRelease(certArray);
 +    if (keyArray)
 +        CFRelease(keyArray);
 +    if (outArray)
 +        CFRelease(outArray);
 +    if (id)
 +        CFRelease(id);
 +    return ret;
 +}
 +
 +static OSStatus tls_read_cb(SSLConnectionRef connection, void *data, size_t *dataLength)
 +{
 +    URLContext *h = (URLContext*)connection;
 +    TLSContext *c = h->priv_data;
 +    int read = ffurl_read_complete(c->tls_shared.tcp, data, *dataLength);
 +    if (read <= 0) {
 +        *dataLength = 0;
 +        switch(AVUNERROR(read)) {
 +            case ENOENT:
 +            case 0:
 +                return errSSLClosedGraceful;
 +            case ECONNRESET:
 +                return errSSLClosedAbort;
 +            case EAGAIN:
 +                return errSSLWouldBlock;
 +            default:
 +                c->lastErr = read;
 +                return ioErr;
 +        }
 +    } else {
 +        *dataLength = read;
 +        return noErr;
 +    }
 +}
 +
 +static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size_t *dataLength)
 +{
 +    URLContext *h = (URLContext*)connection;
 +    TLSContext *c = h->priv_data;
 +    int written = ffurl_write(c->tls_shared.tcp, data, *dataLength);
 +    if (written <= 0) {
 +        *dataLength = 0;
 +        switch(AVUNERROR(written)) {
 +            case EAGAIN:
 +                return errSSLWouldBlock;
 +            default:
 +                c->lastErr = written;
 +                return ioErr;
 +        }
 +    } else {
 +        *dataLength = written;
 +        return noErr;
 +    }
 +}
 +
 +static int tls_close(URLContext *h)
 +{
 +    TLSContext *c = h->priv_data;
 +    if (c->ssl_context) {
 +        SSLClose(c->ssl_context);
 +        CFRelease(c->ssl_context);
 +    }
 +    if (c->ca_array)
 +        CFRelease(c->ca_array);
 +    if (c->tls_shared.tcp)
 +        ffurl_close(c->tls_shared.tcp);
 +    return 0;
 +}
 +
 +#define CHECK_ERROR(func, ...) do {                                     \
 +        OSStatus status = func(__VA_ARGS__);                            \
 +        if (status != noErr) {                                          \
 +            ret = AVERROR_UNKNOWN;                                      \
 +            av_log(h, AV_LOG_ERROR, #func ": Error %i\n", (int)status); \
 +            goto fail;                                                  \
 +        }                                                               \
 +    } while (0)
 +
 +static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
 +{
 +    TLSContext *c = h->priv_data;
 +    TLSShared *s = &c->tls_shared;
 +    int ret;
 +
 +    if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
 +        goto fail;
 +
 +    c->ssl_context = SSLCreateContext(NULL, s->listen ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
 +    if (!c->ssl_context) {
 +        av_log(h, AV_LOG_ERROR, "Unable to create SSL context\n");
 +        ret = AVERROR(ENOMEM);
 +        goto fail;
 +    }
 +    if (s->ca_file) {
 +        if ((ret = load_ca(h)) < 0)
 +            goto fail;
 +    }
 +    if (s->ca_file || !s->verify)
 +        CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
 +    if (s->cert_file)
 +        if ((ret = load_cert(h)) < 0)
 +            goto fail;
 +    CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, s->host, strlen(s->host));
 +    CHECK_ERROR(SSLSetIOFuncs, c->ssl_context, tls_read_cb, tls_write_cb);
 +    CHECK_ERROR(SSLSetConnection, c->ssl_context, h);
 +    while (1) {
 +        OSStatus status = SSLHandshake(c->ssl_context);
 +        if (status == errSSLServerAuthCompleted) {
 +            SecTrustRef peerTrust;
 +            SecTrustResultType trustResult;
 +            if (!s->verify)
 +                continue;
 +
 +            if (SSLCopyPeerTrust(c->ssl_context, &peerTrust) != noErr) {
 +                ret = AVERROR(ENOMEM);
 +                goto fail;
 +            }
 +
 +            if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr) {
 +                ret = AVERROR_UNKNOWN;
 +                goto fail;
 +            }
 +
 +            if (SecTrustEvaluate(peerTrust, &trustResult) != noErr) {
 +                ret = AVERROR_UNKNOWN;
 +                goto fail;
 +            }
 +
 +            if (trustResult == kSecTrustResultProceed ||
 +                trustResult == kSecTrustResultUnspecified) {
 +                // certificate is trusted
 +                status = errSSLWouldBlock; // so we call SSLHandshake again
 +            } else if (trustResult == kSecTrustResultRecoverableTrustFailure) {
 +                // not trusted, for some reason other than being expired
 +                status = errSSLXCertChainInvalid;
 +            } else {
 +                // cannot use this certificate (fatal)
 +                status = errSSLBadCert;
 +            }
 +
 +            if (peerTrust)
 +                CFRelease(peerTrust);
 +        }
 +        if (status == noErr)
 +            break;
 +
 +        av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session: %i\n", (int)status);
 +        ret = AVERROR(EIO);
 +        goto fail;
 +    }
 +
 +    return 0;
 +fail:
 +    tls_close(h);
 +    return ret;
 +}
 +
 +static int map_ssl_error(OSStatus status, size_t processed)
 +{
 +    switch (status) {
 +    case noErr:
 +        return processed;
 +    case errSSLClosedGraceful:
 +    case errSSLClosedNoNotify:
 +        return 0;
 +    default:
 +        return (int)status;
 +    }
 +}
 +
 +static int tls_read(URLContext *h, uint8_t *buf, int size)
 +{
 +    TLSContext *c = h->priv_data;
 +    size_t processed = 0;
 +    int ret = SSLRead(c->ssl_context, buf, size, &processed);
 +    ret = map_ssl_error(ret, processed);
 +    if (ret > 0)
 +        return ret;
 +    if (ret == 0)
 +        return AVERROR_EOF;
 +    return print_tls_error(h, ret);
 +}
 +
 +static int tls_write(URLContext *h, const uint8_t *buf, int size)
 +{
 +    TLSContext *c = h->priv_data;
 +    size_t processed = 0;
 +    int ret = SSLWrite(c->ssl_context, buf, size, &processed);
 +    ret = map_ssl_error(ret, processed);
 +    if (ret > 0)
 +        return ret;
 +    if (ret == 0)
 +        return AVERROR_EOF;
 +    return print_tls_error(h, ret);
 +}
 +
 +static const AVOption options[] = {
 +    TLS_COMMON_OPTIONS(TLSContext, tls_shared),
 +    { NULL }
 +};
 +
 +static const AVClass tls_class = {
 +    .class_name = "tls",
 +    .item_name  = av_default_item_name,
 +    .option     = options,
 +    .version    = LIBAVUTIL_VERSION_INT,
 +};
 +
++const URLProtocol ff_tls_securetransport_protocol = {
 +    .name           = "tls",
 +    .url_open2      = tls_open,
 +    .url_read       = tls_read,
 +    .url_write      = tls_write,
 +    .url_close      = tls_close,
 +    .priv_data_size = sizeof(TLSContext),
 +    .flags          = URL_PROTOCOL_FLAG_NETWORK,
 +    .priv_data_class = &tls_class,
 +};
@@@ -991,18 -693,6 +991,18 @@@ const URLProtocol ff_udp_protocol = 
      .url_close           = udp_close,
      .url_get_file_handle = udp_get_file_handle,
      .priv_data_size      = sizeof(UDPContext),
 -    .flags               = URL_PROTOCOL_FLAG_NETWORK,
      .priv_data_class     = &udp_class,
- URLProtocol ff_udplite_protocol = {
 +    .flags               = URL_PROTOCOL_FLAG_NETWORK,
 +};
 +
++const URLProtocol ff_udplite_protocol = {
 +    .name                = "udplite",
 +    .url_open            = udplite_open,
 +    .url_read            = udp_read,
 +    .url_write           = udp_write,
 +    .url_close           = udp_close,
 +    .url_get_file_handle = udp_get_file_handle,
 +    .priv_data_size      = sizeof(UDPContext),
 +    .priv_data_class     = &udplite_context_class,
 +    .flags               = URL_PROTOCOL_FLAG_NETWORK,
  };
Simple merge
@@@ -315,12 -264,6 +302,13 @@@ int ff_url_join(char *str, int size, co
  void ff_make_absolute_url(char *buf, int size, const char *base,
                            const char *rel);
  
 +/**
 + * Allocate directory entry with default values.
 + *
 + * @return entry or NULL on error
 + */
 +AVIODirEntry *ff_alloc_dir_entry(void);
 +
+ extern const URLProtocol *ff_url_protocols[];
  
  #endif /* AVFORMAT_URL_H */
@@@ -1,7 -1,3 +1,7 @@@
- FATE_LIBAVFORMAT-$(HAVE_PTHREADS) += fate-async
- fate-async: libavformat/async-test$(EXESUF)
- fate-async: CMD = run libavformat/async-test
++#FATE_LIBAVFORMAT-$(HAVE_PTHREADS) += fate-async
++#fate-async: libavformat/async-test$(EXESUF)
++#fate-async: CMD = run libavformat/async-test
 +
  FATE_LIBAVFORMAT-$(CONFIG_NETWORK) += fate-noproxy
  fate-noproxy: libavformat/noproxy-test$(EXESUF)
  fate-noproxy: CMD = run libavformat/noproxy-test