1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 /***
26
27
28 RECEIVING COOKIE INFORMATION
29 ============================
30
31 Curl_cookie_init()
32
33 Inits a cookie struct to store data in a local file. This is always
34 called before any cookies are set.
35
36 Curl_cookie_add()
37
38 Adds a cookie to the in-memory cookie jar.
39
40
41 SENDING COOKIE INFORMATION
42 ==========================
43
44 Curl_cookie_getlist()
45
46 For a given host and path, return a linked list of cookies that
47 the client should send to the server if used now. The secure
48 boolean informs the cookie if a secure connection is achieved or
49 not.
50
51 It shall only return cookies that have not expired.
52
53 Example set of cookies:
54
55 Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure
56 Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
57 domain=.fidelity.com; path=/ftgw; secure
58 Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
59 domain=.fidelity.com; path=/; secure
60 Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
61 domain=.fidelity.com; path=/; secure
62 Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
63 domain=.fidelity.com; path=/; secure
64 Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
65 domain=.fidelity.com; path=/; secure
66 Set-cookie:
67 Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
68 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
69 ****/
70
71
72 #include "curl_setup.h"
73
74 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
75
76 #include "urldata.h"
77 #include "cookie.h"
78 #include "psl.h"
79 #include "strtok.h"
80 #include "sendf.h"
81 #include "slist.h"
82 #include "share.h"
83 #include "strtoofft.h"
84 #include "strcase.h"
85 #include "curl_get_line.h"
86 #include "curl_memrchr.h"
87 #include "parsedate.h"
88 #include "rename.h"
89 #include "fopen.h"
90 #include "strdup.h"
91 #include "llist.h"
92
93 /* The last 3 #include files should be in this order */
94 #include "curl_printf.h"
95 #include "curl_memory.h"
96 #include "memdebug.h"
97
98 static void strstore(char **str, const char *newstr, size_t len);
99
freecookie(struct Cookie * co)100 static void freecookie(struct Cookie *co)
101 {
102 free(co->domain);
103 free(co->path);
104 free(co->spath);
105 free(co->name);
106 free(co->value);
107 free(co);
108 }
109
cookie_tailmatch(const char * cookie_domain,size_t cookie_domain_len,const char * hostname)110 static bool cookie_tailmatch(const char *cookie_domain,
111 size_t cookie_domain_len,
112 const char *hostname)
113 {
114 size_t hostname_len = strlen(hostname);
115
116 if(hostname_len < cookie_domain_len)
117 return FALSE;
118
119 if(!strncasecompare(cookie_domain,
120 hostname + hostname_len-cookie_domain_len,
121 cookie_domain_len))
122 return FALSE;
123
124 /*
125 * A lead char of cookie_domain is not '.'.
126 * RFC6265 4.1.2.3. The Domain Attribute says:
127 * For example, if the value of the Domain attribute is
128 * "example.com", the user agent will include the cookie in the Cookie
129 * header when making HTTP requests to example.com, www.example.com, and
130 * www.corp.example.com.
131 */
132 if(hostname_len == cookie_domain_len)
133 return TRUE;
134 if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
135 return TRUE;
136 return FALSE;
137 }
138
139 /*
140 * matching cookie path and URL path
141 * RFC6265 5.1.4 Paths and Path-Match
142 */
pathmatch(const char * cookie_path,const char * request_uri)143 static bool pathmatch(const char *cookie_path, const char *request_uri)
144 {
145 size_t cookie_path_len;
146 size_t uri_path_len;
147 char *uri_path = NULL;
148 char *pos;
149 bool ret = FALSE;
150
151 /* cookie_path must not have last '/' separator. ex: /sample */
152 cookie_path_len = strlen(cookie_path);
153 if(1 == cookie_path_len) {
154 /* cookie_path must be '/' */
155 return TRUE;
156 }
157
158 uri_path = strdup(request_uri);
159 if(!uri_path)
160 return FALSE;
161 pos = strchr(uri_path, '?');
162 if(pos)
163 *pos = 0x0;
164
165 /* #-fragments are already cut off! */
166 if(0 == strlen(uri_path) || uri_path[0] != '/') {
167 strstore(&uri_path, "/", 1);
168 if(!uri_path)
169 return FALSE;
170 }
171
172 /*
173 * here, RFC6265 5.1.4 says
174 * 4. Output the characters of the uri-path from the first character up
175 * to, but not including, the right-most %x2F ("/").
176 * but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site
177 * without redirect.
178 * Ignore this algorithm because /hoge is uri path for this case
179 * (uri path is not /).
180 */
181
182 uri_path_len = strlen(uri_path);
183
184 if(uri_path_len < cookie_path_len) {
185 ret = FALSE;
186 goto pathmatched;
187 }
188
189 /* not using checkprefix() because matching should be case-sensitive */
190 if(strncmp(cookie_path, uri_path, cookie_path_len)) {
191 ret = FALSE;
192 goto pathmatched;
193 }
194
195 /* The cookie-path and the uri-path are identical. */
196 if(cookie_path_len == uri_path_len) {
197 ret = TRUE;
198 goto pathmatched;
199 }
200
201 /* here, cookie_path_len < uri_path_len */
202 if(uri_path[cookie_path_len] == '/') {
203 ret = TRUE;
204 goto pathmatched;
205 }
206
207 ret = FALSE;
208
209 pathmatched:
210 free(uri_path);
211 return ret;
212 }
213
214 /*
215 * Return the top-level domain, for optimal hashing.
216 */
get_top_domain(const char * const domain,size_t * outlen)217 static const char *get_top_domain(const char * const domain, size_t *outlen)
218 {
219 size_t len = 0;
220 const char *first = NULL, *last;
221
222 if(domain) {
223 len = strlen(domain);
224 last = memrchr(domain, '.', len);
225 if(last) {
226 first = memrchr(domain, '.', (last - domain));
227 if(first)
228 len -= (++first - domain);
229 }
230 }
231
232 if(outlen)
233 *outlen = len;
234
235 return first ? first : domain;
236 }
237
238 /* Avoid C1001, an "internal error" with MSVC14 */
239 #if defined(_MSC_VER) && (_MSC_VER == 1900)
240 #pragma optimize("", off)
241 #endif
242
243 /*
244 * A case-insensitive hash for the cookie domains.
245 */
cookie_hash_domain(const char * domain,const size_t len)246 static size_t cookie_hash_domain(const char *domain, const size_t len)
247 {
248 const char *end = domain + len;
249 size_t h = 5381;
250
251 while(domain < end) {
252 size_t j = (size_t)Curl_raw_toupper(*domain++);
253 h += h << 5;
254 h ^= j;
255 }
256
257 return (h % COOKIE_HASH_SIZE);
258 }
259
260 #if defined(_MSC_VER) && (_MSC_VER == 1900)
261 #pragma optimize("", on)
262 #endif
263
264 /*
265 * Hash this domain.
266 */
cookiehash(const char * const domain)267 static size_t cookiehash(const char * const domain)
268 {
269 const char *top;
270 size_t len;
271
272 if(!domain || Curl_host_is_ipnum(domain))
273 return 0;
274
275 top = get_top_domain(domain, &len);
276 return cookie_hash_domain(top, len);
277 }
278
279 /*
280 * cookie path sanitize
281 */
sanitize_cookie_path(const char * cookie_path)282 static char *sanitize_cookie_path(const char *cookie_path)
283 {
284 size_t len;
285 char *new_path = strdup(cookie_path);
286 if(!new_path)
287 return NULL;
288
289 /* some stupid site sends path attribute with '"'. */
290 len = strlen(new_path);
291 if(new_path[0] == '\"') {
292 memmove(new_path, new_path + 1, len);
293 len--;
294 }
295 if(len && (new_path[len - 1] == '\"')) {
296 new_path[--len] = 0x0;
297 }
298
299 /* RFC6265 5.2.4 The Path Attribute */
300 if(new_path[0] != '/') {
301 /* Let cookie-path be the default-path. */
302 strstore(&new_path, "/", 1);
303 return new_path;
304 }
305
306 /* convert /hoge/ to /hoge */
307 if(len && new_path[len - 1] == '/') {
308 new_path[len - 1] = 0x0;
309 }
310
311 return new_path;
312 }
313
314 /*
315 * Load cookies from all given cookie files (CURLOPT_COOKIEFILE).
316 *
317 * NOTE: OOM or cookie parsing failures are ignored.
318 */
Curl_cookie_loadfiles(struct Curl_easy * data)319 void Curl_cookie_loadfiles(struct Curl_easy *data)
320 {
321 struct curl_slist *list = data->state.cookielist;
322 if(list) {
323 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
324 while(list) {
325 struct CookieInfo *ci =
326 Curl_cookie_init(data, list->data, data->cookies,
327 data->set.cookiesession);
328 if(!ci)
329 /*
330 * Failure may be due to OOM or a bad cookie; both are ignored
331 * but only the first should be
332 */
333 infof(data, "ignoring failed cookie_init for %s", list->data);
334 else
335 data->cookies = ci;
336 list = list->next;
337 }
338 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
339 }
340 }
341
342 /*
343 * strstore
344 *
345 * A thin wrapper around strdup which ensures that any memory allocated at
346 * *str will be freed before the string allocated by strdup is stored there.
347 * The intended usecase is repeated assignments to the same variable during
348 * parsing in a last-wins scenario. The caller is responsible for checking
349 * for OOM errors.
350 */
strstore(char ** str,const char * newstr,size_t len)351 static void strstore(char **str, const char *newstr, size_t len)
352 {
353 DEBUGASSERT(newstr);
354 DEBUGASSERT(str);
355 free(*str);
356 *str = Curl_memdup0(newstr, len);
357 }
358
359 /*
360 * remove_expired
361 *
362 * Remove expired cookies from the hash by inspecting the expires timestamp on
363 * each cookie in the hash, freeing and deleting any where the timestamp is in
364 * the past. If the cookiejar has recorded the next timestamp at which one or
365 * more cookies expire, then processing will exit early in case this timestamp
366 * is in the future.
367 */
remove_expired(struct CookieInfo * ci)368 static void remove_expired(struct CookieInfo *ci)
369 {
370 struct Cookie *co;
371 curl_off_t now = (curl_off_t)time(NULL);
372 unsigned int i;
373
374 /*
375 * If the earliest expiration timestamp in the jar is in the future we can
376 * skip scanning the whole jar and instead exit early as there will not be
377 * any cookies to evict. If we need to evict however, reset the
378 * next_expiration counter in order to track the next one. In case the
379 * recorded first expiration is the max offset, then perform the safe
380 * fallback of checking all cookies.
381 */
382 if(now < ci->next_expiration &&
383 ci->next_expiration != CURL_OFF_T_MAX)
384 return;
385 else
386 ci->next_expiration = CURL_OFF_T_MAX;
387
388 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
389 struct Curl_llist_node *n;
390 struct Curl_llist_node *e = NULL;
391
392 for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {
393 co = Curl_node_elem(n);
394 e = Curl_node_next(n);
395 if(co->expires && co->expires < now) {
396 Curl_node_remove(n);
397 freecookie(co);
398 ci->numcookies--;
399 }
400 else {
401 /*
402 * If this cookie has an expiration timestamp earlier than what we
403 * have seen so far then record it for the next round of expirations.
404 */
405 if(co->expires && co->expires < ci->next_expiration)
406 ci->next_expiration = co->expires;
407 }
408 }
409 }
410 }
411
412 #ifndef USE_LIBPSL
413 /* Make sure domain contains a dot or is localhost. */
bad_domain(const char * domain,size_t len)414 static bool bad_domain(const char *domain, size_t len)
415 {
416 if((len == 9) && strncasecompare(domain, "localhost", 9))
417 return FALSE;
418 else {
419 /* there must be a dot present, but that dot must not be a trailing dot */
420 char *dot = memchr(domain, '.', len);
421 if(dot) {
422 size_t i = dot - domain;
423 if((len - i) > 1)
424 /* the dot is not the last byte */
425 return FALSE;
426 }
427 }
428 return TRUE;
429 }
430 #endif
431
432 /*
433 RFC 6265 section 4.1.1 says a server should accept this range:
434
435 cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
436
437 But Firefox and Chrome as of June 2022 accept space, comma and double-quotes
438 fine. The prime reason for filtering out control bytes is that some HTTP
439 servers return 400 for requests that contain such.
440 */
invalid_octets(const char * p)441 static int invalid_octets(const char *p)
442 {
443 /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */
444 static const char badoctets[] = {
445 "\x01\x02\x03\x04\x05\x06\x07\x08\x0a"
446 "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
447 "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"
448 };
449 size_t len;
450 /* scan for all the octets that are *not* in cookie-octet */
451 len = strcspn(p, badoctets);
452 return (p[len] != '\0');
453 }
454
455 #define CERR_OK 0
456 #define CERR_TOO_LONG 1 /* input line too long */
457 #define CERR_TAB 2 /* in a wrong place */
458 #define CERR_TOO_BIG 3 /* name/value too large */
459 #define CERR_BAD 4 /* deemed incorrect */
460 #define CERR_NO_SEP 5 /* semicolon problem */
461 #define CERR_NO_NAME_VALUE 6 /* name or value problem */
462 #define CERR_INVALID_OCTET 7 /* bad content */
463 #define CERR_BAD_SECURE 8 /* secure in a bad place */
464 #define CERR_OUT_OF_MEMORY 9
465 #define CERR_NO_TAILMATCH 10
466 #define CERR_COMMENT 11 /* a commented line */
467 #define CERR_RANGE 12 /* expire range problem */
468 #define CERR_FIELDS 13 /* incomplete netscape line */
469 #define CERR_PSL 14 /* a public suffix */
470 #define CERR_LIVE_WINS 15
471
472 static int
parse_cookie_header(struct Curl_easy * data,struct Cookie * co,struct CookieInfo * ci,const char * ptr,const char * domain,const char * path,bool secure)473 parse_cookie_header(struct Curl_easy *data,
474 struct Cookie *co,
475 struct CookieInfo *ci,
476 const char *ptr,
477 const char *domain, /* default domain */
478 const char *path, /* full path used when this cookie is
479 set, used to get default path for
480 the cookie unless set */
481 bool secure) /* TRUE if connection is over secure
482 origin */
483 {
484 /* This line was read off an HTTP-header */
485 time_t now;
486 size_t linelength = strlen(ptr);
487 if(linelength > MAX_COOKIE_LINE)
488 /* discard overly long lines at once */
489 return CERR_TOO_LONG;
490
491 now = time(NULL);
492 do {
493 size_t vlen;
494 size_t nlen;
495
496 while(*ptr && ISBLANK(*ptr))
497 ptr++;
498
499 /* we have a <name>=<value> pair or a stand-alone word here */
500 nlen = strcspn(ptr, ";\t\r\n=");
501 if(nlen) {
502 bool done = FALSE;
503 bool sep = FALSE;
504 const char *namep = ptr;
505 const char *valuep;
506
507 ptr += nlen;
508
509 /* trim trailing spaces and tabs after name */
510 while(nlen && ISBLANK(namep[nlen - 1]))
511 nlen--;
512
513 if(*ptr == '=') {
514 vlen = strcspn(++ptr, ";\r\n");
515 valuep = ptr;
516 sep = TRUE;
517 ptr = &valuep[vlen];
518
519 /* Strip off trailing whitespace from the value */
520 while(vlen && ISBLANK(valuep[vlen-1]))
521 vlen--;
522
523 /* Skip leading whitespace from the value */
524 while(vlen && ISBLANK(*valuep)) {
525 valuep++;
526 vlen--;
527 }
528
529 /* Reject cookies with a TAB inside the value */
530 if(memchr(valuep, '\t', vlen)) {
531 infof(data, "cookie contains TAB, dropping");
532 return CERR_TAB;
533 }
534 }
535 else {
536 valuep = NULL;
537 vlen = 0;
538 }
539
540 /*
541 * Check for too long individual name or contents, or too long
542 * combination of name + contents. Chrome and Firefox support 4095 or
543 * 4096 bytes combo
544 */
545 if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) ||
546 ((nlen + vlen) > MAX_NAME)) {
547 infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
548 nlen, vlen);
549 return CERR_TOO_BIG;
550 }
551
552 /*
553 * Check if we have a reserved prefix set before anything else, as we
554 * otherwise have to test for the prefix in both the cookie name and
555 * "the rest". Prefixes must start with '__' and end with a '-', so
556 * only test for names where that can possibly be true.
557 */
558 if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') {
559 if(strncasecompare("__Secure-", namep, 9))
560 co->prefix_secure = TRUE;
561 else if(strncasecompare("__Host-", namep, 7))
562 co->prefix_host = TRUE;
563 }
564
565 /*
566 * Use strstore() below to properly deal with received cookie
567 * headers that have the same string property set more than once,
568 * and then we use the last one.
569 */
570
571 if(!co->name) {
572 /* The very first name/value pair is the actual cookie name */
573 if(!sep)
574 /* Bad name/value pair. */
575 return CERR_NO_SEP;
576
577 strstore(&co->name, namep, nlen);
578 strstore(&co->value, valuep, vlen);
579 done = TRUE;
580 if(!co->name || !co->value)
581 return CERR_NO_NAME_VALUE;
582
583 if(invalid_octets(co->value) || invalid_octets(co->name)) {
584 infof(data, "invalid octets in name/value, cookie dropped");
585 return CERR_INVALID_OCTET;
586 }
587 }
588 else if(!vlen) {
589 /*
590 * this was a "<name>=" with no content, and we must allow
591 * 'secure' and 'httponly' specified this weirdly
592 */
593 done = TRUE;
594 /*
595 * secure cookies are only allowed to be set when the connection is
596 * using a secure protocol, or when the cookie is being set by
597 * reading from file
598 */
599 if((nlen == 6) && strncasecompare("secure", namep, 6)) {
600 if(secure || !ci->running) {
601 co->secure = TRUE;
602 }
603 else {
604 return CERR_BAD_SECURE;
605 }
606 }
607 else if((nlen == 8) && strncasecompare("httponly", namep, 8))
608 co->httponly = TRUE;
609 else if(sep)
610 /* there was a '=' so we are not done parsing this field */
611 done = FALSE;
612 }
613 if(done)
614 ;
615 else if((nlen == 4) && strncasecompare("path", namep, 4)) {
616 strstore(&co->path, valuep, vlen);
617 if(!co->path)
618 return CERR_OUT_OF_MEMORY;
619 free(co->spath); /* if this is set again */
620 co->spath = sanitize_cookie_path(co->path);
621 if(!co->spath)
622 return CERR_OUT_OF_MEMORY;
623 }
624 else if((nlen == 6) &&
625 strncasecompare("domain", namep, 6) && vlen) {
626 bool is_ip;
627
628 /*
629 * Now, we make sure that our host is within the given domain, or
630 * the given domain is not valid and thus cannot be set.
631 */
632
633 if('.' == valuep[0]) {
634 valuep++; /* ignore preceding dot */
635 vlen--;
636 }
637
638 #ifndef USE_LIBPSL
639 /*
640 * Without PSL we do not know when the incoming cookie is set on a
641 * TLD or otherwise "protected" suffix. To reduce risk, we require a
642 * dot OR the exact hostname being "localhost".
643 */
644 if(bad_domain(valuep, vlen))
645 domain = ":";
646 #endif
647
648 is_ip = Curl_host_is_ipnum(domain ? domain : valuep);
649
650 if(!domain
651 || (is_ip && !strncmp(valuep, domain, vlen) &&
652 (vlen == strlen(domain)))
653 || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) {
654 strstore(&co->domain, valuep, vlen);
655 if(!co->domain)
656 return CERR_OUT_OF_MEMORY;
657
658 if(!is_ip)
659 co->tailmatch = TRUE; /* we always do that if the domain name was
660 given */
661 }
662 else {
663 /*
664 * We did not get a tailmatch and then the attempted set domain is
665 * not a domain to which the current host belongs. Mark as bad.
666 */
667 infof(data, "skipped cookie with bad tailmatch domain: %s",
668 valuep);
669 return CERR_NO_TAILMATCH;
670 }
671 }
672 else if((nlen == 7) && strncasecompare("version", namep, 7)) {
673 /* just ignore */
674 }
675 else if((nlen == 7) && strncasecompare("max-age", namep, 7)) {
676 /*
677 * Defined in RFC2109:
678 *
679 * Optional. The Max-Age attribute defines the lifetime of the
680 * cookie, in seconds. The delta-seconds value is a decimal non-
681 * negative integer. After delta-seconds seconds elapse, the
682 * client should discard the cookie. A value of zero means the
683 * cookie should be discarded immediately.
684 */
685 CURLofft offt;
686 const char *maxage = valuep;
687 offt = curlx_strtoofft((*maxage == '\"') ?
688 &maxage[1] : &maxage[0], NULL, 10,
689 &co->expires);
690 switch(offt) {
691 case CURL_OFFT_FLOW:
692 /* overflow, used max value */
693 co->expires = CURL_OFF_T_MAX;
694 break;
695 case CURL_OFFT_INVAL:
696 /* negative or otherwise bad, expire */
697 co->expires = 1;
698 break;
699 case CURL_OFFT_OK:
700 if(!co->expires)
701 /* already expired */
702 co->expires = 1;
703 else if(CURL_OFF_T_MAX - now < co->expires)
704 /* would overflow */
705 co->expires = CURL_OFF_T_MAX;
706 else
707 co->expires += now;
708 break;
709 }
710 }
711 else if((nlen == 7) && strncasecompare("expires", namep, 7)) {
712 if(!co->expires) {
713 /*
714 * Let max-age have priority.
715 *
716 * If the date cannot get parsed for whatever reason, the cookie
717 * will be treated as a session cookie
718 */
719 co->expires = Curl_getdate_capped(valuep);
720
721 /*
722 * Session cookies have expires set to 0 so if we get that back
723 * from the date parser let's add a second to make it a
724 * non-session cookie
725 */
726 if(co->expires == 0)
727 co->expires = 1;
728 else if(co->expires < 0)
729 co->expires = 0;
730 }
731 }
732
733 /*
734 * Else, this is the second (or more) name we do not know about!
735 */
736 }
737 else {
738 /* this is an "illegal" <what>=<this> pair */
739 }
740
741 while(*ptr && ISBLANK(*ptr))
742 ptr++;
743 if(*ptr == ';')
744 ptr++;
745 else
746 break;
747 } while(1);
748
749 if(!co->domain && domain) {
750 /* no domain was given in the header line, set the default */
751 co->domain = strdup(domain);
752 if(!co->domain)
753 return CERR_OUT_OF_MEMORY;
754 }
755
756 if(!co->path && path) {
757 /*
758 * No path was given in the header line, set the default. Note that the
759 * passed-in path to this function MAY have a '?' and following part that
760 * MUST NOT be stored as part of the path.
761 */
762 char *queryp = strchr(path, '?');
763
764 /*
765 * queryp is where the interesting part of the path ends, so now we
766 * want to the find the last
767 */
768 char *endslash;
769 if(!queryp)
770 endslash = strrchr(path, '/');
771 else
772 endslash = memrchr(path, '/', (queryp - path));
773 if(endslash) {
774 size_t pathlen = (endslash-path + 1); /* include end slash */
775 co->path = Curl_memdup0(path, pathlen);
776 if(co->path) {
777 co->spath = sanitize_cookie_path(co->path);
778 if(!co->spath)
779 return CERR_OUT_OF_MEMORY;
780 }
781 else
782 return CERR_OUT_OF_MEMORY;
783 }
784 }
785
786 /*
787 * If we did not get a cookie name, or a bad one, the this is an illegal
788 * line so bail out.
789 */
790 if(!co->name)
791 return CERR_BAD;
792
793 data->req.setcookies++;
794 return CERR_OK;
795 }
796
797 static int
parse_netscape(struct Cookie * co,struct CookieInfo * ci,const char * lineptr,bool secure)798 parse_netscape(struct Cookie *co,
799 struct CookieInfo *ci,
800 const char *lineptr,
801 bool secure) /* TRUE if connection is over secure
802 origin */
803 {
804 /*
805 * This line is NOT an HTTP header style line, we do offer support for
806 * reading the odd netscape cookies-file format here
807 */
808 char *ptr;
809 char *firstptr;
810 char *tok_buf = NULL;
811 int fields;
812
813 /*
814 * In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS
815 * attacks. Cookies marked httpOnly are not accessible to JavaScript. In
816 * Firefox's cookie files, they are prefixed #HttpOnly_ and the rest
817 * remains as usual, so we skip 10 characters of the line.
818 */
819 if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
820 lineptr += 10;
821 co->httponly = TRUE;
822 }
823
824 if(lineptr[0]=='#')
825 /* do not even try the comments */
826 return CERR_COMMENT;
827
828 /* strip off the possible end-of-line characters */
829 ptr = strchr(lineptr, '\r');
830 if(ptr)
831 *ptr = 0; /* clear it */
832 ptr = strchr(lineptr, '\n');
833 if(ptr)
834 *ptr = 0; /* clear it */
835
836 /* tokenize on TAB */
837 firstptr = Curl_strtok_r((char *)lineptr, "\t", &tok_buf);
838
839 /*
840 * Now loop through the fields and init the struct we already have
841 * allocated
842 */
843 fields = 0;
844 for(ptr = firstptr; ptr;
845 ptr = Curl_strtok_r(NULL, "\t", &tok_buf), fields++) {
846 switch(fields) {
847 case 0:
848 if(ptr[0]=='.') /* skip preceding dots */
849 ptr++;
850 co->domain = strdup(ptr);
851 if(!co->domain)
852 return CERR_OUT_OF_MEMORY;
853 break;
854 case 1:
855 /*
856 * flag: A TRUE/FALSE value indicating if all machines within a given
857 * domain can access the variable. Set TRUE when the cookie says
858 * .domain.com and to false when the domain is complete www.domain.com
859 */
860 co->tailmatch = !!strcasecompare(ptr, "TRUE");
861 break;
862 case 2:
863 /* The file format allows the path field to remain not filled in */
864 if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
865 /* only if the path does not look like a boolean option! */
866 co->path = strdup(ptr);
867 if(!co->path)
868 return CERR_OUT_OF_MEMORY;
869 else {
870 co->spath = sanitize_cookie_path(co->path);
871 if(!co->spath)
872 return CERR_OUT_OF_MEMORY;
873 }
874 break;
875 }
876 /* this does not look like a path, make one up! */
877 co->path = strdup("/");
878 if(!co->path)
879 return CERR_OUT_OF_MEMORY;
880 co->spath = strdup("/");
881 if(!co->spath)
882 return CERR_OUT_OF_MEMORY;
883 fields++; /* add a field and fall down to secure */
884 FALLTHROUGH();
885 case 3:
886 co->secure = FALSE;
887 if(strcasecompare(ptr, "TRUE")) {
888 if(secure || ci->running)
889 co->secure = TRUE;
890 else
891 return CERR_BAD_SECURE;
892 }
893 break;
894 case 4:
895 if(curlx_strtoofft(ptr, NULL, 10, &co->expires))
896 return CERR_RANGE;
897 break;
898 case 5:
899 co->name = strdup(ptr);
900 if(!co->name)
901 return CERR_OUT_OF_MEMORY;
902 else {
903 /* For Netscape file format cookies we check prefix on the name */
904 if(strncasecompare("__Secure-", co->name, 9))
905 co->prefix_secure = TRUE;
906 else if(strncasecompare("__Host-", co->name, 7))
907 co->prefix_host = TRUE;
908 }
909 break;
910 case 6:
911 co->value = strdup(ptr);
912 if(!co->value)
913 return CERR_OUT_OF_MEMORY;
914 break;
915 }
916 }
917 if(6 == fields) {
918 /* we got a cookie with blank contents, fix it */
919 co->value = strdup("");
920 if(!co->value)
921 return CERR_OUT_OF_MEMORY;
922 else
923 fields++;
924 }
925
926 if(7 != fields)
927 /* we did not find the sufficient number of fields */
928 return CERR_FIELDS;
929
930 return CERR_OK;
931 }
932
933 static int
is_public_suffix(struct Curl_easy * data,struct Cookie * co,const char * domain)934 is_public_suffix(struct Curl_easy *data,
935 struct Cookie *co,
936 const char *domain)
937 {
938 #ifdef USE_LIBPSL
939 /*
940 * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
941 * must also check that the data handle is not NULL since the psl code will
942 * dereference it.
943 */
944 DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s",
945 co->name, co->domain, domain));
946 if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
947 bool acceptable = FALSE;
948 char lcase[256];
949 char lcookie[256];
950 size_t dlen = strlen(domain);
951 size_t clen = strlen(co->domain);
952 if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {
953 const psl_ctx_t *psl = Curl_psl_use(data);
954 if(psl) {
955 /* the PSL check requires lowercase domain name and pattern */
956 Curl_strntolower(lcase, domain, dlen + 1);
957 Curl_strntolower(lcookie, co->domain, clen + 1);
958 acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);
959 Curl_psl_release(data);
960 }
961 else
962 infof(data, "libpsl problem, rejecting cookie for satety");
963 }
964
965 if(!acceptable) {
966 infof(data, "cookie '%s' dropped, domain '%s' must not "
967 "set cookies for '%s'", co->name, domain, co->domain);
968 return CERR_PSL;
969 }
970 }
971 #else
972 (void)data;
973 (void)co;
974 (void)domain;
975 DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",
976 co->name, co->domain, domain));
977 #endif
978 return CERR_OK;
979 }
980
981 static int
replace_existing(struct Curl_easy * data,struct Cookie * co,struct CookieInfo * ci,bool secure,bool * replacep)982 replace_existing(struct Curl_easy *data,
983 struct Cookie *co,
984 struct CookieInfo *ci,
985 bool secure,
986 bool *replacep)
987 {
988 bool replace_old = FALSE;
989 struct Curl_llist_node *replace_n = NULL;
990 struct Curl_llist_node *n;
991 size_t myhash = cookiehash(co->domain);
992 for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
993 struct Cookie *clist = Curl_node_elem(n);
994 if(!strcmp(clist->name, co->name)) {
995 /* the names are identical */
996 bool matching_domains = FALSE;
997
998 if(clist->domain && co->domain) {
999 if(strcasecompare(clist->domain, co->domain))
1000 /* The domains are identical */
1001 matching_domains = TRUE;
1002 }
1003 else if(!clist->domain && !co->domain)
1004 matching_domains = TRUE;
1005
1006 if(matching_domains && /* the domains were identical */
1007 clist->spath && co->spath && /* both have paths */
1008 clist->secure && !co->secure && !secure) {
1009 size_t cllen;
1010 const char *sep;
1011
1012 /*
1013 * A non-secure cookie may not overlay an existing secure cookie.
1014 * For an existing cookie "a" with path "/login", refuse a new
1015 * cookie "a" with for example path "/login/en", while the path
1016 * "/loginhelper" is ok.
1017 */
1018
1019 sep = strchr(clist->spath + 1, '/');
1020
1021 if(sep)
1022 cllen = sep - clist->spath;
1023 else
1024 cllen = strlen(clist->spath);
1025
1026 if(strncasecompare(clist->spath, co->spath, cllen)) {
1027 infof(data, "cookie '%s' for domain '%s' dropped, would "
1028 "overlay an existing cookie", co->name, co->domain);
1029 return CERR_BAD_SECURE;
1030 }
1031 }
1032 }
1033
1034 if(!replace_n && !strcmp(clist->name, co->name)) {
1035 /* the names are identical */
1036
1037 if(clist->domain && co->domain) {
1038 if(strcasecompare(clist->domain, co->domain) &&
1039 (clist->tailmatch == co->tailmatch))
1040 /* The domains are identical */
1041 replace_old = TRUE;
1042 }
1043 else if(!clist->domain && !co->domain)
1044 replace_old = TRUE;
1045
1046 if(replace_old) {
1047 /* the domains were identical */
1048
1049 if(clist->spath && co->spath &&
1050 !strcasecompare(clist->spath, co->spath))
1051 replace_old = FALSE;
1052 else if(!clist->spath != !co->spath)
1053 replace_old = FALSE;
1054 }
1055
1056 if(replace_old && !co->livecookie && clist->livecookie) {
1057 /*
1058 * Both cookies matched fine, except that the already present cookie
1059 * is "live", which means it was set from a header, while the new one
1060 * was read from a file and thus is not "live". "live" cookies are
1061 * preferred so the new cookie is freed.
1062 */
1063 return CERR_LIVE_WINS;
1064 }
1065 if(replace_old)
1066 replace_n = n;
1067 }
1068 }
1069 if(replace_n) {
1070 struct Cookie *repl = Curl_node_elem(replace_n);
1071
1072 /* when replacing, creationtime is kept from old */
1073 co->creationtime = repl->creationtime;
1074
1075 /* unlink the old */
1076 Curl_node_remove(replace_n);
1077
1078 /* free the old cookie */
1079 freecookie(repl);
1080 }
1081 *replacep = replace_old;
1082 return CERR_OK;
1083 }
1084
1085 /*
1086 * Curl_cookie_add
1087 *
1088 * Add a single cookie line to the cookie keeping object. Be aware that
1089 * sometimes we get an IP-only hostname, and that might also be a numerical
1090 * IPv6 address.
1091 *
1092 * Returns NULL on out of memory or invalid cookie. This is suboptimal,
1093 * as they should be treated separately.
1094 */
1095 struct Cookie *
Curl_cookie_add(struct Curl_easy * data,struct CookieInfo * ci,bool httpheader,bool noexpire,const char * lineptr,const char * domain,const char * path,bool secure)1096 Curl_cookie_add(struct Curl_easy *data,
1097 struct CookieInfo *ci,
1098 bool httpheader, /* TRUE if HTTP header-style line */
1099 bool noexpire, /* if TRUE, skip remove_expired() */
1100 const char *lineptr, /* first character of the line */
1101 const char *domain, /* default domain */
1102 const char *path, /* full path used when this cookie is set,
1103 used to get default path for the cookie
1104 unless set */
1105 bool secure) /* TRUE if connection is over secure origin */
1106 {
1107 struct Cookie *co;
1108 size_t myhash;
1109 int rc;
1110 bool replaces = FALSE;
1111
1112 DEBUGASSERT(data);
1113 DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
1114 if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
1115 return NULL;
1116
1117 /* First, alloc and init a new struct for it */
1118 co = calloc(1, sizeof(struct Cookie));
1119 if(!co)
1120 return NULL; /* bail out if we are this low on memory */
1121
1122 if(httpheader)
1123 rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure);
1124 else
1125 rc = parse_netscape(co, ci, lineptr, secure);
1126
1127 if(rc)
1128 goto fail;
1129
1130 if(co->prefix_secure && !co->secure)
1131 /* The __Secure- prefix only requires that the cookie be set secure */
1132 goto fail;
1133
1134 if(co->prefix_host) {
1135 /*
1136 * The __Host- prefix requires the cookie to be secure, have a "/" path
1137 * and not have a domain set.
1138 */
1139 if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
1140 ;
1141 else
1142 goto fail;
1143 }
1144
1145 if(!ci->running && /* read from a file */
1146 ci->newsession && /* clean session cookies */
1147 !co->expires) /* this is a session cookie since it does not expire */
1148 goto fail;
1149
1150 co->livecookie = ci->running;
1151 co->creationtime = ++ci->lastct;
1152
1153 /*
1154 * Now we have parsed the incoming line, we must now check if this supersedes
1155 * an already existing cookie, which it may if the previous have the same
1156 * domain and path as this.
1157 */
1158
1159 /* remove expired cookies */
1160 if(!noexpire)
1161 remove_expired(ci);
1162
1163 if(is_public_suffix(data, co, domain))
1164 goto fail;
1165
1166 if(replace_existing(data, co, ci, secure, &replaces))
1167 goto fail;
1168
1169 /* add this cookie to the list */
1170 myhash = cookiehash(co->domain);
1171 Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
1172
1173 if(ci->running)
1174 /* Only show this when NOT reading the cookies from a file */
1175 infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1176 "expire %" FMT_OFF_T,
1177 replaces ? "Replaced":"Added", co->name, co->value,
1178 co->domain, co->path, co->expires);
1179
1180 if(!replaces)
1181 ci->numcookies++; /* one more cookie in the jar */
1182
1183 /*
1184 * Now that we have added a new cookie to the jar, update the expiration
1185 * tracker in case it is the next one to expire.
1186 */
1187 if(co->expires && (co->expires < ci->next_expiration))
1188 ci->next_expiration = co->expires;
1189
1190 return co;
1191 fail:
1192 freecookie(co);
1193 return NULL;
1194 }
1195
1196
1197 /*
1198 * Curl_cookie_init()
1199 *
1200 * Inits a cookie struct to read data from a local file. This is always
1201 * called before any cookies are set. File may be NULL in which case only the
1202 * struct is initialized. Is file is "-" then STDIN is read.
1203 *
1204 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1205 *
1206 * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1207 * will be ignored.
1208 *
1209 * Returns NULL on out of memory. Invalid cookies are ignored.
1210 */
Curl_cookie_init(struct Curl_easy * data,const char * file,struct CookieInfo * ci,bool newsession)1211 struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
1212 const char *file,
1213 struct CookieInfo *ci,
1214 bool newsession)
1215 {
1216 FILE *handle = NULL;
1217
1218 if(!ci) {
1219 int i;
1220
1221 /* we did not get a struct, create one */
1222 ci = calloc(1, sizeof(struct CookieInfo));
1223 if(!ci)
1224 return NULL; /* failed to get memory */
1225
1226 /* This does not use the destructor callback since we want to add
1227 and remove to lists while keeping the cookie struct intact */
1228 for(i = 0; i < COOKIE_HASH_SIZE; i++)
1229 Curl_llist_init(&ci->cookielist[i], NULL);
1230 /*
1231 * Initialize the next_expiration time to signal that we do not have enough
1232 * information yet.
1233 */
1234 ci->next_expiration = CURL_OFF_T_MAX;
1235 }
1236 ci->newsession = newsession; /* new session? */
1237
1238 if(data) {
1239 FILE *fp = NULL;
1240 if(file && *file) {
1241 if(!strcmp(file, "-"))
1242 fp = stdin;
1243 else {
1244 fp = fopen(file, "rb");
1245 if(!fp)
1246 infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1247 else
1248 handle = fp;
1249 }
1250 }
1251
1252 ci->running = FALSE; /* this is not running, this is init */
1253 if(fp) {
1254 struct dynbuf buf;
1255 Curl_dyn_init(&buf, MAX_COOKIE_LINE);
1256 while(Curl_get_line(&buf, fp)) {
1257 char *lineptr = Curl_dyn_ptr(&buf);
1258 bool headerline = FALSE;
1259 if(checkprefix("Set-Cookie:", lineptr)) {
1260 /* This is a cookie line, get it! */
1261 lineptr += 11;
1262 headerline = TRUE;
1263 while(*lineptr && ISBLANK(*lineptr))
1264 lineptr++;
1265 }
1266
1267 Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE);
1268 }
1269 Curl_dyn_free(&buf); /* free the line buffer */
1270
1271 /*
1272 * Remove expired cookies from the hash. We must make sure to run this
1273 * after reading the file, and not on every cookie.
1274 */
1275 remove_expired(ci);
1276
1277 if(handle)
1278 fclose(handle);
1279 }
1280 data->state.cookie_engine = TRUE;
1281 }
1282 ci->running = TRUE; /* now, we are running */
1283
1284 return ci;
1285 }
1286
1287 /*
1288 * cookie_sort
1289 *
1290 * Helper function to sort cookies such that the longest path gets before the
1291 * shorter path. Path, domain and name lengths are considered in that order,
1292 * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1293 * be unique per cookie, so we know we will get an ordering at that point.
1294 */
cookie_sort(const void * p1,const void * p2)1295 static int cookie_sort(const void *p1, const void *p2)
1296 {
1297 struct Cookie *c1 = *(struct Cookie **)p1;
1298 struct Cookie *c2 = *(struct Cookie **)p2;
1299 size_t l1, l2;
1300
1301 /* 1 - compare cookie path lengths */
1302 l1 = c1->path ? strlen(c1->path) : 0;
1303 l2 = c2->path ? strlen(c2->path) : 0;
1304
1305 if(l1 != l2)
1306 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
1307
1308 /* 2 - compare cookie domain lengths */
1309 l1 = c1->domain ? strlen(c1->domain) : 0;
1310 l2 = c2->domain ? strlen(c2->domain) : 0;
1311
1312 if(l1 != l2)
1313 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
1314
1315 /* 3 - compare cookie name lengths */
1316 l1 = c1->name ? strlen(c1->name) : 0;
1317 l2 = c2->name ? strlen(c2->name) : 0;
1318
1319 if(l1 != l2)
1320 return (l2 > l1) ? 1 : -1;
1321
1322 /* 4 - compare cookie creation time */
1323 return (c2->creationtime > c1->creationtime) ? 1 : -1;
1324 }
1325
1326 /*
1327 * cookie_sort_ct
1328 *
1329 * Helper function to sort cookies according to creation time.
1330 */
cookie_sort_ct(const void * p1,const void * p2)1331 static int cookie_sort_ct(const void *p1, const void *p2)
1332 {
1333 struct Cookie *c1 = *(struct Cookie **)p1;
1334 struct Cookie *c2 = *(struct Cookie **)p2;
1335
1336 return (c2->creationtime > c1->creationtime) ? 1 : -1;
1337 }
1338
1339 /*
1340 * Curl_cookie_getlist
1341 *
1342 * For a given host and path, return a linked list of cookies that the client
1343 * should send to the server if used now. The secure boolean informs the cookie
1344 * if a secure connection is achieved or not.
1345 *
1346 * It shall only return cookies that have not expired.
1347 *
1348 * Returns 0 when there is a list returned. Otherwise non-zero.
1349 */
Curl_cookie_getlist(struct Curl_easy * data,struct CookieInfo * ci,const char * host,const char * path,bool secure,struct Curl_llist * list)1350 int Curl_cookie_getlist(struct Curl_easy *data,
1351 struct CookieInfo *ci,
1352 const char *host, const char *path,
1353 bool secure,
1354 struct Curl_llist *list)
1355 {
1356 size_t matches = 0;
1357 bool is_ip;
1358 const size_t myhash = cookiehash(host);
1359 struct Curl_llist_node *n;
1360
1361 Curl_llist_init(list, NULL);
1362
1363 if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
1364 return 1; /* no cookie struct or no cookies in the struct */
1365
1366 /* at first, remove expired cookies */
1367 remove_expired(ci);
1368
1369 /* check if host is an IP(v4|v6) address */
1370 is_ip = Curl_host_is_ipnum(host);
1371
1372 for(n = Curl_llist_head(&ci->cookielist[myhash]);
1373 n; n = Curl_node_next(n)) {
1374 struct Cookie *co = Curl_node_elem(n);
1375
1376 /* if the cookie requires we are secure we must only continue if we are! */
1377 if(co->secure ? secure : TRUE) {
1378
1379 /* now check if the domain is correct */
1380 if(!co->domain ||
1381 (co->tailmatch && !is_ip &&
1382 cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1383 ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
1384 /*
1385 * the right part of the host matches the domain stuff in the
1386 * cookie data
1387 */
1388
1389 /*
1390 * now check the left part of the path with the cookies path
1391 * requirement
1392 */
1393 if(!co->spath || pathmatch(co->spath, path) ) {
1394
1395 /*
1396 * This is a match and we add it to the return-linked-list
1397 */
1398 Curl_llist_append(list, co, &co->getnode);
1399 matches++;
1400 if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1401 infof(data, "Included max number of cookies (%zu) in request!",
1402 matches);
1403 break;
1404 }
1405 }
1406 }
1407 }
1408 }
1409
1410 if(matches) {
1411 /*
1412 * Now we need to make sure that if there is a name appearing more than
1413 * once, the longest specified path version comes first. To make this
1414 * the swiftest way, we just sort them all based on path length.
1415 */
1416 struct Cookie **array;
1417 size_t i;
1418
1419 /* alloc an array and store all cookie pointers */
1420 array = malloc(sizeof(struct Cookie *) * matches);
1421 if(!array)
1422 goto fail;
1423
1424 n = Curl_llist_head(list);
1425
1426 for(i = 0; n; n = Curl_node_next(n))
1427 array[i++] = Curl_node_elem(n);
1428
1429 /* now sort the cookie pointers in path length order */
1430 qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1431
1432 /* remake the linked list order according to the new order */
1433 Curl_llist_destroy(list, NULL);
1434
1435 for(i = 0; i < matches; i++)
1436 Curl_llist_append(list, array[i], &array[i]->getnode);
1437
1438 free(array); /* remove the temporary data again */
1439 }
1440
1441 return 0; /* success */
1442
1443 fail:
1444 /* failure, clear up the allocated chain and return NULL */
1445 Curl_llist_destroy(list, NULL);
1446 return 2; /* error */
1447 }
1448
1449 /*
1450 * Curl_cookie_clearall
1451 *
1452 * Clear all existing cookies and reset the counter.
1453 */
Curl_cookie_clearall(struct CookieInfo * ci)1454 void Curl_cookie_clearall(struct CookieInfo *ci)
1455 {
1456 if(ci) {
1457 unsigned int i;
1458 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1459 struct Curl_llist_node *n;
1460 for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
1461 struct Cookie *c = Curl_node_elem(n);
1462 struct Curl_llist_node *e = Curl_node_next(n);
1463 Curl_node_remove(n);
1464 freecookie(c);
1465 n = e;
1466 }
1467 }
1468 ci->numcookies = 0;
1469 }
1470 }
1471
1472 /*
1473 * Curl_cookie_clearsess
1474 *
1475 * Free all session cookies in the cookies list.
1476 */
Curl_cookie_clearsess(struct CookieInfo * ci)1477 void Curl_cookie_clearsess(struct CookieInfo *ci)
1478 {
1479 unsigned int i;
1480
1481 if(!ci)
1482 return;
1483
1484 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1485 struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);
1486 struct Curl_llist_node *e = NULL;
1487
1488 for(; n; n = e) {
1489 struct Cookie *curr = Curl_node_elem(n);
1490 e = Curl_node_next(n); /* in case the node is removed, get it early */
1491 if(!curr->expires) {
1492 Curl_node_remove(n);
1493 freecookie(curr);
1494 ci->numcookies--;
1495 }
1496 }
1497 }
1498 }
1499
1500 /*
1501 * Curl_cookie_cleanup()
1502 *
1503 * Free a "cookie object" previous created with Curl_cookie_init().
1504 */
Curl_cookie_cleanup(struct CookieInfo * ci)1505 void Curl_cookie_cleanup(struct CookieInfo *ci)
1506 {
1507 if(ci) {
1508 Curl_cookie_clearall(ci);
1509 free(ci); /* free the base struct as well */
1510 }
1511 }
1512
1513 /*
1514 * get_netscape_format()
1515 *
1516 * Formats a string for Netscape output file, w/o a newline at the end.
1517 * Function returns a char * to a formatted line. The caller is responsible
1518 * for freeing the returned pointer.
1519 */
get_netscape_format(const struct Cookie * co)1520 static char *get_netscape_format(const struct Cookie *co)
1521 {
1522 return aprintf(
1523 "%s" /* httponly preamble */
1524 "%s%s\t" /* domain */
1525 "%s\t" /* tailmatch */
1526 "%s\t" /* path */
1527 "%s\t" /* secure */
1528 "%" FMT_OFF_T "\t" /* expires */
1529 "%s\t" /* name */
1530 "%s", /* value */
1531 co->httponly ? "#HttpOnly_" : "",
1532 /*
1533 * Make sure all domains are prefixed with a dot if they allow
1534 * tailmatching. This is Mozilla-style.
1535 */
1536 (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
1537 co->domain ? co->domain : "unknown",
1538 co->tailmatch ? "TRUE" : "FALSE",
1539 co->path ? co->path : "/",
1540 co->secure ? "TRUE" : "FALSE",
1541 co->expires,
1542 co->name,
1543 co->value ? co->value : "");
1544 }
1545
1546 /*
1547 * cookie_output()
1548 *
1549 * Writes all internally known cookies to the specified file. Specify
1550 * "-" as filename to write to stdout.
1551 *
1552 * The function returns non-zero on write failure.
1553 */
cookie_output(struct Curl_easy * data,struct CookieInfo * ci,const char * filename)1554 static CURLcode cookie_output(struct Curl_easy *data,
1555 struct CookieInfo *ci,
1556 const char *filename)
1557 {
1558 FILE *out = NULL;
1559 bool use_stdout = FALSE;
1560 char *tempstore = NULL;
1561 CURLcode error = CURLE_OK;
1562
1563 if(!ci)
1564 /* no cookie engine alive */
1565 return CURLE_OK;
1566
1567 /* at first, remove expired cookies */
1568 remove_expired(ci);
1569
1570 if(!strcmp("-", filename)) {
1571 /* use stdout */
1572 out = stdout;
1573 use_stdout = TRUE;
1574 }
1575 else {
1576 error = Curl_fopen(data, filename, &out, &tempstore);
1577 if(error)
1578 goto error;
1579 }
1580
1581 fputs("# Netscape HTTP Cookie File\n"
1582 "# https://curl.se/docs/http-cookies.html\n"
1583 "# This file was generated by libcurl! Edit at your own risk.\n\n",
1584 out);
1585
1586 if(ci->numcookies) {
1587 unsigned int i;
1588 size_t nvalid = 0;
1589 struct Cookie **array;
1590 struct Curl_llist_node *n;
1591
1592 array = calloc(1, sizeof(struct Cookie *) * ci->numcookies);
1593 if(!array) {
1594 error = CURLE_OUT_OF_MEMORY;
1595 goto error;
1596 }
1597
1598 /* only sort the cookies with a domain property */
1599 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1600 for(n = Curl_llist_head(&ci->cookielist[i]); n;
1601 n = Curl_node_next(n)) {
1602 struct Cookie *co = Curl_node_elem(n);
1603 if(!co->domain)
1604 continue;
1605 array[nvalid++] = co;
1606 }
1607 }
1608
1609 qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1610
1611 for(i = 0; i < nvalid; i++) {
1612 char *format_ptr = get_netscape_format(array[i]);
1613 if(!format_ptr) {
1614 free(array);
1615 error = CURLE_OUT_OF_MEMORY;
1616 goto error;
1617 }
1618 fprintf(out, "%s\n", format_ptr);
1619 free(format_ptr);
1620 }
1621
1622 free(array);
1623 }
1624
1625 if(!use_stdout) {
1626 fclose(out);
1627 out = NULL;
1628 if(tempstore && Curl_rename(tempstore, filename)) {
1629 unlink(tempstore);
1630 error = CURLE_WRITE_ERROR;
1631 goto error;
1632 }
1633 }
1634
1635 /*
1636 * If we reach here we have successfully written a cookie file so there is
1637 * no need to inspect the error, any error case should have jumped into the
1638 * error block below.
1639 */
1640 free(tempstore);
1641 return CURLE_OK;
1642
1643 error:
1644 if(out && !use_stdout)
1645 fclose(out);
1646 free(tempstore);
1647 return error;
1648 }
1649
cookie_list(struct Curl_easy * data)1650 static struct curl_slist *cookie_list(struct Curl_easy *data)
1651 {
1652 struct curl_slist *list = NULL;
1653 struct curl_slist *beg;
1654 unsigned int i;
1655 struct Curl_llist_node *n;
1656
1657 if(!data->cookies || (data->cookies->numcookies == 0))
1658 return NULL;
1659
1660 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1661 for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;
1662 n = Curl_node_next(n)) {
1663 struct Cookie *c = Curl_node_elem(n);
1664 char *line;
1665 if(!c->domain)
1666 continue;
1667 line = get_netscape_format(c);
1668 if(!line) {
1669 curl_slist_free_all(list);
1670 return NULL;
1671 }
1672 beg = Curl_slist_append_nodup(list, line);
1673 if(!beg) {
1674 free(line);
1675 curl_slist_free_all(list);
1676 return NULL;
1677 }
1678 list = beg;
1679 }
1680 }
1681
1682 return list;
1683 }
1684
Curl_cookie_list(struct Curl_easy * data)1685 struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1686 {
1687 struct curl_slist *list;
1688 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1689 list = cookie_list(data);
1690 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1691 return list;
1692 }
1693
Curl_flush_cookies(struct Curl_easy * data,bool cleanup)1694 void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1695 {
1696 CURLcode res;
1697
1698 if(data->set.str[STRING_COOKIEJAR]) {
1699 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1700
1701 /* if we have a destination file for all the cookies to get dumped to */
1702 res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);
1703 if(res)
1704 infof(data, "WARNING: failed to save cookies in %s: %s",
1705 data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
1706 }
1707 else {
1708 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1709 }
1710
1711 if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1712 Curl_cookie_cleanup(data->cookies);
1713 data->cookies = NULL;
1714 }
1715 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1716 }
1717
1718 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
1719