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