From 0b80a12184ef64a0b20c4baa70270b223e821812 Mon Sep 17 00:00:00 2001 From: Micah Galizia Date: Sun, 13 Jan 2013 21:32:57 -0500 Subject: [PATCH] lavf/http: add HTTP protocol cookie support Signed-off-by: Stefano Sabatini --- libavformat/http.c | 116 ++++++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/libavformat/http.c b/libavformat/http.c index a9d952befb..903cf4a108 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", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 }, {NULL} }; #define HTTP_CLASS(flavor)\ @@ -359,11 +361,117 @@ 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); + } } } return 1; } +/** + * Create a string containing cookie values for use as a HTTP cookie header + * field value for a particular path and domain from the cookie values stored in + * the HTTP protocol context. The cookie string is stored in *cookies. + * + * @return a negative value if an error condition occurred, 0 otherwise + */ +static int get_cookies(HTTPContext *s, char **cookies, const char *path, + const char *domain) +{ + // cookie strings will look like Set-Cookie header field values. Multiple + // Set-Cookie fields will result in multiple values delimited by a newline + int ret = 0; + char *next, *cookie, *set_cookies = av_strdup(s->cookies), *cset_cookies = set_cookies; + + if (!set_cookies) return AVERROR(EINVAL); + + *cookies = NULL; + while ((cookie = av_strtok(set_cookies, "\n", &next))) { + int domain_offset = 0; + char *param, *next_param, *cdomain = NULL, *cpath = NULL, *cvalue = NULL; + set_cookies = NULL; + + while ((param = av_strtok(cookie, "; ", &next_param))) { + cookie = NULL; + if (!av_strncasecmp("path=", param, 5)) { + cpath = av_strdup(¶m[5]); + } else if (!av_strncasecmp("domain=", param, 7)) { + cdomain = av_strdup(¶m[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); + } + } + + // ensure all of the necessary values are valid + if (!cdomain || !cpath || !cvalue) { + av_log(s, AV_LOG_WARNING, + "Invalid cookie found, no value, path or domain specified\n"); + goto done_cookie; + } + + // 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 + 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))) { + ret = AVERROR(ENOMEM); + goto done_cookie; + } + } else { + char *tmp = *cookies; + size_t str_size = strlen(cvalue) + strlen(*cookies) + 3; + if (!(*cookies = av_malloc(str_size))) { + ret = AVERROR(ENOMEM); + goto done_cookie; + } + snprintf(*cookies, str_size, "%s; %s", tmp, cvalue); + av_free(tmp); + } + + done_cookie: + av_free(cdomain); + av_free(cpath); + av_free(cvalue); + if (ret < 0) { + if (*cookies) av_freep(cookies); + av_free(cset_cookies); + return ret; + } + } + + av_free(cset_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 +568,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) diff --git a/libavformat/version.h b/libavformat/version.h index e1fe74e034..cffc4bf6a5 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -31,7 +31,7 @@ #define LIBAVFORMAT_VERSION_MAJOR 54 #define LIBAVFORMAT_VERSION_MINOR 60 -#define LIBAVFORMAT_VERSION_MICRO 100 +#define LIBAVFORMAT_VERSION_MICRO 101 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \