From patchwork Fri Mar 31 01:33:47 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Micah Galizia X-Patchwork-Id: 3209 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.44.195 with SMTP id s186csp1825644vss; Thu, 30 Mar 2017 18:41:59 -0700 (PDT) X-Received: by 10.223.169.113 with SMTP id u104mr312712wrc.168.1490924519707; Thu, 30 Mar 2017 18:41:59 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id o18si1208279wmd.14.2017.03.30.18.41.59; Thu, 30 Mar 2017 18:41:59 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 925FF689898; Fri, 31 Mar 2017 04:41:56 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-io0-f196.google.com (mail-io0-f196.google.com [209.85.223.196]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 3166D68837F for ; Fri, 31 Mar 2017 04:41:50 +0300 (EEST) Received: by mail-io0-f196.google.com with SMTP id f103so4676897ioi.2 for ; Thu, 30 Mar 2017 18:41:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=zBUTWqsyKbvDKSiW7MLaADZMIfpTXOLiRllK9oBLSX8=; b=ZcE2ZnLjBpsLwaqT8ad1Z0iklIAzhDRtu65DuvrCoWmRwX58eqCm9ViDqJAzC6fG3k jEN7IurRg4v8PzhKeSNK+BvVzNmmb3RhJt4hkqtZOB9cSjQBAhNkyEXEtHhV1pCsGWRY pKfdNOh8ZmxWMHJstrQFDuXNlKQmEJaP1keln2skTlRrMu06Wd2Ps14d2F67tF6o2LsI ANVlF6ZJUed3ChmnJQIxqmVk57B+meSATTIfnvUbTipTLj6r4r/3LMLeLfuWdTxF+UY7 tWex/LyXdjA9QtDu2dt76zzN/lFWgLdegH75TODiypOBr7OBfNjz0Nbi9SzuT9MN91Ml 6d1w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=zBUTWqsyKbvDKSiW7MLaADZMIfpTXOLiRllK9oBLSX8=; b=atu6V7Ug7mpjDJKRoF4UU2w5/KkZqUj4INfUuLg5EyBBi4J7Ruda+xjJziUrC6hh85 QvODcaVqBRNoNYcLL+sXZRZhUeHWWEPRIWCLoM/ZwFrsHKRQktY54pnrUfLnohJPa910 y4zjrJLwI/MThX+7H5lVwUITAWjD8A68WbL5s0+ILXMbt8ST43koMBuGtbU66xBN0BF9 dRqpu8bAp58keIdJ15jOuxREaBJUuHvFZr8BJ2vOdvAJD+6zUit+ZDmAZVaZ/zRcHbvz vEtokZxMvgSCm2+0EuI7vciQjBf4BWMaxCkQexDGaH2BMgjMFXkM20N9UhAqyf+DgJWh tB2Q== X-Gm-Message-State: AFeK/H0admt8QBeU4Me6IRoPkP6XeOPo7BPZEAHkLsllL3qSd1XoMzYarUFdfV9sRShGbg== X-Received: by 10.107.50.11 with SMTP id y11mr612554ioy.10.1490924072790; Thu, 30 Mar 2017 18:34:32 -0700 (PDT) Received: from localhost.localdomain (ip-24-54-73-138.user.start.ca. [24.54.73.138]) by smtp.gmail.com with ESMTPSA id f63sm505825ita.26.2017.03.30.18.34.31 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 Mar 2017 18:34:32 -0700 (PDT) From: Micah Galizia To: ffmpeg-devel@ffmpeg.org Date: Thu, 30 Mar 2017 21:33:47 -0400 Message-Id: <20170331013348.12504-2-micahgalizia@gmail.com> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170331013348.12504-1-micahgalizia@gmail.com> References: <20170331013348.12504-1-micahgalizia@gmail.com> Subject: [FFmpeg-devel] [PATCH 1/2] Ignore expired cookies X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Micah Galizia MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Micah Galizia --- libavformat/http.c | 204 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 147 insertions(+), 57 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 293a8a7..a9f1b3f 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -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,102 @@ 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; + + if (!(cstr = av_strdup(set_cookie))) + return AVERROR(EINVAL); + + next_param = cstr; + while ((param = av_strtok(next_param, ";", &next_param))) { + char *name, *value; + param += strspn(param, WHITESPACES); + if ((name = av_strtok(param, "=", &value))) { + av_dict_set(dict, name, value, 0); + } + } + + 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 +963,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 +971,82 @@ 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; + 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); + if (ret < 0) + av_freep(cookies); - return 0; + return ret; } static inline int has_header(const char *str, const char *header)