On Wed, 23 Jul 2014 09:45:31 +0200, [email protected] wrote:
> From: ePirat <[email protected]>
> 
> Fixes: Possible leak in cat_header
>        Unused #define D AV_OPT_FLAG_DECODING_PARAM
>        use av_freep() instead of av_free()
>        make byte compare arrays static const
> 
> ---
> 
> Icecast is basically a convenience wrapper around the HTTP protocol.
> 
> ---
>  Changelog                |   1 +
>  configure                |   1 +
>  doc/general.texi         |   1 +
>  doc/protocols.texi       |  40 +++++++++
>  libavformat/Makefile     |   1 +
>  libavformat/allformats.c |   1 +
>  libavformat/icecast.c    | 222 
> +++++++++++++++++++++++++++++++++++++++++++++++
>  libavformat/version.h    |   2 +-
>  8 files changed, 268 insertions(+), 1 deletion(-)
>  create mode 100644 libavformat/icecast.c
> 
> diff --git a/Changelog b/Changelog
> index bfdd8d1..edba289 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -28,6 +28,7 @@ version <next>:
>  - native Opus decoder
>  - display matrix export and rotation api
>  - drop avserver, it was unmaintained for years and largely broken
> +- Icecast protocol
>  
>  
>  version 10:
> diff --git a/configure b/configure
> index e401711..41e6244 100755
> --- a/configure
> +++ b/configure
> @@ -2086,6 +2086,7 @@ gopher_protocol_select="network"
>  http_protocol_select="tcp_protocol"
>  httpproxy_protocol_select="tcp_protocol"
>  https_protocol_select="tls_protocol"
> +icecast_protocol_select="http_protocol"
>  librtmp_protocol_deps="librtmp"
>  librtmpe_protocol_deps="librtmp"
>  librtmps_protocol_deps="librtmp"
> diff --git a/doc/general.texi b/doc/general.texi
> index 1fd5f36..ae59941 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -948,6 +948,7 @@ performance on systems without hardware floating point 
> support).
>  @item HLS          @tab X
>  @item HTTP         @tab X
>  @item HTTPS        @tab X
> +@item Icecast      @tab X
>  @item MMSH         @tab X
>  @item MMST         @tab X
>  @item pipe         @tab X
> diff --git a/doc/protocols.texi b/doc/protocols.texi
> index 1501dab..ec7d924 100644
> --- a/doc/protocols.texi
> +++ b/doc/protocols.texi
> @@ -138,6 +138,46 @@ Set initial byte offset.
>  Try to limit the request to bytes preceding this offset.
>  @end table
>  
> +@section Icecast
> +
> +Icecast (stream to Icecast servers)
> +
> +This protocol accepts the following options:
> +
> +@table @option
> +@item ice_genre
> +Set the stream genre.
> +
> +@item ice_name
> +Set the stream name.
> +
> +@item ice_description
> +Set the stream description.
> +
> +@item ice_url
> +Set the stream website URL.
> +
> +@item ice_public
> +Set if the stream should be public or not.
> +The default is 0 (not public).
> +
> +@item user_agent
> +Override the User-Agent header. If not specified a string of the form
> +"Lavf/<version>" will be used.
> +
> +@item password
> +Set the Icecast mountpoint password.
> +
> +@item content_type
> +Set the stream content type. This must be set if it is different from
> +audio/mpeg.
> +
> +@item legacy_icecast
> +This enables support for Icecast versions < 2.4.0, that do not support the
> +HTTP PUT method but the SOURCE method.
> +
> +@end table
> +
>  @section mmst
>  
>  MMS (Microsoft Media Server) protocol over TCP.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index c2d77b3..a048157 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -375,6 +375,7 @@ OBJS-$(CONFIG_HLS_PROTOCOL)              += hlsproto.o
>  OBJS-$(CONFIG_HTTP_PROTOCOL)             += http.o httpauth.o urldecode.o
>  OBJS-$(CONFIG_HTTPPROXY_PROTOCOL)        += http.o httpauth.o urldecode.o
>  OBJS-$(CONFIG_HTTPS_PROTOCOL)            += http.o httpauth.o urldecode.o
> +OBJS-$(CONFIG_ICECAST_PROTOCOL)          += icecast.o
>  OBJS-$(CONFIG_MMSH_PROTOCOL)             += mmsh.o mms.o asf.o
>  OBJS-$(CONFIG_MMST_PROTOCOL)             += mmst.o mms.o asf.o
>  OBJS-$(CONFIG_MD5_PROTOCOL)              += md5proto.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index 8083ef3..984bb52 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -270,6 +270,7 @@ void av_register_all(void)
>      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);
> diff --git a/libavformat/icecast.c b/libavformat/icecast.c
> new file mode 100644
> index 0000000..5c43bb8
> --- /dev/null
> +++ b/libavformat/icecast.c
> @@ -0,0 +1,222 @@
> +/*
> + * Icecast protocol for Libav
> + * Copyright (c) 2014 Marvin Scholz
> + *
> + * This file is part of Libav.
> + *
> + * Libav 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.
> + *
> + * Libav 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 Libav; 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/opt.h"
> +
> +#include "avformat.h"
> +#include "network.h"
> +
> +
> +typedef struct IcecastContext {
> +    const AVClass *class;
> +    URLContext *hd;
> +    char *content_type;
> +    char *description;
> +    char *genre;
> +    char *headers;
> +    int legacy_icecast;
> +    char *name;
> +    char *pass;
> +    int public;
> +    int send_started;
> +    char *url;
> +    char *user;
> +    char *user_agent;

It'd be clearer if the fields set by AVOptions were grouped together and
separated from the actual runtime state. Then one can immediately see what needs
to be freed on close and what is freed automatically by the options system.

> +} IcecastContext;
> +
> +#define DEFAULT_ICE_USER "source"
> +
> +#define NOT_EMPTY(s) (s && s[0] != '\0')

nit: we usually write just "s[0]" instead of "s[0] != '\0'"

> +
> +#define OFFSET(x) offsetof(IcecastContext, x)
> +#define E AV_OPT_FLAG_ENCODING_PARAM
> +
> +static const AVOption options[] = {
> +    { "ice_genre", "set stream genre", OFFSET(genre), AV_OPT_TYPE_STRING, { 
> 0 }, 0, 0, E },
> +    { "ice_name", "set stream description", OFFSET(name), 
> AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
> +    { "ice_description", "set stream description", OFFSET(description), 
> AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
> +    { "ice_url", "set stream website", OFFSET(url), AV_OPT_TYPE_STRING, { 0 
> }, 0, 0, E },
> +    { "ice_public", "set if stream is public", OFFSET(public), 
> AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
> +    { "user_agent", "override User-Agent header", OFFSET(user_agent), 
> AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
> +    { "password", "set password", OFFSET(pass), AV_OPT_TYPE_STRING, { 0 }, 
> 0, 0, E },
> +    { "content_type", "set content-type, MUST be set if not audio/mpeg", 
> OFFSET(content_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
> +    { "legacy_icecast", "use legacy SOURCE method, for Icecast < v2.4", 
> OFFSET(legacy_icecast), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
> +    { NULL }
> +};
> +
> +
> +static char *cat_header(char buf[], const char key[], const char value[])
> +{
> +    int len = strlen(key) + strlen(value) + 5;
> +    int is_first = !buf;
> +
> +    if (buf)
> +        len += strlen(buf);
> +    if (!(buf = av_realloc(buf, len))) {
> +        av_freep(&buf);
> +        return NULL;
> +    }
> +    if (is_first)
> +        *buf = '\0';
> +
> +    av_strlcatf(buf, len, "%s: %s\r\n", key, value);
> +    return buf;
> +}
> +
> +static int icecast_close(URLContext *h)
> +{
> +    IcecastContext *s = h->priv_data;
> +    if (s->hd)
> +        ffurl_close(s->hd);
> +    return 0;
> +}
> +
> +static int icecast_open(URLContext *h, const char *uri, int flags)
> +{
> +    IcecastContext *s = h->priv_data;
> +
> +    // Dict to set options that we pass to the HTTP protocol
> +    AVDictionary *opt_dict = NULL;
> +
> +    // URI part variables
> +    char h_url[1024], host[1024], auth[1024], path[1024];
> +    int port;
> +
> +    // Build header strings
> +    if (NOT_EMPTY(s->name))
> +        s->headers = cat_header(s->headers, "Ice-Name", s->name);

s->headers does not seem to be used outside of this function, so it does not
need to live in the context.

Also, I don't see it being freed.

> +    if (NOT_EMPTY(s->description))
> +        s->headers = cat_header(s->headers, "Ice-Description", 
> s->description);
> +    if (NOT_EMPTY(s->url))
> +        s->headers = cat_header(s->headers, "Ice-URL", s->url);
> +    if (NOT_EMPTY(s->genre))
> +        s->headers = cat_header(s->headers, "Ice-Genre", s->genre);
> +    s->headers = cat_header(s->headers, "Ice-Public", s->public ? "1" : "0");
> +
> +    // Set options
> +    av_dict_set(&opt_dict, "method", s->legacy_icecast ? "SOURCE" : "PUT", 
> 0);
> +    av_dict_set(&opt_dict, "auth_type", "basic", 0);
> +    if (NOT_EMPTY(s->headers))
> +        av_dict_set(&opt_dict, "headers", s->headers, 0);
> +    if (NOT_EMPTY(s->content_type))
> +        av_dict_set(&opt_dict, "content_type", s->content_type, 0);
> +    if (NOT_EMPTY(s->user_agent))
> +        av_dict_set(&opt_dict, "user_agent", s->user_agent, 0);
> +
> +    // Parse URI
> +    av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host),
> +                 &port, path, sizeof(path), uri);
> +
> +    // Check for auth data in URI
> +    if (auth[0] != '\0') {
> +        int user_len;
> +        char *p = strchr(auth, ':');
> +        if (p) {
> +            // Setting user and pass from URI
> +            user_len = p - auth + 1;
> +            if (!(s->user = av_malloc(user_len)))

Since the length of this string is limited from about by the length of auth
(i.e. 1024), perhaps it'd be simpler to make it a fixed-size buffer.

> +                return AVERROR(ENOMEM);
> +            av_strlcpy(s->user, auth, user_len);
> +            p++;
> +            if (s->pass != NULL) {
> +                av_free(s->pass);
> +                av_log(h, AV_LOG_WARNING, "Overwriting -password <pass> with 
> URI password!\n");
> +            }
> +            if (!(s->pass = av_malloc(strlen(auth) - user_len))) {
> +                av_free(s->user);
> +                return AVERROR(ENOMEM);
> +            }
> +            av_strlcpy(s->pass, p, strlen(auth) - user_len);
> +        } else {
> +            // Setting user from URI
> +            if (!(s->user = av_malloc(strlen(auth))))
> +                return AVERROR(ENOMEM);
> +            av_strlcpy(s->user, auth, strlen(auth));
> +        }
> +    }
> +
> +    // Build new authstring
> +    snprintf(auth, sizeof(auth),
> +             "%s:%s",
> +             s->user ? s->user : DEFAULT_ICE_USER,
> +             s->pass ? s->pass : "");
> +
> +    // Check for mountpoint (path)
> +    if (path[0] == '\0' || strcmp(path, "/") == 0) {
> +        av_log(h, AV_LOG_ERROR, "No mountpoint (path) specified!\n");
> +        return AVERROR(EIO);
> +    }
> +
> +    // Build new URI for passing to http protocol
> +    ff_url_join(h_url, sizeof(h_url), "http", auth, host, port, "%s", path);
> +
> +    // Free variables
> +    av_freep(&s->user);
> +
> +    // Finally open http proto handler
> +    return ffurl_open(&s->hd, h_url, AVIO_FLAG_READ_WRITE, NULL, &opt_dict);

This is not fully safe, since some options might be rejected by the http
protocol and remain in the dict.
So you should free it explicitly yourself after ffurl_open()

> +}
> +
> +static int icecast_write(URLContext *h, const uint8_t *buf, int size)
> +{
> +    IcecastContext *s = h->priv_data;
> +    if (!s->send_started) {
> +        s->send_started = 1;
> +        if (!s->content_type && size >= 8) {
> +            static const uint8_t oggs[4] = { 0x4F, 0x67, 0x67, 0x53 };
> +            static const uint8_t webm[4] = { 0x1A, 0x45, 0xDF, 0xA3 };
> +            static const uint8_t opus[8] = { 0x4F, 0x70, 0x75, 0x73, 0x48, 
> 0x65, 0x61, 0x64 };
> +            if (memcmp(buf, oggs, sizeof(oggs)) == 0) {
> +                av_log(h, AV_LOG_WARNING, "Streaming ogg but appropriated 
> content type NOT set!\n");
                                                                           ^
that 'd' should not be there i think

-- 
Anton Khirnov
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to