* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
#include "urldata.h"
#include "cookie.h"
-#include "strequal.h"
#include "strtok.h"
#include "sendf.h"
#include "slist.h"
#include "share.h"
#include "strtoofft.h"
-#include "rawstr.h"
+#include "strcase.h"
#include "curl_memrchr.h"
#include "inet_pton.h"
if(hostname_len < cookie_domain_len)
return FALSE;
- if(!Curl_raw_equal(cooke_domain, hostname+hostname_len-cookie_domain_len))
+ if(!strcasecompare(cooke_domain, hostname + hostname_len-cookie_domain_len))
return FALSE;
/* A lead char of cookie_domain is not '.'.
* matching cookie path and url path
* RFC6265 5.1.4 Paths and Path-Match
*/
-static bool pathmatch(const char* cookie_path, const char* request_uri)
+static bool pathmatch(const char *cookie_path, const char *request_uri)
{
size_t cookie_path_len;
size_t uri_path_len;
- char* uri_path = NULL;
- char* pos;
+ char *uri_path = NULL;
+ char *pos;
bool ret = FALSE;
/* cookie_path must not have last '/' separator. ex: /sample */
while(co) {
nx = co->next;
if(co->expires && co->expires < now) {
- if(co == cookies->cookies) {
+ if(!pv) {
cookies->cookies = co->next;
}
else {
unless set */
{
struct Cookie *clist;
- char name[MAX_NAME];
struct Cookie *co;
- struct Cookie *lastc=NULL;
+ struct Cookie *lastc = NULL;
time_t now = time(NULL);
bool replace_old = FALSE;
bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
if(httpheader) {
/* This line was read off a HTTP-header */
+ char name[MAX_NAME];
+ char what[MAX_NAME];
const char *ptr;
const char *semiptr;
- char *what;
- what = malloc(MAX_COOKIE_LINE);
- if(!what) {
+ size_t linelength = strlen(lineptr);
+ if(linelength > MAX_COOKIE_LINE) {
+ /* discard overly long lines at once */
free(co);
return NULL;
}
- semiptr=strchr(lineptr, ';'); /* first, find a semicolon */
+ semiptr = strchr(lineptr, ';'); /* first, find a semicolon */
while(*lineptr && ISBLANK(*lineptr))
lineptr++;
ptr = lineptr;
do {
/* we have a <what>=<this> pair or a stand-alone word here */
- name[0]=what[0]=0; /* init the buffers */
+ name[0] = what[0] = 0; /* init the buffers */
if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n=] =%"
- MAX_COOKIE_LINE_TXT "[^;\r\n]",
+ MAX_NAME_TXT "[^;\r\n]",
name, what)) {
/* Use strstore() below to properly deal with received cookie
headers that have the same string property set more than once,
const char *whatptr;
bool done = FALSE;
bool sep;
- size_t len=strlen(what);
+ size_t len = strlen(what);
size_t nlen = strlen(name);
const char *endofn = &ptr[ nlen ];
+ if(nlen >= (MAX_NAME-1) || len >= (MAX_NAME-1) ||
+ ((nlen + len) > MAX_NAME)) {
+ /* too long individual name or contents, or too long combination of
+ name + contents. Chrome and Firefox support 4095 or 4096 bytes
+ combo. */
+ freecookie(co);
+ infof(data, "oversized cookie dropped, name/val %d + %d bytes\n",
+ nlen, len);
+ return NULL;
+ }
+
/* name ends with a '=' ? */
sep = (*endofn == '=')?TRUE:FALSE;
endofn--;
nlen--;
}
- name[nlen]=0; /* new end of name */
+ name[nlen] = 0; /* new end of name */
}
}
/* Strip off trailing whitespace from the 'what' */
while(len && ISBLANK(what[len-1])) {
- what[len-1]=0;
+ what[len-1] = 0;
len--;
}
/* Skip leading whitespace from the 'what' */
- whatptr=what;
+ whatptr = what;
while(*whatptr && ISBLANK(*whatptr))
whatptr++;
/* this was a "<name>=" with no content, and we must allow
'secure' and 'httponly' specified this weirdly */
done = TRUE;
- if(Curl_raw_equal("secure", name))
+ if(strcasecompare("secure", name))
co->secure = TRUE;
- else if(Curl_raw_equal("httponly", name))
+ else if(strcasecompare("httponly", name))
co->httponly = TRUE;
else if(sep)
/* there was a '=' so we're not done parsing this field */
}
if(done)
;
- else if(Curl_raw_equal("path", name)) {
+ else if(strcasecompare("path", name)) {
strstore(&co->path, whatptr);
if(!co->path) {
badcookie = TRUE; /* out of memory bad */
break;
}
+ free(co->spath); /* if this is set again */
co->spath = sanitize_cookie_path(co->path);
if(!co->spath) {
badcookie = TRUE; /* out of memory bad */
break;
}
}
- else if(Curl_raw_equal("domain", name)) {
+ else if(strcasecompare("domain", name)) {
bool is_ip;
- const char *dotp;
/* 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('.' == whatptr[0])
whatptr++; /* ignore preceding dot */
- is_ip = isip(domain ? domain : whatptr);
+#ifndef USE_LIBPSL
+ /*
+ * Without PSL we don't know when the incoming cookie is set on a
+ * TLD or otherwise "protected" suffix. To reduce risk, we require a
+ * dot OR the exact host name being "localhost".
+ */
+ {
+ const char *dotp;
+ /* check for more dots */
+ dotp = strchr(whatptr, '.');
+ if(!dotp && !strcasecompare("localhost", whatptr))
+ domain = ":";
+ }
+#endif
- /* check for more dots */
- dotp = strchr(whatptr, '.');
- if(!dotp)
- domain=":";
+ is_ip = isip(domain ? domain : whatptr);
if(!domain
|| (is_ip && !strcmp(whatptr, domain))
break;
}
if(!is_ip)
- co->tailmatch=TRUE; /* we always do that if the domain name was
- given */
+ 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. */
- badcookie=TRUE;
+ badcookie = TRUE;
infof(data, "skipped cookie with bad tailmatch domain: %s\n",
whatptr);
}
}
- else if(Curl_raw_equal("version", name)) {
+ else if(strcasecompare("version", name)) {
strstore(&co->version, whatptr);
if(!co->version) {
badcookie = TRUE;
break;
}
}
- else if(Curl_raw_equal("max-age", name)) {
+ else if(strcasecompare("max-age", name)) {
/* Defined in RFC2109:
Optional. The Max-Age attribute defines the lifetime of the
break;
}
}
- else if(Curl_raw_equal("expires", name)) {
+ else if(strcasecompare("expires", name)) {
strstore(&co->expirestr, whatptr);
if(!co->expirestr) {
badcookie = TRUE;
continue;
}
- ptr=semiptr+1;
+ ptr = semiptr + 1;
while(*ptr && ISBLANK(*ptr))
ptr++;
- semiptr=strchr(ptr, ';'); /* now, find the next semicolon */
+ semiptr = strchr(ptr, ';'); /* now, find the next semicolon */
if(!semiptr && *ptr)
/* There are no more semicolons, but there's a final name=value pair
coming up */
- semiptr=strchr(ptr, '\0');
+ semiptr = strchr(ptr, '\0');
} while(semiptr);
if(co->maxage) {
- co->expires =
- curlx_strtoofft((*co->maxage=='\"')?
- &co->maxage[1]:&co->maxage[0], NULL, 10);
- if(CURL_OFF_T_MAX - now < co->expires)
- /* avoid overflow */
+ CURLofft offt;
+ offt = curlx_strtoofft((*co->maxage == '\"')?
+ &co->maxage[1]:&co->maxage[0], NULL, 10,
+ &co->expires);
+ if(offt == CURL_OFFT_FLOW)
+ /* overflow, used max value */
co->expires = CURL_OFF_T_MAX;
- else
- co->expires += now;
+ else if(!offt) {
+ if(CURL_OFF_T_MAX - now < co->expires)
+ /* would overflow */
+ co->expires = CURL_OFF_T_MAX;
+ else
+ co->expires += now;
+ }
}
else if(co->expirestr) {
/* Note that if the date couldn't get parsed for whatever reason,
if(!badcookie && !co->domain) {
if(domain) {
/* no domain was given in the header line, set the default */
- co->domain=strdup(domain);
+ co->domain = strdup(domain);
if(!co->domain)
badcookie = TRUE;
}
else
endslash = memrchr(path, '/', (size_t)(queryp - path));
if(endslash) {
- size_t pathlen = (size_t)(endslash-path+1); /* include ending slash */
- co->path=malloc(pathlen+1); /* one extra for the zero byte */
+ size_t pathlen = (size_t)(endslash-path + 1); /* include end slash */
+ co->path = malloc(pathlen + 1); /* one extra for the zero byte */
if(co->path) {
memcpy(co->path, path, pathlen);
- co->path[pathlen]=0; /* zero terminate */
+ co->path[pathlen] = 0; /* zero terminate */
co->spath = sanitize_cookie_path(co->path);
if(!co->spath)
badcookie = TRUE; /* out of memory bad */
}
}
- free(what);
-
if(badcookie || !co->name) {
/* we didn't get a cookie name or a bad one,
this is an illegal line, bail out */
reading the odd netscape cookies-file format here */
char *ptr;
char *firstptr;
- char *tok_buf=NULL;
+ char *tok_buf = NULL;
int fields;
/* IE introduced HTTP-only cookies to prevent XSS attacks. Cookies
return NULL;
}
/* strip off the possible end-of-line characters */
- ptr=strchr(lineptr, '\r');
+ ptr = strchr(lineptr, '\r');
if(ptr)
- *ptr=0; /* clear it */
- ptr=strchr(lineptr, '\n');
+ *ptr = 0; /* clear it */
+ ptr = strchr(lineptr, '\n');
if(ptr)
- *ptr=0; /* clear it */
+ *ptr = 0; /* clear it */
- firstptr=strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */
+ firstptr = strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */
/* Now loop through the fields and init the struct we already have
allocated */
- for(ptr=firstptr, fields=0; ptr && !badcookie;
- ptr=strtok_r(NULL, "\t", &tok_buf), fields++) {
+ for(ptr = firstptr, fields = 0; ptr && !badcookie;
+ ptr = strtok_r(NULL, "\t", &tok_buf), fields++) {
switch(fields) {
case 0:
if(ptr[0]=='.') /* skip preceding dots */
As far as I can see, it is set to true when the cookie says
.domain.com and to false when the domain is complete www.domain.com
*/
- co->tailmatch = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE;
+ co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
break;
case 2:
/* It turns out, that sometimes the file format allows the path
fields++; /* add a field and fall down to secure */
/* FALLTHROUGH */
case 3:
- co->secure = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE;
+ co->secure = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
break;
case 4:
- co->expires = curlx_strtoofft(ptr, NULL, 10);
+ if(curlx_strtoofft(ptr, NULL, 10, &co->expires))
+ badcookie = TRUE;
break;
case 5:
co->name = strdup(ptr);
/* Check if the domain is a Public Suffix and if yes, ignore the cookie.
This needs a libpsl compiled with builtin data. */
if(domain && co->domain && !isip(co->domain)) {
- if(((psl = psl_builtin()) != NULL)
- && !psl_is_cookie_domain_acceptable(psl, domain, co->domain)) {
+ psl = psl_builtin();
+ if(psl && !psl_is_cookie_domain_acceptable(psl, domain, co->domain)) {
infof(data,
"cookie '%s' dropped, domain '%s' must not set cookies for '%s'\n",
co->name, domain, co->domain);
clist = c->cookies;
replace_old = FALSE;
while(clist) {
- if(Curl_raw_equal(clist->name, co->name)) {
+ if(strcasecompare(clist->name, co->name)) {
/* the names are identical */
if(clist->domain && co->domain) {
- if(Curl_raw_equal(clist->domain, co->domain))
+ if(strcasecompare(clist->domain, co->domain) &&
+ (clist->tailmatch == co->tailmatch))
/* The domains are identical */
- replace_old=TRUE;
+ replace_old = TRUE;
}
else if(!clist->domain && !co->domain)
replace_old = TRUE;
/* the domains were identical */
if(clist->spath && co->spath) {
- if(Curl_raw_equal(clist->spath, co->spath)) {
+ if(strcasecompare(clist->spath, co->spath)) {
replace_old = TRUE;
}
else
return co;
}
+/*
+ * get_line() makes sure to only return complete whole lines that fit in 'len'
+ * bytes and end with a newline.
+ */
+static char *get_line(char *buf, int len, FILE *input)
+{
+ bool partial = FALSE;
+ while(1) {
+ char *b = fgets(buf, len, input);
+ if(b) {
+ size_t rlen = strlen(b);
+ if(rlen && (b[rlen-1] == '\n')) {
+ if(partial) {
+ partial = FALSE;
+ continue;
+ }
+ return b;
+ }
+ /* read a partial, discard the next piece that ends with newline */
+ partial = TRUE;
+ }
+ else
+ break;
+ }
+ return NULL;
+}
+
+
/*****************************************************************************
*
* Curl_cookie_init()
{
struct CookieInfo *c;
FILE *fp = NULL;
- bool fromfile=TRUE;
+ bool fromfile = TRUE;
char *line = NULL;
if(NULL == inc) {
}
c->running = FALSE; /* this is not running, this is init */
- if(file && strequal(file, "-")) {
+ if(file && !strcmp(file, "-")) {
fp = stdin;
- fromfile=FALSE;
+ fromfile = FALSE;
}
else if(file && !*file) {
/* points to a "" string */
line = malloc(MAX_COOKIE_LINE);
if(!line)
goto fail;
- while(fgets(line, MAX_COOKIE_LINE, fp)) {
+ while(get_line(line, MAX_COOKIE_LINE, fp)) {
if(checkprefix("Set-Cookie:", line)) {
/* This is a cookie line, get it! */
- lineptr=&line[11];
- headerline=TRUE;
+ lineptr = &line[11];
+ headerline = TRUE;
}
else {
- lineptr=line;
- headerline=FALSE;
+ lineptr = line;
+ headerline = FALSE;
}
while(*lineptr && ISBLANK(*lineptr))
lineptr++;
return 0;
}
+#define CLONE(field) \
+ do { \
+ if(src->field) { \
+ d->field = strdup(src->field); \
+ if(!d->field) \
+ goto fail; \
+ } \
+ } while(0)
+
+static struct Cookie *dup_cookie(struct Cookie *src)
+{
+ struct Cookie *d = calloc(sizeof(struct Cookie), 1);
+ if(d) {
+ CLONE(expirestr);
+ CLONE(domain);
+ CLONE(path);
+ CLONE(spath);
+ CLONE(name);
+ CLONE(value);
+ CLONE(maxage);
+ CLONE(version);
+ d->expires = src->expires;
+ d->tailmatch = src->tailmatch;
+ d->secure = src->secure;
+ d->livecookie = src->livecookie;
+ d->httponly = src->httponly;
+ }
+ return d;
+
+ fail:
+ freecookie(d);
+ return NULL;
+}
+
/*****************************************************************************
*
* Curl_cookie_getlist()
struct Cookie *newco;
struct Cookie *co;
time_t now = time(NULL);
- struct Cookie *mainco=NULL;
+ struct Cookie *mainco = NULL;
size_t matches = 0;
bool is_ip;
/* now check if the domain is correct */
if(!co->domain ||
(co->tailmatch && !is_ip && tailmatch(co->domain, host)) ||
- ((!co->tailmatch || is_ip) && Curl_raw_equal(host, co->domain)) ) {
+ ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
/* the right part of the host matches the domain stuff in the
cookie data */
/* and now, we know this is a match and we should create an
entry for the return-linked-list */
- newco = malloc(sizeof(struct Cookie));
+ newco = dup_cookie(co);
if(newco) {
- /* first, copy the whole source cookie: */
- memcpy(newco, co, sizeof(struct Cookie));
-
/* then modify our next */
newco->next = mainco;
else {
fail:
/* failure, clear up the allocated chain and return NULL */
- while(mainco) {
- co = mainco->next;
- free(mainco);
- mainco = co;
- }
-
+ Curl_cookie_freelist(mainco);
return NULL;
}
}
co = mainco;
- for(i=0; co; co = co->next)
+ for(i = 0; co; co = co->next)
array[i++] = co;
/* now sort the cookie pointers in path length order */
/* remake the linked list order according to the new order */
mainco = array[0]; /* start here */
- for(i=0; i<matches-1; i++)
- array[i]->next = array[i+1];
+ for(i = 0; i<matches-1; i++)
+ array[i]->next = array[i + 1];
array[matches-1]->next = NULL; /* terminate the list */
free(array); /* remove the temporary data again */
void Curl_cookie_clearall(struct CookieInfo *cookies)
{
if(cookies) {
- Curl_cookie_freelist(cookies->cookies, TRUE);
+ Curl_cookie_freelist(cookies->cookies);
cookies->cookies = NULL;
cookies->numcookies = 0;
}
*
* Free a list of cookies previously returned by Curl_cookie_getlist();
*
- * The 'cookiestoo' argument tells this function whether to just free the
- * list or actually also free all cookies within the list as well.
- *
****************************************************************************/
-void Curl_cookie_freelist(struct Cookie *co, bool cookiestoo)
+void Curl_cookie_freelist(struct Cookie *co)
{
struct Cookie *next;
while(co) {
next = co->next;
- if(cookiestoo)
- freecookie(co);
- else
- free(co); /* we only free the struct since the "members" are all just
- pointed out in the main cookie list! */
+ freecookie(co);
co = next;
}
}
{
if(c) {
free(c->filename);
- Curl_cookie_freelist(c->cookies, TRUE);
+ Curl_cookie_freelist(c->cookies);
free(c); /* free the base struct as well */
}
}
{
struct Cookie *co;
FILE *out;
- bool use_stdout=FALSE;
+ bool use_stdout = FALSE;
char *format_ptr;
if((NULL == c) || (0 == c->numcookies))
/* at first, remove expired cookies */
remove_expired(c);
- if(strequal("-", dumphere)) {
+ if(!strcmp("-", dumphere)) {
/* use stdout */
out = stdout;
- use_stdout=TRUE;
+ use_stdout = TRUE;
}
else {
out = fopen(dumphere, FOPEN_WRITETEXT);
return 0;
}
-struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
+static struct curl_slist *cookie_list(struct Curl_easy *data)
{
struct curl_slist *list = NULL;
struct curl_slist *beg;
return list;
}
+struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
+{
+ struct curl_slist *list;
+ Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
+ list = cookie_list(data);
+ Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
+ return list;
+}
+
void Curl_flush_cookies(struct Curl_easy *data, int cleanup)
{
if(data->set.str[STRING_COOKIEJAR]) {