mirror of
https://github.com/curl/curl.git
synced 2026-08-24 16:53:34 +03:00
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:
parent
397088e8f4
commit
1aea05a6c2
3 changed files with 105 additions and 138 deletions
209
lib/cookie.c
209
lib/cookie.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue