@@ -29,6 +29,7 @@
#include "libavutil/avstring.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"
+#include "libavutil/parseutils.h"
#include "avformat.h"
#include "http.h"
@@ -48,6 +49,8 @@
#define MAX_REDIRECTS 8
#define HTTP_SINGLE 1
#define HTTP_MUTLI 2
+#define MAX_EXPIRY 19
+#define WHITESPACES " \n\t\r"
typedef enum {
LOWER_PROTO,
READ_HEADERS,
@@ -680,10 +683,112 @@ static int parse_icy(HTTPContext *s, const char *tag, const char *p)
return 0;
}
+static int parse_set_cookie_expiry_time(const char *exp_str, struct tm *buf)
+{
+ char exp_buf[MAX_EXPIRY];
+ int i, j, exp_buf_len = MAX_EXPIRY-1;
+ char *expiry;
+
+ // strip off any punctuation or whitespace
+ for (i = 0, j = 0; exp_str[i] != '\0' && j < exp_buf_len; i++) {
+ if ((exp_str[i] >= '0' && exp_str[i] <= '9') ||
+ (exp_str[i] >= 'A' && exp_str[i] <= 'Z') ||
+ (exp_str[i] >= 'a' && exp_str[i] <= 'z')) {
+ exp_buf[j] = exp_str[i];
+ j++;
+ }
+ }
+ exp_buf[j] = '\0';
+ expiry = exp_buf;
+
+ // move the string beyond the day of week
+ while ((*expiry < '0' || *expiry > '9') && *expiry != '\0')
+ expiry++;
+
+ return av_small_strptime(expiry, "%d%b%Y%H%M%S", buf) ? 0 : AVERROR(EINVAL);
+}
+
+static int parse_set_cookie(const char *set_cookie, AVDictionary **dict)
+{
+ char *param, *next_param, *cstr, *back;
+
+ if (!(cstr = av_strdup(set_cookie)))
+ return AVERROR(EINVAL);
+
+ // strip any trailing whitespace
+ back = &cstr[strlen(cstr)-1];
+ while (strchr(WHITESPACES, *back)) {
+ *back='\0';
+ back--;
+ }
+
+ next_param = cstr;
+ while ((param = av_strtok(next_param, ";", &next_param))) {
+ char *name, *value;
+ param += strspn(param, WHITESPACES);
+ if ((name = av_strtok(param, "=", &value))) {
+ if (av_dict_set(dict, name, value, 0) < 0) {
+ av_free(cstr);
+ return -1;
+ }
+ }
+ }
+
+ av_free(cstr);
+ return 0;
+}
+
static int parse_cookie(HTTPContext *s, const char *p, AVDictionary **cookies)
{
+ AVDictionary *new_params = NULL;
+ AVDictionaryEntry *e, *cookie_entry;
char *eql, *name;
+ // ensure the cookie is parsable
+ if (parse_set_cookie(p, &new_params))
+ return -1;
+
+ // if there is no cookie value there is nothing to parse
+ cookie_entry = av_dict_get(new_params, "", NULL, AV_DICT_IGNORE_SUFFIX);
+ if (!cookie_entry || !cookie_entry->value) {
+ av_dict_free(&new_params);
+ return -1;
+ }
+
+ // ensure the cookie is not expired or older than an existing value
+ if ((e = av_dict_get(new_params, "expires", NULL, 0)) && e->value) {
+ struct tm new_tm = {0};
+ if (!parse_set_cookie_expiry_time(e->value, &new_tm)) {
+ AVDictionaryEntry *e2;
+
+ // if the cookie has already expired ignore it
+ if (av_timegm(&new_tm) < av_gettime() / 1000000) {
+ av_dict_free(&new_params);
+ return -1;
+ }
+
+ // only replace an older cookie with the same name
+ e2 = av_dict_get(*cookies, cookie_entry->key, NULL, 0);
+ if (e2 && e2->value) {
+ AVDictionary *old_params = NULL;
+ if (!parse_set_cookie(p, &old_params)) {
+ e2 = av_dict_get(old_params, "expires", NULL, 0);
+ if (e2 && e2->value) {
+ struct tm old_tm = {0};
+ if (!parse_set_cookie_expiry_time(e->value, &old_tm)) {
+ if (av_timegm(&new_tm) < av_timegm(&old_tm)) {
+ av_dict_free(&new_params);
+ av_dict_free(&old_params);
+ return -1;
+ }
+ }
+ }
+ }
+ av_dict_free(&old_params);
+ }
+ }
+ }
+
// duplicate the cookie name (dict will dupe the value)
if (!(eql = strchr(p, '='))) return AVERROR(EINVAL);
if (!(name = av_strndup(p, eql - p))) return AVERROR(ENOMEM);
@@ -868,7 +973,7 @@ static int get_cookies(HTTPContext *s, char **cookies, const char *path,
// 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;
+ char *cookie, *set_cookies = av_strdup(s->cookies), *next = set_cookies;
if (!set_cookies) return AVERROR(EINVAL);
@@ -876,87 +981,81 @@ static int get_cookies(HTTPContext *s, char **cookies, const char *path,
av_dict_free(&s->cookie_dict);
*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 ((cookie = av_strtok(next, "\n", &next))) {
+ AVDictionary *cookie_params = NULL;
+ AVDictionaryEntry *cookie_entry, *e;
// store the cookie in a dict in case it is updated in the response
if (parse_cookie(s, cookie, &s->cookie_dict))
av_log(s, AV_LOG_WARNING, "Unable to parse '%s'\n", cookie);
- while ((param = av_strtok(cookie, "; ", &next_param))) {
- if (cookie) {
- // first key-value pair is the actual cookie value
- cvalue = av_strdup(param);
- cookie = NULL;
- } else if (!av_strncasecmp("path=", param, 5)) {
- av_free(cpath);
- cpath = av_strdup(¶m[5]);
- } else if (!av_strncasecmp("domain=", param, 7)) {
- // if the cookie specifies a sub-domain, skip the leading dot thereby
- // supporting URLs that point to sub-domains and the master domain
- int leading_dot = (param[7] == '.');
- av_free(cdomain);
- cdomain = av_strdup(¶m[7+leading_dot]);
- } else {
- // ignore unknown attributes
- }
+ // continue on to the next cookie if this one cannot be parsed
+ if (parse_set_cookie(cookie, &cookie_params))
+ continue;
+
+ // if the cookie has no value, skip it
+ cookie_entry = av_dict_get(cookie_params, "", NULL, AV_DICT_IGNORE_SUFFIX);
+ if (!cookie_entry || !cookie_entry->value) {
+ av_dict_free(&cookie_params);
+ continue;
}
- if (!cdomain)
- cdomain = av_strdup(domain);
-
- // 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;
+
+ // if the cookie has expired, don't add it
+ if ((e = av_dict_get(cookie_params, "expires", NULL, 0)) && e->value) {
+ struct tm tm_buf = {0};
+ if (!parse_set_cookie_expiry_time(e->value, &tm_buf)) {
+ if (av_timegm(&tm_buf) < av_gettime() / 1000000) {
+ av_dict_free(&cookie_params);
+ continue;
+ }
+ }
}
- // check if the request path matches the cookie path
- if (av_strncasecmp(path, cpath, strlen(cpath)))
- goto done_cookie;
+ // if no domain in the cookie assume it appied to this request
+ if ((e = av_dict_get(cookie_params, "domain", NULL, 0)) && e->value) {
+ // find the offset comparison is on the min domain (b.com, not a.b.com)
+ int domain_offset = strlen(domain) - strlen(e->value);
+ if (domain_offset < 0) {
+ av_dict_free(&cookie_params);
+ continue;
+ }
- // 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], e->value)) {
+ av_dict_free(&cookie_params);
+ continue;
+ }
+ }
- // match the cookie domain
- if (av_strcasecmp(&domain[domain_offset], cdomain))
- goto done_cookie;
+ // ensure this cookie matches the path
+ e = av_dict_get(cookie_params, "path", NULL, 0);
+ if (!e || av_strncasecmp(path, e->value, strlen(e->value))) {
+ av_dict_free(&cookie_params);
+ continue;
+ }
// cookie parameters match, so copy the value
if (!*cookies) {
- if (!(*cookies = av_strdup(cvalue))) {
+ if (!(*cookies = av_asprintf("%s=%s", cookie_entry->key, cookie_entry->value))) {
ret = AVERROR(ENOMEM);
- goto done_cookie;
+ break;
}
} else {
char *tmp = *cookies;
- size_t str_size = strlen(cvalue) + strlen(*cookies) + 3;
+ size_t str_size = strlen(cookie_entry->key) + strlen(cookie_entry->value) + strlen(*cookies) + 4;
if (!(*cookies = av_malloc(str_size))) {
ret = AVERROR(ENOMEM);
- goto done_cookie;
+ av_free(tmp);
+ break;
}
- snprintf(*cookies, str_size, "%s; %s", tmp, cvalue);
+ snprintf(*cookies, str_size, "%s; %s=%s", tmp, cookie_entry->key, cookie_entry->value);
av_free(tmp);
}
-
- done_cookie:
- av_freep(&cdomain);
- av_freep(&cpath);
- av_freep(&cvalue);
- if (ret < 0) {
- if (*cookies) av_freep(cookies);
- av_free(cset_cookies);
- return ret;
- }
}
- av_free(cset_cookies);
+ av_free(set_cookies);
- return 0;
+ return ret;
}
static inline int has_header(const char *str, const char *header)