[FFmpeg-devel,1/2] libavformat/http: Ignore expired cookies

Submitted by Micah Galizia on May 31, 2017, 1:29 a.m.

Details

Message ID 20170531012955.4094-2-micahgalizia@gmail.com
State New
Headers show

Commit Message

Micah Galizia May 31, 2017, 1:29 a.m.
Signed-off-by: Micah Galizia <micahgalizia@gmail.com>
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
(cherry picked from commit 28b24670741e1de25bfc7b5ea7c1d6dbae1aef6f)
Signed-off-by: Micah Galizia <micahgalizia@gmail.com>
---
 libavformat/http.c | 213 +++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 156 insertions(+), 57 deletions(-)

Patch hide | download patch | download mbox

diff --git a/libavformat/http.c b/libavformat/http.c
index 293a8a7204..d06103ab6d 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,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(&param[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(&param[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)