|
|
|
@ -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) |
|
|
|
|