[FFmpeg-devel] [PATCH] HTTP cookie support

Stefano Sabatini stefasab at gmail.com
Sun Jan 13 14:43:06 CET 2013


On date Saturday 2013-01-12 13:31:31 -0500, Micah Galizia encoded:
> Hello,
> 
> I'm submitting two patches to add support for cookies to the HTTP protocol.
> It contains the recommendations made by both Michael and Sefano in a prior
> thread. The first is makes the code change and the second updates
> protocols.texi to include the new protocol option.
> 
> Thanks in advance!
> -- 
> "The mark of an immature man is that he wants to die nobly for a cause,
> while the mark of the mature man is that he wants to live humbly for
> one."   --W. Stekel

> From 030d545d4c2a9fe12e77df1670dd46a9a461d3d9 Mon Sep 17 00:00:00 2001
> From: Micah Galizia <micahgalizia at gmail.com>
> Date: Sat, 12 Jan 2013 13:25:19 -0500
> Subject: [PATCH 1/2] add HTTP protocol cookie support
> 
> ---
>  libavformat/http.c |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 114 insertions(+)
> 
> diff --git a/libavformat/http.c b/libavformat/http.c
> index a9d952b..a2fdb93 100644
> --- a/libavformat/http.c
> +++ b/libavformat/http.c
> @@ -64,6 +64,7 @@ typedef struct {
>      int is_akamai;
>      int rw_timeout;
>      char *mime_type;
> +    char *cookies;          ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
>  } HTTPContext;
>  
>  #define OFFSET(x) offsetof(HTTPContext, x)
> @@ -80,6 +81,7 @@ static const AVOption options[] = {
>  {"post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D|E },
>  {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
>  {"mime_type", "set MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
> +{"cookies", "cookies to be sent in applicable future requests. Uses newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },

nit: "set cookies ... . Use ..."

>  {NULL}
>  };
>  #define HTTP_CLASS(flavor)\
> @@ -359,11 +361,115 @@ static int process_line(URLContext *h, char *line, int line_count,
>              s->is_akamai = 1;
>          } else if (!av_strcasecmp (tag, "Content-Type")) {
>              av_free(s->mime_type); s->mime_type = av_strdup(p);
> +        } else if (!av_strcasecmp (tag, "Set-Cookie")) {
> +            if (!s->cookies) {
> +                if (!(s->cookies = av_strdup(p)))
> +                    return AVERROR(ENOMEM);
> +            } else {
> +                char *tmp = s->cookies;
> +                size_t str_size = strlen(tmp) + strlen(p) + 2;
> +                if (!(s->cookies = av_malloc(str_size))) {
> +                    s->cookies = tmp;
> +                    return AVERROR(ENOMEM);
> +                }
> +                snprintf(s->cookies, str_size, "%s\n%s", tmp, p);
> +                av_free(tmp);

Alternatively this may realloc cookies (would require to store
cookies_size in the context), or even use a list of pair-values
couples, so you don't need to parse them later (but feel free to keep
the current implementation).

> +            }
>          }
>      }
>      return 1;
>  }
>  

> +static int get_cookies(HTTPContext *s, char **cookies, const char *path,
> +                       const char *domain)

Please add a doxy here, something like:

Create a string containing the cookies name-values pairs which are
stored in the HTTP protocol context matching path and domain. The
string is stored into *cookies.

> +{
> +    // cookie strings will look like Set-Cookie header field values.  Multiple
> +    // Set-Cookie fields will result in multiple values delimited by a newline
> +    const char *set_cookies = s->cookies;
> +
> +    if (!set_cookies) return AVERROR(EINVAL);
> +
> +    while (*set_cookies) {
> +        int domain_offset = 0;
> +        char *cdomain = NULL, *cpath = NULL, *cvalue = NULL;

> +        char *cookie = av_get_token(&set_cookies, "\n"), *c = cookie;
> +
> +        while (*cookie) {
> +            char *param = av_get_token((const char**)&cookie, "; ");

Considering that HTTP cookies don't support '\-escaping, av_strtok() might
be a safer choice.

> +
> +            if (!av_strncasecmp("path=", param, 5)) {
> +                cpath = av_strdup(&param[5]);
> +            } else if (!av_strncasecmp("domain=", param, 7)) {
> +                cdomain = av_strdup(&param[7]);
> +            } else if (!av_strncasecmp("secure", param, 6) ||
> +                     !av_strncasecmp("comment", param, 7) ||
> +                     !av_strncasecmp("max-age", param, 7) ||
> +                     !av_strncasecmp("version", param, 7)) {
> +                // ignore Comment, Max-Age, Secure and Version
> +            } else {
> +                cvalue = av_strdup(param);
> +            }
> +
> +            if (*cookie == ';') cookie++;
> +            av_free(param);
> +        }
> +

> +        // ensure all of the necessary values are valid
> +        if (!cdomain || !cpath || !cvalue) {
> +            goto done_cookie;
> +        }
> +

You may issue a warning in case the set cookie is not valid.


> +        // check if the request path matches the cookie path
> +        if (av_strncasecmp(path, cpath, strlen(cpath)))
> +            goto done_cookie;

> +


> +        // the domain should be at least the size of our cookie domain and

and?

> +        domain_offset = strlen(domain) - strlen(cdomain);
> +        if (domain_offset < 0)
> +            goto done_cookie;

> +
> +        // match the cookie domain
> +        if (av_strcasecmp(&domain[domain_offset], cdomain))
> +            goto done_cookie;
> +

> +        // cookie parameters match, so copy the value
> +        if (!*cookies) {
> +            if (!(*cookies = av_strdup(cvalue))) {
> +                av_free(cdomain);
> +                av_free(cpath);
> +                av_free(cvalue);
> +                av_free(c);
> +                return AVERROR(ENOMEM);
> +            }
> +        } else {
> +            char *tmp = *cookies;
> +            size_t str_size = strlen(cvalue) + strlen(*cookies) + 3;
> +            if (!(*cookies = av_malloc(str_size))) {
> +                av_free(tmp);

> +                av_free(cdomain);
> +                av_free(cpath);
> +                av_free(cvalue);
> +                av_free(c);

This part could be factorized, you set ret to ENOMEM, go to
done_cookie and return ret if ret < 0.

> +                return AVERROR(ENOMEM);
> +            }
> +            snprintf(*cookies, str_size, "%s; %s", tmp, cvalue);
> +            av_free(tmp);
> +        }
> +
> +        done_cookie:
> +        av_free(cdomain);
> +        av_free(cpath);
> +        av_free(cvalue);
> +
> +        av_free(c);
> +
> +        // advance the token
> +        if (*set_cookies == '\n') set_cookies++;
> +    }
> +
> +    return 0;
> +}
> +
>  static inline int has_header(const char *str, const char *header)
>  {
>      /* header + 2 to skip over CRLF prefix. (make sure you have one!) */
> @@ -460,6 +566,14 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
>      if (!has_header(s->headers, "\r\nContent-Type: ") && s->content_type)
>          len += av_strlcatf(headers + len, sizeof(headers) - len,
>                             "Content-Type: %s\r\n", s->content_type);
> +    if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) {
> +        char *cookies = NULL;
> +        if (!get_cookies(s, &cookies, path, hoststr)) {
> +            len += av_strlcatf(headers + len, sizeof(headers) - len,
> +                               "Cookie: %s\r\n", cookies);
> +            av_free(cookies);
> +        }
> +    }
>  
>      /* now add in custom headers */
>      if (s->headers)
> -- 
> 1.7.10.4
> 

> From d2cc05b6315173f2423ab2c744a2f18a949eb888 Mon Sep 17 00:00:00 2001
> From: Micah Galizia <micahgalizia at gmail.com>
> Date: Sat, 12 Jan 2013 13:25:33 -0500
> Subject: [PATCH 2/2] document HTTP protocol cookie support
> 
> ---
>  doc/protocols.texi |   19 +++++++++++++++++++
>  1 file changed, 19 insertions(+)
> 
> diff --git a/doc/protocols.texi b/doc/protocols.texi
> index 6fdd08d..17fcf3a 100644
> --- a/doc/protocols.texi
> +++ b/doc/protocols.texi
> @@ -164,8 +164,27 @@ not specified.
>  
>  @item mime_type
>  Set MIME type.
> +
> + at item cookies
> +Set the cookies to be sent in future requests. The format of each cookie is the
> +same as the value of a Set-Cookie HTTP response field. Multiple cookies can be
> +delimited by a newline character.
>  @end table
>  
> + at subsection HTTP Cookies
> +
> +Some HTTP requests will be denied unless cookie values are passed in with the
> +request. The -cookies flag allows these cookies to be specified. At

The @option{cookies} option allows ...

> +the very least, each cookie must specify a value along with a path and domain.
> +HTTP requests that match both the domain and path will automatically include the
> +cookie value in the HTTP Cookie header field. Multiple cookies can be delimited
> +by a newline.
> +
> +The required syntax to play a stream specifying a cookie is:

> + at example
> +ffplay http://somedomain.com/somestream.m3u8 -cookies "value; path=/; domain=somedomain.com;"

Note: although I'm not sure how the options are parsed in this case, I
supppose it would be safer to specify the option affecting the
protocol *before* the corresponding input.
In this case:
ffplay -cookies "value; path=/; domain=somedomain.com;" http://somedomain.com/somestream.m3u8 

Also "value" might be replaced by a more concrete example, like
KEY=VALUE; ATTRS

Alternatively we may want to support a syntax of the kind:
ffplay -proto http=cookies="....":... URI

to force protocol and options.

[...]
-- 
FFmpeg = Faithless Fostering Murdering Puritan Elaborated Geek


More information about the ffmpeg-devel mailing list