cookie: convert to using strparse

- using strparse cleans up the code and makes it easier to read and follow
- remove ? handling never used - since the path is provided without queries nowadays
- simplify sanitize_cookie_path
- avoid the strdup in pathmatch()

Closes #16386
This commit is contained in:
Daniel Stenberg 2025-02-18 23:03:09 +01:00
parent 397088e8f4
commit 1aea05a6c2
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 105 additions and 138 deletions

View file

@ -159,12 +159,10 @@ static bool cookie_tailmatch(const char *cookie_domain,
* 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 *uri_path)
{
size_t cookie_path_len;
size_t uri_path_len;
char *uri_path = NULL;
char *pos;
bool ret = FALSE;
/* cookie_path must not have last '/' separator. ex: /sample */
@ -174,19 +172,9 @@ static bool pathmatch(const char *cookie_path, const char *request_uri)
return TRUE;
}
uri_path = strdup(request_uri);
if(!uri_path)
return FALSE;
pos = strchr(uri_path, '?');
if(pos)
*pos = 0x0;
/* #-fragments are already cut off! */
if(0 == strlen(uri_path) || uri_path[0] != '/') {
strstore(&uri_path, "/", 1);
if(!uri_path)
return FALSE;
}
if(0 == strlen(uri_path) || uri_path[0] != '/')
uri_path = "/";
/*
* here, RFC6265 5.1.4 says
@ -200,16 +188,12 @@ static bool pathmatch(const char *cookie_path, const char *request_uri)
uri_path_len = strlen(uri_path);
if(uri_path_len < cookie_path_len) {
ret = FALSE;
if(uri_path_len < cookie_path_len)
goto pathmatched;
}
/* not using checkprefix() because matching should be case-sensitive */
if(strncmp(cookie_path, uri_path, cookie_path_len)) {
ret = FALSE;
if(strncmp(cookie_path, uri_path, cookie_path_len))
goto pathmatched;
}
/* The cookie-path and the uri-path are identical. */
if(cookie_path_len == uri_path_len) {
@ -223,10 +207,7 @@ static bool pathmatch(const char *cookie_path, const char *request_uri)
goto pathmatched;
}
ret = FALSE;
pathmatched:
free(uri_path);
return ret;
}
@ -300,34 +281,27 @@ static size_t cookiehash(const char * const domain)
*/
static char *sanitize_cookie_path(const char *cookie_path)
{
size_t len;
char *new_path = strdup(cookie_path);
if(!new_path)
return NULL;
size_t len = strlen(cookie_path);
/* some stupid site sends path attribute with '"'. */
len = strlen(new_path);
if(new_path[0] == '\"') {
memmove(new_path, new_path + 1, len);
/* some sites send path attribute within '"'. */
if(cookie_path[0] == '\"') {
cookie_path++;
len--;
}
if(len && (new_path[len - 1] == '\"')) {
new_path[--len] = 0x0;
}
if(len && (cookie_path[len - 1] == '\"'))
len--;
/* RFC6265 5.2.4 The Path Attribute */
if(new_path[0] != '/') {
if(cookie_path[0] != '/')
/* Let cookie-path be the default-path. */
strstore(&new_path, "/", 1);
return new_path;
}
return strdup("/");
/* remove trailing slash */
/* convert /hoge/ to /hoge */
if(len && new_path[len - 1] == '/') {
new_path[len - 1] = 0x0;
}
if(len && cookie_path[len - 1] == '/')
len--;
return new_path;
return Curl_memdup0(cookie_path, len);
}
/*
@ -369,9 +343,12 @@ void Curl_cookie_loadfiles(struct Curl_easy *data)
*/
static void strstore(char **str, const char *newstr, size_t len)
{
DEBUGASSERT(newstr);
DEBUGASSERT(str);
free(*str);
if(!len) {
len++;
newstr = "";
}
*str = Curl_memdup0(newstr, len);
}
@ -515,50 +492,30 @@ parse_cookie_header(struct Curl_easy *data,
now = time(NULL);
do {
size_t vlen;
size_t nlen;
while(ISBLANK(*ptr))
ptr++;
struct Curl_str name;
struct Curl_str val;
/* we have a <name>=<value> pair or a stand-alone word here */
nlen = strcspn(ptr, ";\t\r\n=");
if(nlen) {
if(!Curl_str_cspn(&ptr, &name, ";\t\r\n=")) {
bool done = FALSE;
bool sep = FALSE;
const char *namep = ptr;
const char *valuep;
Curl_str_trimblanks(&name);
ptr += nlen;
if(!Curl_str_single(&ptr, '=')) {
sep = TRUE; /* a '=' was used */
if(!Curl_str_cspn(&ptr, &val, ";\r\n")) {
Curl_str_trimblanks(&val);
/* trim trailing spaces and tabs after name */
while(nlen && ISBLANK(namep[nlen - 1]))
nlen--;
if(*ptr == '=') {
ptr++;
/* Skip spaces and tabs before the value */
while(ISBLANK(*ptr))
ptr++;
vlen = strcspn(ptr, ";\r\n");
valuep = ptr;
sep = TRUE;
ptr = &valuep[vlen];
/* Strip off trailing whitespace from the value */
while(vlen && ISBLANK(valuep[vlen-1]))
vlen--;
/* Reject cookies with a TAB inside the value */
if(memchr(valuep, '\t', vlen)) {
infof(data, "cookie contains TAB, dropping");
return CERR_TAB;
/* Reject cookies with a TAB inside the value */
if(memchr(val.str, '\t', val.len)) {
infof(data, "cookie contains TAB, dropping");
return CERR_TAB;
}
}
}
else {
valuep = NULL;
vlen = 0;
val.str = NULL;
val.len = 0;
}
/*
@ -566,10 +523,10 @@ parse_cookie_header(struct Curl_easy *data,
* combination of name + contents. Chrome and Firefox support 4095 or
* 4096 bytes combo
*/
if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) ||
((nlen + vlen) > MAX_NAME)) {
if(name.len >= (MAX_NAME-1) || val.len >= (MAX_NAME-1) ||
((name.len + val.len) > MAX_NAME)) {
infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
nlen, vlen);
name.len, val.len);
return CERR_TOO_BIG;
}
@ -579,12 +536,10 @@ parse_cookie_header(struct Curl_easy *data,
* "the rest". Prefixes must start with '__' and end with a '-', so
* only test for names where that can possibly be true.
*/
if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') {
if(strncasecompare("__Secure-", namep, 9))
co->prefix_secure = TRUE;
else if(strncasecompare("__Host-", namep, 7))
co->prefix_host = TRUE;
}
if(strncasecompare("__Secure-", name.str, 9))
co->prefix_secure = TRUE;
else if(strncasecompare("__Host-", name.str, 7))
co->prefix_host = TRUE;
/*
* Use strstore() below to properly deal with received cookie
@ -598,8 +553,8 @@ parse_cookie_header(struct Curl_easy *data,
/* Bad name/value pair. */
return CERR_NO_SEP;
strstore(&co->name, namep, nlen);
strstore(&co->value, valuep, vlen);
strstore(&co->name, name.str, name.len);
strstore(&co->value, val.str, val.len);
done = TRUE;
if(!co->name || !co->value)
return CERR_NO_NAME_VALUE;
@ -609,7 +564,7 @@ parse_cookie_header(struct Curl_easy *data,
return CERR_INVALID_OCTET;
}
}
else if(!vlen) {
else if(!val.len) {
/*
* this was a "<name>=" with no content, and we must allow
* 'secure' and 'httponly' specified this weirdly
@ -620,7 +575,7 @@ parse_cookie_header(struct Curl_easy *data,
* using a secure protocol, or when the cookie is being set by
* reading from file
*/
if((nlen == 6) && strncasecompare("secure", namep, 6)) {
if(Curl_str_casecompare(&name, "secure")) {
if(secure || !ci->running) {
co->secure = TRUE;
}
@ -628,7 +583,7 @@ parse_cookie_header(struct Curl_easy *data,
return CERR_BAD_SECURE;
}
}
else if((nlen == 8) && strncasecompare("httponly", namep, 8))
else if(Curl_str_casecompare(&name, "httponly"))
co->httponly = TRUE;
else if(sep)
/* there was a '=' so we are not done parsing this field */
@ -636,8 +591,8 @@ parse_cookie_header(struct Curl_easy *data,
}
if(done)
;
else if((nlen == 4) && strncasecompare("path", namep, 4)) {
strstore(&co->path, valuep, vlen);
else if(Curl_str_casecompare(&name, "path")) {
strstore(&co->path, val.str, val.len);
if(!co->path)
return CERR_OUT_OF_MEMORY;
free(co->spath); /* if this is set again */
@ -645,8 +600,7 @@ parse_cookie_header(struct Curl_easy *data,
if(!co->spath)
return CERR_OUT_OF_MEMORY;
}
else if((nlen == 6) &&
strncasecompare("domain", namep, 6) && vlen) {
else if(Curl_str_casecompare(&name, "domain") && val.len) {
bool is_ip;
/*
@ -654,10 +608,8 @@ parse_cookie_header(struct Curl_easy *data,
* the given domain is not valid and thus cannot be set.
*/
if('.' == valuep[0]) {
valuep++; /* ignore preceding dot */
vlen--;
}
if('.' == val.str[0])
Curl_str_nudge(&val, 1);
#ifndef USE_LIBPSL
/*
@ -665,17 +617,17 @@ parse_cookie_header(struct Curl_easy *data,
* TLD or otherwise "protected" suffix. To reduce risk, we require a
* dot OR the exact hostname being "localhost".
*/
if(bad_domain(valuep, vlen))
if(bad_domain(val.str, val.len))
domain = ":";
#endif
is_ip = Curl_host_is_ipnum(domain ? domain : valuep);
is_ip = Curl_host_is_ipnum(domain ? domain : val.str);
if(!domain
|| (is_ip && !strncmp(valuep, domain, vlen) &&
(vlen == strlen(domain)))
|| (!is_ip && cookie_tailmatch(valuep, vlen, domain))) {
strstore(&co->domain, valuep, vlen);
|| (is_ip && !strncmp(val.str, domain, val.len) &&
(val.len == strlen(domain)))
|| (!is_ip && cookie_tailmatch(val.str, val.len, domain))) {
strstore(&co->domain, val.str, val.len);
if(!co->domain)
return CERR_OUT_OF_MEMORY;
@ -689,14 +641,14 @@ parse_cookie_header(struct Curl_easy *data,
* not a domain to which the current host belongs. Mark as bad.
*/
infof(data, "skipped cookie with bad tailmatch domain: %s",
valuep);
val.str);
return CERR_NO_TAILMATCH;
}
}
else if((nlen == 7) && strncasecompare("version", namep, 7)) {
else if(Curl_str_casecompare(&name, "version")) {
/* just ignore */
}
else if((nlen == 7) && strncasecompare("max-age", namep, 7)) {
else if(Curl_str_casecompare(&name, "max-age") && val.len) {
/*
* Defined in RFC2109:
*
@ -707,7 +659,7 @@ parse_cookie_header(struct Curl_easy *data,
* cookie should be discarded immediately.
*/
int rc;
const char *maxage = valuep;
const char *maxage = val.str;
if(*maxage == '\"')
maxage++;
rc = Curl_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
@ -734,8 +686,8 @@ parse_cookie_header(struct Curl_easy *data,
}
cap_expires(now, co);
}
else if((nlen == 7) && strncasecompare("expires", namep, 7)) {
if(!co->expires && (vlen < MAX_DATE_LENGTH)) {
else if(Curl_str_casecompare(&name, "expires") && val.len) {
if(!co->expires && (val.len < MAX_DATE_LENGTH)) {
/*
* Let max-age have priority.
*
@ -743,8 +695,8 @@ parse_cookie_header(struct Curl_easy *data,
* will be treated as a session cookie
*/
char dbuf[MAX_DATE_LENGTH + 1];
memcpy(dbuf, valuep, vlen);
dbuf[vlen] = 0;
memcpy(dbuf, val.str, val.len);
dbuf[val.len] = 0;
co->expires = Curl_getdate_capped(dbuf);
/*
@ -764,15 +716,8 @@ parse_cookie_header(struct Curl_easy *data,
* Else, this is the second (or more) name we do not know about!
*/
}
else {
/* this is an "illegal" <what>=<this> pair */
}
while(ISBLANK(*ptr))
ptr++;
if(*ptr == ';')
ptr++;
else
if(Curl_str_single(&ptr, ';'))
break;
} while(1);
@ -785,23 +730,11 @@ parse_cookie_header(struct Curl_easy *data,
if(!co->path && path) {
/*
* No path was given in the header line, set the default. Note that the
* passed-in path to this function MAY have a '?' and following part that
* MUST NOT be stored as part of the path.
* No path was given in the header line, set the default.
*/
char *queryp = strchr(path, '?');
/*
* queryp is where the interesting part of the path ends, so now we
* want to the find the last
*/
char *endslash;
if(!queryp)
endslash = strrchr(path, '/');
else
endslash = memrchr(path, '/', (queryp - path));
const char *endslash = strrchr(path, '/');
if(endslash) {
size_t pathlen = (endslash-path + 1); /* include end slash */
size_t pathlen = (endslash - path + 1); /* include end slash */
co->path = Curl_memdup0(path, pathlen);
if(co->path) {
co->spath = sanitize_cookie_path(co->path);

View file

@ -210,3 +210,34 @@ int Curl_str_nudge(struct Curl_str *str, size_t num)
}
return STRE_OVERFLOW;
}
/* Get the following character sequence that consists only of bytes not
present in the 'reject' string. Like strcspn(). */
int Curl_str_cspn(const char **linep, struct Curl_str *out, const char *reject)
{
const char *s = *linep;
size_t len;
DEBUGASSERT(linep && *linep);
len = strcspn(s, reject);
if(len) {
out->str = s;
out->len = len;
*linep = &s[len];
return STRE_OK;
}
out->str = NULL;
out->len = 0;
return STRE_SHORT;
}
/* remove ISBLANK()s from both ends of the string */
void Curl_str_trimblanks(struct Curl_str *out)
{
while(out->len && ISBLANK(*out->str))
Curl_str_nudge(out, 1);
/* trim trailing spaces and tabs */
while(out->len && ISBLANK(out->str[out->len - 1]))
out->len--;
}

View file

@ -81,4 +81,7 @@ int Curl_str_casecompare(struct Curl_str *str, const char *check);
int Curl_str_nudge(struct Curl_str *str, size_t num);
int Curl_str_cspn(const char **linep, struct Curl_str *out, const char *cspn);
void Curl_str_trimblanks(struct Curl_str *out);
#endif /* HEADER_CURL_STRPARSE_H */