static bool invalid_octets(const char *ptr, size_t len)
{
const unsigned char *p = (const unsigned char *)ptr;
- /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */
+ /* Reject all bytes \x01 - \x1f + \x7f */
while(len && *p) {
- if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f))
+ if((*p < 0x20) || (*p == 0x7f))
return TRUE;
p++;
len--;
return result;
}
-/* this function return errors on OOM etc, not on plain cookie format
- problems */
-static CURLcode parse_cookie_header(
- struct Curl_easy *data,
- struct Cookie *co,
- const struct CookieInfo *ci,
- bool *okay, /* if the cookie was fine */
- const char *ptr,
- const char *domain, /* default domain */
- const char *path, /* full path used when this cookie is
- set, used to get default path for
- the cookie unless set */
- bool secure) /* TRUE if connection is over secure origin */
+/*
+ * Parse the first name/value pair of the cookie header, which is the actual
+ * cookie name and value.
+ */
+static bool parse_first_pair(struct Curl_easy *data, struct Cookie *co,
+ struct Curl_str *cookie,
+ struct Curl_str *name,
+ struct Curl_str *val,
+ bool sep)
+{
+ /* The first name/value pair is the actual cookie name */
+ if(!sep || !curlx_strlen(name)) {
+ infof(data, "invalid cookie, dropped");
+ return FALSE;
+ }
+
+ /*
+ * Check for too long individual name or contents. Chrome and Firefox
+ * support 4095 or 4096 bytes combo
+ */
+ if((curlx_strlen(name) + curlx_strlen(val)) > MAX_NAME) {
+ infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
+ curlx_strlen(name), curlx_strlen(val));
+ return FALSE;
+ }
+
+ /* Check if we have a reserved prefix set. */
+ if(!strncmp("__Secure-", curlx_str(name), 9))
+ co->prefix_secure = TRUE;
+ else if(!strncmp("__Host-", curlx_str(name), 7))
+ co->prefix_host = TRUE;
+
+ cookie[COOKIE_NAME] = *name;
+ cookie[COOKIE_VALUE] = *val;
+ return TRUE;
+}
+
+static bool parse_flag(struct Curl_easy *data, struct Cookie *co,
+ const struct CookieInfo *ci,
+ struct Curl_str *name, bool secure)
+{
+ /*
+ * secure cookies are only allowed to be set when the connection is
+ * using a secure protocol, or when the cookie is being set by
+ * reading from file
+ */
+ if(curlx_str_casecompare(name, "secure")) {
+ if(secure || !ci->running)
+ co->secure = TRUE;
+ else {
+ infof(data, "skipped cookie because not 'secure'");
+ return FALSE;
+ }
+ }
+ else if(curlx_str_casecompare(name, "httponly"))
+ co->httponly = TRUE;
+
+ return TRUE;
+}
+
+static bool parse_domain(struct Curl_easy *data, struct Cookie *co,
+ struct Curl_str *cookie_domain,
+ struct Curl_str *val,
+ const char **domainp)
+{
+ bool is_ip;
+ const char *domain = *domainp;
+ const char *v = curlx_str(val);
+ /*
+ * Now, we make sure that our host is within the given domain, or
+ * the given domain is not valid and thus cannot be set.
+ */
+
+ if('.' == *v)
+ curlx_str_nudge(val, 1);
+
+#ifndef USE_LIBPSL
+ /*
+ * Without PSL we do not know when the incoming cookie is set on a
+ * TLD or otherwise "protected" suffix. To reduce risk, we require a
+ * dot OR the exact hostname being "localhost".
+ */
+ if(bad_domain(curlx_str(val), curlx_strlen(val))) {
+ *domainp = ":";
+ domain = ":";
+ }
+#endif
+
+ is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(val));
+
+ if(!domain ||
+ (is_ip &&
+ !strncmp(curlx_str(val), domain, curlx_strlen(val)) &&
+ (curlx_strlen(val) == strlen(domain))) ||
+ (!is_ip && cookie_tailmatch(curlx_str(val),
+ curlx_strlen(val), domain))) {
+ *cookie_domain = *val;
+ if(!is_ip)
+ co->tailmatch = TRUE; /* we always do that if the domain name was
+ given */
+ }
+ else {
+ /*
+ * We did not get a tailmatch and then the attempted set domain is
+ * not a domain to which the current host belongs. Mark as bad.
+ */
+ infof(data, "skipped cookie with bad tailmatch domain: %s",
+ curlx_str(val));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void parse_maxage(struct Cookie *co, struct Curl_str *val,
+ time_t *nowp)
+{
+ int rc;
+ const char *maxage = curlx_str(val);
+ if(*maxage == '\"')
+ maxage++;
+ rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
+ if(!*nowp)
+ *nowp = time(NULL);
+ switch(rc) {
+ case STRE_OVERFLOW:
+ /* overflow, used max value */
+ co->expires = CURL_OFF_T_MAX;
+ break;
+ default:
+ /* negative or otherwise bad, expire */
+ co->expires = 1;
+ break;
+ case STRE_OK:
+ if(!co->expires)
+ co->expires = 1; /* expire now */
+ else if(CURL_OFF_T_MAX - *nowp < co->expires)
+ /* would overflow */
+ co->expires = CURL_OFF_T_MAX;
+ else
+ co->expires += *nowp;
+ break;
+ }
+ cap_expires(*nowp, co);
+}
+
+static void parse_expires(struct Cookie *co, struct Curl_str *val,
+ time_t *nowp)
+{
+ /*
+ * Let max-age have priority.
+ *
+ * If the date cannot get parsed for whatever reason, the cookie
+ * will be treated as a session cookie
+ */
+ if(!co->expires && (curlx_strlen(val) < MAX_DATE_LENGTH)) {
+ char dbuf[MAX_DATE_LENGTH + 1];
+ time_t date = 0;
+ memcpy(dbuf, curlx_str(val), curlx_strlen(val));
+ dbuf[curlx_strlen(val)] = 0;
+ if(!Curl_getdate_capped(dbuf, &date)) {
+ if(!date)
+ date++;
+ co->expires = (curl_off_t)date;
+ }
+ else
+ co->expires = 0;
+ if(!*nowp)
+ *nowp = time(NULL);
+ cap_expires(*nowp, co);
+ }
+}
+
+/* this function returns errors on OOM etc, not for cookie format problems */
+static CURLcode
+parse_cookie_header(struct Curl_easy *data,
+ struct Cookie *co,
+ const struct CookieInfo *ci,
+ bool *okay, /* if the cookie was fine */
+ const char *ptr, /* the header */
+ const char *domain, /* default domain */
+ /* full path used when this cookie is set */
+ const char *path,
+ bool secure_origin)
{
/* This line was read off an HTTP-header */
time_t now = 0;
memset(cookie, 0, sizeof(cookie));
do {
struct Curl_str name;
- struct Curl_str val;
/* we have a <name>=<value> pair or a stand-alone word here */
if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {
+ struct Curl_str val;
bool sep = FALSE;
curlx_str_trimblanks(&name);
+ if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) {
+ infof(data, "invalid octets in name, cookie dropped");
+ return CURLE_OK;
+ }
+
if(!curlx_str_single(&ptr, '=')) {
sep = TRUE; /* a '=' was used */
if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
curlx_str_trimblanks(&val);
- /* Reject cookies with a TAB inside the value */
- if(curlx_strlen(&val) &&
- memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {
- infof(data, "cookie contains TAB, dropping");
+ if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) {
+ infof(data, "invalid octets in value, cookie dropped");
return CURLE_OK;
}
}
curlx_str_init(&val);
if(!curlx_strlen(&cookie[COOKIE_NAME])) {
- /* The first name/value pair is the actual cookie name */
- if(!sep ||
- /* Bad name/value pair. */
- invalid_octets(curlx_str(&name), curlx_strlen(&name)) ||
- invalid_octets(curlx_str(&val), curlx_strlen(&val)) ||
- !curlx_strlen(&name)) {
- infof(data, "invalid octets in name/value, cookie dropped");
- return CURLE_OK;
- }
-
- /*
- * Check for too long individual name or contents, or too long
- * combination of name + contents. Chrome and Firefox support 4095 or
- * 4096 bytes combo
- */
- if(curlx_strlen(&name) >= (MAX_NAME - 1) ||
- curlx_strlen(&val) >= (MAX_NAME - 1) ||
- ((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) {
- infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
- curlx_strlen(&name), curlx_strlen(&val));
+ if(!parse_first_pair(data, co, cookie, &name, &val, sep))
return CURLE_OK;
- }
-
- /* Check if we have a reserved prefix set. */
- if(!strncmp("__Secure-", curlx_str(&name), 9))
- co->prefix_secure = TRUE;
- else if(!strncmp("__Host-", curlx_str(&name), 7))
- co->prefix_host = TRUE;
-
- cookie[COOKIE_NAME] = name;
- cookie[COOKIE_VALUE] = val;
}
else if(!sep) {
- /*
- * this is a "<name>" with no content
- */
-
- /*
- * secure cookies are only allowed to be set when the connection is
- * using a secure protocol, or when the cookie is being set by
- * reading from file
- */
- if(curlx_str_casecompare(&name, "secure")) {
- if(secure || !ci->running)
- co->secure = TRUE;
- else {
- infof(data, "skipped cookie because not 'secure'");
- return CURLE_OK;
- }
- }
- else if(curlx_str_casecompare(&name, "httponly"))
- co->httponly = TRUE;
+ if(!parse_flag(data, co, ci, &name, secure_origin))
+ return CURLE_OK;
}
- else if(curlx_str_casecompare(&name, "path")) {
+ else if(curlx_str_casecompare(&name, "path"))
cookie[COOKIE_PATH] = val;
- }
else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) {
- bool is_ip;
- const char *v = curlx_str(&val);
- /*
- * Now, we make sure that our host is within the given domain, or
- * the given domain is not valid and thus cannot be set.
- */
-
- if('.' == *v)
- curlx_str_nudge(&val, 1);
-
-#ifndef USE_LIBPSL
- /*
- * Without PSL we do not know when the incoming cookie is set on a
- * TLD or otherwise "protected" suffix. To reduce risk, we require a
- * dot OR the exact hostname being "localhost".
- */
- if(bad_domain(curlx_str(&val), curlx_strlen(&val)))
- domain = ":";
-#endif
-
- is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val));
-
- if(!domain ||
- (is_ip &&
- !strncmp(curlx_str(&val), domain, curlx_strlen(&val)) &&
- (curlx_strlen(&val) == strlen(domain))) ||
- (!is_ip && cookie_tailmatch(curlx_str(&val),
- curlx_strlen(&val), domain))) {
- cookie[COOKIE_DOMAIN] = val;
- if(!is_ip)
- co->tailmatch = TRUE; /* we always do that if the domain name was
- given */
- }
- else {
- /*
- * We did not get a tailmatch and then the attempted set domain is
- * not a domain to which the current host belongs. Mark as bad.
- */
- infof(data, "skipped cookie with bad tailmatch domain: %s",
- curlx_str(&val));
+ if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain))
return CURLE_OK;
- }
- }
- else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) {
- /*
- * Defined in RFC2109:
- *
- * Optional. The Max-Age attribute defines the lifetime of the
- * cookie, in seconds. The delta-seconds value is a decimal non-
- * negative integer. After delta-seconds seconds elapse, the
- * client should discard the cookie. A value of zero means the
- * cookie should be discarded immediately.
- */
- int rc;
- const char *maxage = curlx_str(&val);
- if(*maxage == '\"')
- maxage++;
- rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
- if(!now)
- now = time(NULL);
- switch(rc) {
- case STRE_OVERFLOW:
- /* overflow, used max value */
- co->expires = CURL_OFF_T_MAX;
- break;
- default:
- /* negative or otherwise bad, expire */
- co->expires = 1;
- break;
- case STRE_OK:
- if(!co->expires)
- co->expires = 1; /* expire now */
- else if(CURL_OFF_T_MAX - now < co->expires)
- /* would overflow */
- co->expires = CURL_OFF_T_MAX;
- else
- co->expires += now;
- break;
- }
- cap_expires(now, co);
- }
- else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val) &&
- !co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) {
- /*
- * Let max-age have priority.
- *
- * If the date cannot get parsed for whatever reason, the cookie
- * will be treated as a session cookie
- */
- char dbuf[MAX_DATE_LENGTH + 1];
- time_t date = 0;
- memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));
- dbuf[curlx_strlen(&val)] = 0;
- if(!Curl_getdate_capped(dbuf, &date)) {
- if(!date)
- date++;
- co->expires = (curl_off_t)date;
- }
- else
- co->expires = 0;
- if(!now)
- now = time(NULL);
- cap_expires(now, co);
}
+ else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val))
+ parse_maxage(co, &val, &now);
+ else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val))
+ parse_expires(co, &val, &now);
}
} while(!curlx_str_single(&ptr, ';'));
const struct CookieInfo *ci,
bool *okay,
const char *lineptr,
- bool secure) /* TRUE if connection is over
- secure origin */
+ bool secure_origin)
{
/*
* This line is NOT an HTTP header style line, we do offer support for
case 3:
co->secure = FALSE;
if(curl_strnequal(ptr, "TRUE", len)) {
- if(secure || ci->running)
+ if(secure_origin || ci->running)
co->secure = TRUE;
else
return CURLE_OK;