cookie: only keep and use the canonical cleaned up path

Instead of keeping both versions around.

Closes #19864
This commit is contained in:
Daniel Stenberg 2025-12-07 16:09:13 +01:00
parent 524936fbeb
commit a093c93994
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
11 changed files with 116 additions and 125 deletions

View file

@ -67,7 +67,6 @@ static void freecookie(struct Cookie *co, bool maintoo)
{
curlx_free(co->domain);
curlx_free(co->path);
curlx_free(co->spath);
curlx_free(co->name);
curlx_free(co->value);
if(maintoo)
@ -227,20 +226,19 @@ static size_t cookiehash(const char * const domain)
/*
* cookie path sanitize
*/
static char *sanitize_cookie_path(const char *cookie_path)
static char *sanitize_cookie_path(const char *cookie_path, size_t len)
{
size_t len = strlen(cookie_path);
/* some sites send path attribute within '"'. */
if(cookie_path[0] == '\"') {
if(len && (cookie_path[0] == '\"')) {
cookie_path++;
len--;
if(len && (cookie_path[len - 1] == '\"'))
len--;
}
if(len && (cookie_path[len - 1] == '\"'))
len--;
/* RFC6265 5.2.4 The Path Attribute */
if(cookie_path[0] != '/')
if(!len || (cookie_path[0] != '/'))
/* Let cookie-path be the default-path. */
return curlx_strdup("/");
@ -393,23 +391,23 @@ static CURLcode storecookie(struct Cookie *co, struct Curl_str *cp,
result = strstore(&co->value, curlx_str(&cp[COOKIE_VALUE]),
curlx_strlen(&cp[COOKIE_VALUE]));
if(!result) {
if(curlx_strlen(&cp[COOKIE_PATH]))
result = strstore(&co->path, curlx_str(&cp[COOKIE_PATH]),
curlx_strlen(&cp[COOKIE_PATH]));
size_t plen = 0;
if(curlx_strlen(&cp[COOKIE_PATH])) {
path = curlx_str(&cp[COOKIE_PATH]);
plen = curlx_strlen(&cp[COOKIE_PATH]);
}
else if(path) {
/* No path was given in the header line, set the default */
const char *endslash = strrchr(path, '/');
if(endslash) {
size_t pathlen = (endslash - path + 1); /* include end slash */
co->path = Curl_memdup0(path, pathlen);
if(!co->path)
result = CURLE_OUT_OF_MEMORY;
}
if(endslash)
plen = (endslash - path + 1); /* include end slash */
else
plen = strlen(path);
}
if(!result && co->path) {
co->spath = sanitize_cookie_path(co->path);
if(!co->spath)
if(path) {
co->path = sanitize_cookie_path(path, plen);
if(!co->path)
result = CURLE_OUT_OF_MEMORY;
}
}
@ -734,23 +732,17 @@ static CURLcode parse_netscape(struct Cookie *co,
/* The file format allows the path field to remain not filled in */
if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {
/* only if the path does not look like a boolean option! */
co->path = Curl_memdup0(ptr, len);
co->path = sanitize_cookie_path(ptr, len);
if(!co->path)
return CURLE_OUT_OF_MEMORY;
else {
co->spath = sanitize_cookie_path(co->path);
if(!co->spath)
return CURLE_OUT_OF_MEMORY;
}
break;
}
/* this does not look like a path, make one up! */
co->path = curlx_strdup("/");
if(!co->path)
return CURLE_OUT_OF_MEMORY;
co->spath = curlx_strdup("/");
if(!co->spath)
return CURLE_OUT_OF_MEMORY;
else {
/* this does not look like a path, make one up! */
co->path = curlx_strdup("/");
if(!co->path)
return CURLE_OUT_OF_MEMORY;
}
fields++; /* add a field and fall down to secure */
FALLTHROUGH();
case 3:
@ -875,7 +867,7 @@ static bool replace_existing(struct Curl_easy *data,
matching_domains = TRUE;
if(matching_domains && /* the domains were identical */
clist->spath && co->spath && /* both have paths */
clist->path && co->path && /* both have paths */
clist->secure && !co->secure && !secure) {
size_t cllen;
const char *sep = NULL;
@ -887,15 +879,15 @@ static bool replace_existing(struct Curl_easy *data,
* "/loginhelper" is ok.
*/
DEBUGASSERT(clist->spath[0]);
if(clist->spath[0])
sep = strchr(clist->spath + 1, '/');
DEBUGASSERT(clist->path[0]);
if(clist->path[0])
sep = strchr(clist->path + 1, '/');
if(sep)
cllen = sep - clist->spath;
cllen = sep - clist->path;
else
cllen = strlen(clist->spath);
cllen = strlen(clist->path);
if(curl_strnequal(clist->spath, co->spath, cllen)) {
if(curl_strnequal(clist->path, co->path, cllen)) {
infof(data, "cookie '%s' for domain '%s' dropped, would "
"overlay an existing cookie", co->name, co->domain);
return FALSE;
@ -918,10 +910,10 @@ static bool replace_existing(struct Curl_easy *data,
if(replace_old) {
/* the domains were identical */
if(clist->spath && co->spath &&
!curl_strequal(clist->spath, co->spath))
if(clist->path && co->path &&
!curl_strequal(clist->path, co->path))
replace_old = FALSE;
else if(!clist->spath != !co->spath)
else if(!clist->path != !co->path)
replace_old = FALSE;
}
@ -1007,7 +999,7 @@ Curl_cookie_add(struct Curl_easy *data,
* The __Host- prefix requires the cookie to be secure, have a "/" path
* and not have a domain set.
*/
if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
if(co->secure && co->path && !strcmp(co->path, "/") && !co->tailmatch)
;
else
goto fail;
@ -1324,7 +1316,7 @@ CURLcode Curl_cookie_getlist(struct Curl_easy *data,
* now check the left part of the path with the cookies path
* requirement
*/
if(!co->spath || pathmatch(co->spath, path)) {
if(!co->path || pathmatch(co->path, path)) {
/*
* This is a match and we add it to the return-linked-list

View file

@ -34,8 +34,7 @@ struct Cookie {
struct Curl_llist_node getnode; /* for getlist */
char *name; /* <this> = value */
char *value; /* name = <this> */
char *path; /* path = <this> which is in Set-Cookie: */
char *spath; /* sanitized cookie path */
char *path; /* canonical path */
char *domain; /* domain = <this> */
curl_off_t expires; /* expires = <this> */
unsigned int creationtime; /* time when the cookie was written */

View file

@ -61,8 +61,8 @@ userid=myname&password=mypassword
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE "/silly/" FALSE 0 mismatch this
127.0.0.1 FALSE /we/want/ FALSE 0 foobar name
127.0.0.1 FALSE /silly FALSE 0 mismatch this
127.0.0.1 FALSE /we/want FALSE 0 foobar name
</file>
</verify>
</testcase>

View file

@ -98,7 +98,7 @@ Accept: */*
www.example.com FALSE / TRUE 0 __Host-SID 12346
.example.com TRUE / TRUE 0 supersupersuper secret
.example.com TRUE / TRUE 0 __SecURE-SID 12346
.example.com TRUE /%TESTNUMBER/login/ TRUE 0 supersuper secret
.example.com TRUE /%TESTNUMBER/login TRUE 0 supersuper secret
.example.com TRUE /1561 TRUE 0 super secret
</file>

View file

@ -55,8 +55,8 @@ cookies
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
%HOSTIP FALSE /we/want/ FALSE 0 foobar name
%HOSTIP FALSE /we/want/ FALSE 0 secondcookie present
%HOSTIP FALSE /we/want FALSE 0 foobar name
%HOSTIP FALSE /we/want FALSE 0 secondcookie present
</file>
</verify>
</testcase>

View file

@ -53,8 +53,8 @@ Accept: */*
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
%HOSTIP FALSE /we/want/ FALSE 0 secondcookie present
%HOSTIP FALSE /we/want/ FALSE 0 foobar name
%HOSTIP FALSE /we/want FALSE 0 secondcookie present
%HOSTIP FALSE /we/want FALSE 0 foobar name
</file>
</verify>
</testcase>

View file

@ -111,22 +111,22 @@ Accept: */*
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
test31.curl FALSE /we/want/ FALSE 0 %hex[%c3%82%c2%b3%c3%83%5c%78%39%32%c3%83%5c%78%39%61%c3%83%5c%78%38%64%c3%83%5c%78%39%37]hex% %96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A
test31.curl FALSE /we/want/ FALSE 0 prespace yes before
test31.curl FALSE /we/want/ FALSE 0 withspaces2 before equals
test31.curl FALSE /we/want/ FALSE 0 withspaces yes within and around
.test31.curl TRUE /we/want/ FALSE 0 blexp yesyes
#HttpOnly_test31.curl FALSE /silly/ FALSE 0 magic yessir
test31.curl FALSE /we/want/ FALSE %days[400] nodomain value
test31.curl FALSE /we/want FALSE 0 %hex[%c3%82%c2%b3%c3%83%5c%78%39%32%c3%83%5c%78%39%61%c3%83%5c%78%38%64%c3%83%5c%78%39%37]hex% %96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A
test31.curl FALSE /we/want FALSE 0 prespace yes before
test31.curl FALSE /we/want FALSE 0 withspaces2 before equals
test31.curl FALSE /we/want FALSE 0 withspaces yes within and around
.test31.curl TRUE /we/want FALSE 0 blexp yesyes
#HttpOnly_test31.curl FALSE /silly FALSE 0 magic yessir
test31.curl FALSE /we/want FALSE %days[400] nodomain value
.test31.curl TRUE / FALSE 0 partmatch present
#HttpOnly_.test31.curl TRUE /p4/ FALSE 0 httponly myvalue1
#HttpOnly_.test31.curl TRUE /p4/ FALSE 0 httpo4 value4
#HttpOnly_.test31.curl TRUE /p3/ FALSE 0 httpo3 value3
#HttpOnly_.test31.curl TRUE /p2/ FALSE 0 httpo2 value2
#HttpOnly_.test31.curl TRUE /p1/ FALSE 0 httpo1 value1
#HttpOnly_.test31.curl TRUE /p4 FALSE 0 httponly myvalue1
#HttpOnly_.test31.curl TRUE /p4 FALSE 0 httpo4 value4
#HttpOnly_.test31.curl TRUE /p3 FALSE 0 httpo3 value3
#HttpOnly_.test31.curl TRUE /p2 FALSE 0 httpo2 value2
#HttpOnly_.test31.curl TRUE /p1 FALSE 0 httpo1 value1
.test31.curl TRUE /overwrite FALSE 0 overwrite this2
.test31.curl TRUE /silly/ FALSE 0 ISMATCH this
.test31.curl TRUE /silly/ FALSE 0 ismatch this
.test31.curl TRUE /silly FALSE 0 ISMATCH this
.test31.curl TRUE /silly FALSE 0 ismatch this
test31.curl FALSE / FALSE 0 blankdomain sure
</file>
</verify>

View file

@ -44,9 +44,9 @@ http://%HOSTIP:%HTTPPORT/func_test/del_cookie -b %LOGDIR/cookie%TESTNUMBER -c %L
#HttpOnly_%HOSTIP FALSE /func_test FALSE 21709598616 mycookie5 990
#HttpOnly_%HOSTIP FALSE /func_test FALSE 21709598616 mycookie4 950
#HttpOnly_%HOSTIP FALSE /func_test FALSE 21709598616 mycookie3 900
#HttpOnly_%HOSTIP FALSE /func_test/ FALSE 21709598616 mycookie2 5900
#HttpOnly_%HOSTIP FALSE /func_test FALSE 21709598616 mycookie2 5900
#HttpOnly_%HOSTIP FALSE / FALSE 21709598616 mycookie1 4900
#HttpOnly_%HOSTIP FALSE /func_test/ FALSE 0 mycookie 1200
#HttpOnly_%HOSTIP FALSE /func_test FALSE 0 mycookie 1200
</file>
<features>
cookies
@ -61,7 +61,7 @@ GET /func_test/del_cookie HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
Cookie: mycookie2=5900; mycookie=1200; mycookie3=900; mycookie4=950; mycookie5=990; mycookie6=991; mycookie1=4900
Cookie: mycookie2=5900; mycookie3=900; mycookie4=950; mycookie5=990; mycookie6=991; mycookie=1200; mycookie1=4900
</protocol>
<file name="%LOGDIR/save%TESTNUMBER" mode="text">
@ -69,7 +69,7 @@ Cookie: mycookie2=5900; mycookie=1200; mycookie3=900; mycookie4=950; mycookie5=9
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE /func_test/ FALSE 21709598616 mycookie2 5900
#HttpOnly_127.0.0.1 FALSE /func_test FALSE 21709598616 mycookie2 5900
</file>
</verify>
</testcase>

View file

@ -138,56 +138,56 @@ Accept: */*
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
attack.invalid FALSE /a/b/ FALSE 0 cookie-50 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-49 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-48 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-47 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-46 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-45 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-44 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-43 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-42 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-41 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-40 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-39 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-38 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-37 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-36 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-35 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-34 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-33 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-32 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-31 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-30 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-29 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-28 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-27 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-26 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-25 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-24 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-23 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-22 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-21 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-20 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-19 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-18 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-17 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-16 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-15 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-14 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-13 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-12 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-11 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-10 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-9 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-8 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-7 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-6 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-5 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-4 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-3 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-2 yes
attack.invalid FALSE /a/b/ FALSE 0 cookie-1 yes
attack.invalid FALSE /a/b FALSE 0 cookie-50 yes
attack.invalid FALSE /a/b FALSE 0 cookie-49 yes
attack.invalid FALSE /a/b FALSE 0 cookie-48 yes
attack.invalid FALSE /a/b FALSE 0 cookie-47 yes
attack.invalid FALSE /a/b FALSE 0 cookie-46 yes
attack.invalid FALSE /a/b FALSE 0 cookie-45 yes
attack.invalid FALSE /a/b FALSE 0 cookie-44 yes
attack.invalid FALSE /a/b FALSE 0 cookie-43 yes
attack.invalid FALSE /a/b FALSE 0 cookie-42 yes
attack.invalid FALSE /a/b FALSE 0 cookie-41 yes
attack.invalid FALSE /a/b FALSE 0 cookie-40 yes
attack.invalid FALSE /a/b FALSE 0 cookie-39 yes
attack.invalid FALSE /a/b FALSE 0 cookie-38 yes
attack.invalid FALSE /a/b FALSE 0 cookie-37 yes
attack.invalid FALSE /a/b FALSE 0 cookie-36 yes
attack.invalid FALSE /a/b FALSE 0 cookie-35 yes
attack.invalid FALSE /a/b FALSE 0 cookie-34 yes
attack.invalid FALSE /a/b FALSE 0 cookie-33 yes
attack.invalid FALSE /a/b FALSE 0 cookie-32 yes
attack.invalid FALSE /a/b FALSE 0 cookie-31 yes
attack.invalid FALSE /a/b FALSE 0 cookie-30 yes
attack.invalid FALSE /a/b FALSE 0 cookie-29 yes
attack.invalid FALSE /a/b FALSE 0 cookie-28 yes
attack.invalid FALSE /a/b FALSE 0 cookie-27 yes
attack.invalid FALSE /a/b FALSE 0 cookie-26 yes
attack.invalid FALSE /a/b FALSE 0 cookie-25 yes
attack.invalid FALSE /a/b FALSE 0 cookie-24 yes
attack.invalid FALSE /a/b FALSE 0 cookie-23 yes
attack.invalid FALSE /a/b FALSE 0 cookie-22 yes
attack.invalid FALSE /a/b FALSE 0 cookie-21 yes
attack.invalid FALSE /a/b FALSE 0 cookie-20 yes
attack.invalid FALSE /a/b FALSE 0 cookie-19 yes
attack.invalid FALSE /a/b FALSE 0 cookie-18 yes
attack.invalid FALSE /a/b FALSE 0 cookie-17 yes
attack.invalid FALSE /a/b FALSE 0 cookie-16 yes
attack.invalid FALSE /a/b FALSE 0 cookie-15 yes
attack.invalid FALSE /a/b FALSE 0 cookie-14 yes
attack.invalid FALSE /a/b FALSE 0 cookie-13 yes
attack.invalid FALSE /a/b FALSE 0 cookie-12 yes
attack.invalid FALSE /a/b FALSE 0 cookie-11 yes
attack.invalid FALSE /a/b FALSE 0 cookie-10 yes
attack.invalid FALSE /a/b FALSE 0 cookie-9 yes
attack.invalid FALSE /a/b FALSE 0 cookie-8 yes
attack.invalid FALSE /a/b FALSE 0 cookie-7 yes
attack.invalid FALSE /a/b FALSE 0 cookie-6 yes
attack.invalid FALSE /a/b FALSE 0 cookie-5 yes
attack.invalid FALSE /a/b FALSE 0 cookie-4 yes
attack.invalid FALSE /a/b FALSE 0 cookie-3 yes
attack.invalid FALSE /a/b FALSE 0 cookie-2 yes
attack.invalid FALSE /a/b FALSE 0 cookie-1 yes
</file>
</verify>
</testcase>

View file

@ -87,7 +87,7 @@ Cookie: empty=; mooo2=indeed2; mooo=indeed
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
domain..tld FALSE /want/ FALSE 0 simplyhuge %repeat[3998 x z]%
domain..tld FALSE /want FALSE 0 simplyhuge %repeat[3998 x z]%
domain..tld FALSE / FALSE 0 justaname%TAB
domain..tld FALSE / FALSE 0 ASPSESSIONIDQGGQQSJJ GKNBDIFAAOFDPDAIEAKDIBKE
domain..tld FALSE / FALSE 0 ckySession temporary

View file

@ -71,8 +71,8 @@ Accept: */*
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
.host.foo.com TRUE /we/want/ FALSE %days[400] test2 yes
#HttpOnly_.foo.com TRUE /we/want/ FALSE %days[400] test yes
.host.foo.com TRUE /we/want FALSE %days[400] test2 yes
#HttpOnly_.foo.com TRUE /we/want FALSE %days[400] test yes
</file>
</verify>
</testcase>