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
28RECEIVING COOKIE INFORMATION
29============================
30
31struct 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
37struct 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
53SENDING COOKIE INFORMATION
54==========================
55
56struct 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
67Example 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
111static void strstore(char **str, const char *newstr, size_t len);
112
113static 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
123static bool cookie_tailmatch(const char *cookie_domain,
124 size_t cookie_domain_len,
125 const char *hostname)
126{
127 size_t hostname_len = strlen(s: 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 */
156static 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(s: 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(s: uri_path, c: '?');
175 if(pos)
176 *pos = 0x0;
177
178 /* #-fragments are already cut off! */
179 if(0 == strlen(s: uri_path) || uri_path[0] != '/') {
180 strstore(str: &uri_path, newstr: "/", len: 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(s: 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(s1: cookie_path, s2: uri_path, n: 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
222pathmatched:
223 free(uri_path);
224 return ret;
225}
226
227/*
228 * Return the top-level domain, for optimal hashing.
229 */
230static 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(s: 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 */
259static 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(in: *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 */
279static size_t cookiehash(const char * const domain)
280{
281 const char *top;
282 size_t len;
283
284 if(!domain || Curl_host_is_ipnum(hostname: domain))
285 return 0;
286
287 top = get_top_domain(domain, outlen: &len);
288 return cookie_hash_domain(domain: top, len);
289}
290
291/*
292 * cookie path sanitize
293 */
294static 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(s: new_path);
303 if(new_path[0] == '\"') {
304 memmove(dest: new_path, src: new_path + 1, n: 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(str: &new_path, newstr: "/", len: 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 */
331void Curl_cookie_loadfiles(struct Curl_easy *data)
332{
333 struct curl_slist *list = data->set.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, file: list->data, inc: data->cookies,
339 newsession: 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 */
363static void strstore(char **str, const char *newstr, size_t len)
364{
365 DEBUGASSERT(newstr);
366 DEBUGASSERT(str);
367 free(*str);
368 *str = Curl_memdup(src: newstr, buffer_length: len + 1);
369 if(*str)
370 (*str)[len] = 0;
371}
372
373/*
374 * remove_expired
375 *
376 * Remove expired cookies from the hash by inspecting the expires timestamp on
377 * each cookie in the hash, freeing and deleting any where the timestamp is in
378 * the past. If the cookiejar has recorded the next timestamp at which one or
379 * more cookies expire, then processing will exit early in case this timestamp
380 * is in the future.
381 */
382static void remove_expired(struct CookieInfo *cookies)
383{
384 struct Cookie *co, *nx;
385 curl_off_t now = (curl_off_t)time(NULL);
386 unsigned int i;
387
388 /*
389 * If the earliest expiration timestamp in the jar is in the future we can
390 * skip scanning the whole jar and instead exit early as there won't be any
391 * cookies to evict. If we need to evict however, reset the next_expiration
392 * counter in order to track the next one. In case the recorded first
393 * expiration is the max offset, then perform the safe fallback of checking
394 * all cookies.
395 */
396 if(now < cookies->next_expiration &&
397 cookies->next_expiration != CURL_OFF_T_MAX)
398 return;
399 else
400 cookies->next_expiration = CURL_OFF_T_MAX;
401
402 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
403 struct Cookie *pv = NULL;
404 co = cookies->cookies[i];
405 while(co) {
406 nx = co->next;
407 if(co->expires && co->expires < now) {
408 if(!pv) {
409 cookies->cookies[i] = co->next;
410 }
411 else {
412 pv->next = co->next;
413 }
414 cookies->numcookies--;
415 freecookie(co);
416 }
417 else {
418 /*
419 * If this cookie has an expiration timestamp earlier than what we've
420 * seen so far then record it for the next round of expirations.
421 */
422 if(co->expires && co->expires < cookies->next_expiration)
423 cookies->next_expiration = co->expires;
424 pv = co;
425 }
426 co = nx;
427 }
428 }
429}
430
431/* Make sure domain contains a dot or is localhost. */
432static bool bad_domain(const char *domain, size_t len)
433{
434 if((len == 9) && strncasecompare(domain, "localhost", 9))
435 return FALSE;
436 else {
437 /* there must be a dot present, but that dot must not be a trailing dot */
438 char *dot = memchr(s: domain, c: '.', n: len);
439 if(dot) {
440 size_t i = dot - domain;
441 if((len - i) > 1)
442 /* the dot is not the last byte */
443 return FALSE;
444 }
445 }
446 return TRUE;
447}
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*/
458static 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(s: p, reject: 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 */
482struct Cookie *
483Curl_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(s: 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(s: ptr, reject: ";\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(s: ++ptr, reject: ";\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(s: valuep, c: '\t', n: 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(str: &co->name, newstr: namep, len: nlen);
615 strstore(str: &co->value, newstr: valuep, len: vlen);
616 done = TRUE;
617 if(!co->name || !co->value) {
618 badcookie = TRUE;
619 break;
620 }
621 if(invalid_octets(p: co->value) || invalid_octets(p: 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(str: &co->path, newstr: valuep, len: 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(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(domain: valuep, len: vlen))
689 domain = ":";
690#endif
691
692 is_ip = Curl_host_is_ipnum(hostname: domain ? domain : valuep);
693
694 if(!domain
695 || (is_ip && !strncmp(s1: valuep, s2: domain, n: vlen) &&
696 (vlen == strlen(s: domain)))
697 || (!is_ip && cookie_tailmatch(cookie_domain: valuep, cookie_domain_len: vlen, hostname: domain))) {
698 strstore(str: &co->domain, newstr: valuep, len: 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(str: (*maxage == '\"')?
733 &maxage[1]:&maxage[0], NULL, base: 10,
734 num: &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(dest: date, src: valuep, n: 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(p: 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(s: path, c: '?');
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(s: path, c: '/');
822 else
823 endslash = memrchr(path, '/', (queryp - path));
824 if(endslash) {
825 size_t pathlen = (endslash-path + 1); /* include end slash */
826 co->path = malloc(pathlen + 1); /* one extra for the zero byte */
827 if(co->path) {
828 memcpy(dest: co->path, src: path, n: pathlen);
829 co->path[pathlen] = 0; /* null-terminate */
830 co->spath = sanitize_cookie_path(cookie_path: co->path);
831 if(!co->spath)
832 badcookie = TRUE; /* out of memory bad */
833 }
834 else
835 badcookie = TRUE;
836 }
837 }
838
839 /*
840 * If we didn't get a cookie name, or a bad one, the this is an illegal
841 * line so bail out.
842 */
843 if(badcookie || !co->name) {
844 freecookie(co);
845 return NULL;
846 }
847 data->req.setcookies++;
848 }
849 else {
850 /*
851 * This line is NOT an HTTP header style line, we do offer support for
852 * reading the odd netscape cookies-file format here
853 */
854 char *ptr;
855 char *firstptr;
856 char *tok_buf = NULL;
857 int fields;
858
859 /*
860 * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked
861 * with httpOnly after the domain name are not accessible from javascripts,
862 * but since curl does not operate at javascript level, we include them
863 * anyway. In Firefox's cookie files, these lines are preceded with
864 * #HttpOnly_ and then everything is as usual, so we skip 10 characters of
865 * the line..
866 */
867 if(strncmp(s1: lineptr, s2: "#HttpOnly_", n: 10) == 0) {
868 lineptr += 10;
869 co->httponly = TRUE;
870 }
871
872 if(lineptr[0]=='#') {
873 /* don't even try the comments */
874 free(co);
875 return NULL;
876 }
877 /* strip off the possible end-of-line characters */
878 ptr = strchr(s: lineptr, c: '\r');
879 if(ptr)
880 *ptr = 0; /* clear it */
881 ptr = strchr(s: lineptr, c: '\n');
882 if(ptr)
883 *ptr = 0; /* clear it */
884
885 firstptr = strtok_r(s: (char *)lineptr, delim: "\t", save_ptr: &tok_buf); /* tokenize on TAB */
886
887 /*
888 * Now loop through the fields and init the struct we already have
889 * allocated
890 */
891 for(ptr = firstptr, fields = 0; ptr && !badcookie;
892 ptr = strtok_r(NULL, delim: "\t", save_ptr: &tok_buf), fields++) {
893 switch(fields) {
894 case 0:
895 if(ptr[0]=='.') /* skip preceding dots */
896 ptr++;
897 co->domain = strdup(ptr);
898 if(!co->domain)
899 badcookie = TRUE;
900 break;
901 case 1:
902 /*
903 * flag: A TRUE/FALSE value indicating if all machines within a given
904 * domain can access the variable. Set TRUE when the cookie says
905 * .domain.com and to false when the domain is complete www.domain.com
906 */
907 co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
908 break;
909 case 2:
910 /* The file format allows the path field to remain not filled in */
911 if(strcmp(s1: "TRUE", s2: ptr) && strcmp(s1: "FALSE", s2: ptr)) {
912 /* only if the path doesn't look like a boolean option! */
913 co->path = strdup(ptr);
914 if(!co->path)
915 badcookie = TRUE;
916 else {
917 co->spath = sanitize_cookie_path(cookie_path: co->path);
918 if(!co->spath) {
919 badcookie = TRUE; /* out of memory bad */
920 }
921 }
922 break;
923 }
924 /* this doesn't look like a path, make one up! */
925 co->path = strdup("/");
926 if(!co->path)
927 badcookie = TRUE;
928 co->spath = strdup("/");
929 if(!co->spath)
930 badcookie = TRUE;
931 fields++; /* add a field and fall down to secure */
932 /* FALLTHROUGH */
933 case 3:
934 co->secure = FALSE;
935 if(strcasecompare(ptr, "TRUE")) {
936 if(secure || c->running)
937 co->secure = TRUE;
938 else
939 badcookie = TRUE;
940 }
941 break;
942 case 4:
943 if(curlx_strtoofft(str: ptr, NULL, base: 10, num: &co->expires))
944 badcookie = TRUE;
945 break;
946 case 5:
947 co->name = strdup(ptr);
948 if(!co->name)
949 badcookie = TRUE;
950 else {
951 /* For Netscape file format cookies we check prefix on the name */
952 if(strncasecompare("__Secure-", co->name, 9))
953 co->prefix |= COOKIE_PREFIX__SECURE;
954 else if(strncasecompare("__Host-", co->name, 7))
955 co->prefix |= COOKIE_PREFIX__HOST;
956 }
957 break;
958 case 6:
959 co->value = strdup(ptr);
960 if(!co->value)
961 badcookie = TRUE;
962 break;
963 }
964 }
965 if(6 == fields) {
966 /* we got a cookie with blank contents, fix it */
967 co->value = strdup("");
968 if(!co->value)
969 badcookie = TRUE;
970 else
971 fields++;
972 }
973
974 if(!badcookie && (7 != fields))
975 /* we did not find the sufficient number of fields */
976 badcookie = TRUE;
977
978 if(badcookie) {
979 freecookie(co);
980 return NULL;
981 }
982
983 }
984
985 if(co->prefix & COOKIE_PREFIX__SECURE) {
986 /* The __Secure- prefix only requires that the cookie be set secure */
987 if(!co->secure) {
988 freecookie(co);
989 return NULL;
990 }
991 }
992 if(co->prefix & COOKIE_PREFIX__HOST) {
993 /*
994 * The __Host- prefix requires the cookie to be secure, have a "/" path
995 * and not have a domain set.
996 */
997 if(co->secure && co->path && strcmp(s1: co->path, s2: "/") == 0 && !co->tailmatch)
998 ;
999 else {
1000 freecookie(co);
1001 return NULL;
1002 }
1003 }
1004
1005 if(!c->running && /* read from a file */
1006 c->newsession && /* clean session cookies */
1007 !co->expires) { /* this is a session cookie since it doesn't expire! */
1008 freecookie(co);
1009 return NULL;
1010 }
1011
1012 co->livecookie = c->running;
1013 co->creationtime = ++c->lastct;
1014
1015 /*
1016 * Now we have parsed the incoming line, we must now check if this supersedes
1017 * an already existing cookie, which it may if the previous have the same
1018 * domain and path as this.
1019 */
1020
1021 /* at first, remove expired cookies */
1022 if(!noexpire)
1023 remove_expired(cookies: c);
1024
1025#ifdef USE_LIBPSL
1026 /*
1027 * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
1028 * must also check that the data handle isn't NULL since the psl code will
1029 * dereference it.
1030 */
1031 if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
1032 const psl_ctx_t *psl = Curl_psl_use(data);
1033 int acceptable;
1034
1035 if(psl) {
1036 acceptable = psl_is_cookie_domain_acceptable(psl, domain, co->domain);
1037 Curl_psl_release(data);
1038 }
1039 else
1040 acceptable = !bad_domain(domain, strlen(domain));
1041
1042 if(!acceptable) {
1043 infof(data, "cookie '%s' dropped, domain '%s' must not "
1044 "set cookies for '%s'", co->name, domain, co->domain);
1045 freecookie(co);
1046 return NULL;
1047 }
1048 }
1049#endif
1050
1051 /* A non-secure cookie may not overlay an existing secure cookie. */
1052 myhash = cookiehash(domain: co->domain);
1053 clist = c->cookies[myhash];
1054 while(clist) {
1055 if(strcasecompare(clist->name, co->name)) {
1056 /* the names are identical */
1057 bool matching_domains = FALSE;
1058
1059 if(clist->domain && co->domain) {
1060 if(strcasecompare(clist->domain, co->domain))
1061 /* The domains are identical */
1062 matching_domains = TRUE;
1063 }
1064 else if(!clist->domain && !co->domain)
1065 matching_domains = TRUE;
1066
1067 if(matching_domains && /* the domains were identical */
1068 clist->spath && co->spath && /* both have paths */
1069 clist->secure && !co->secure && !secure) {
1070 size_t cllen;
1071 const char *sep;
1072
1073 /*
1074 * A non-secure cookie may not overlay an existing secure cookie.
1075 * For an existing cookie "a" with path "/login", refuse a new
1076 * cookie "a" with for example path "/login/en", while the path
1077 * "/loginhelper" is ok.
1078 */
1079
1080 sep = strchr(s: clist->spath + 1, c: '/');
1081
1082 if(sep)
1083 cllen = sep - clist->spath;
1084 else
1085 cllen = strlen(s: clist->spath);
1086
1087 if(strncasecompare(clist->spath, co->spath, cllen)) {
1088 infof(data, "cookie '%s' for domain '%s' dropped, would "
1089 "overlay an existing cookie", co->name, co->domain);
1090 freecookie(co);
1091 return NULL;
1092 }
1093 }
1094 }
1095
1096 if(!replace_co && strcasecompare(clist->name, co->name)) {
1097 /* the names are identical */
1098
1099 if(clist->domain && co->domain) {
1100 if(strcasecompare(clist->domain, co->domain) &&
1101 (clist->tailmatch == co->tailmatch))
1102 /* The domains are identical */
1103 replace_old = TRUE;
1104 }
1105 else if(!clist->domain && !co->domain)
1106 replace_old = TRUE;
1107
1108 if(replace_old) {
1109 /* the domains were identical */
1110
1111 if(clist->spath && co->spath &&
1112 !strcasecompare(clist->spath, co->spath))
1113 replace_old = FALSE;
1114 else if(!clist->spath != !co->spath)
1115 replace_old = FALSE;
1116 }
1117
1118 if(replace_old && !co->livecookie && clist->livecookie) {
1119 /*
1120 * Both cookies matched fine, except that the already present cookie is
1121 * "live", which means it was set from a header, while the new one was
1122 * read from a file and thus isn't "live". "live" cookies are preferred
1123 * so the new cookie is freed.
1124 */
1125 freecookie(co);
1126 return NULL;
1127 }
1128 if(replace_old) {
1129 replace_co = co;
1130 replace_clist = clist;
1131 }
1132 }
1133 lastc = clist;
1134 clist = clist->next;
1135 }
1136 if(replace_co) {
1137 co = replace_co;
1138 clist = replace_clist;
1139 co->next = clist->next; /* get the next-pointer first */
1140
1141 /* when replacing, creationtime is kept from old */
1142 co->creationtime = clist->creationtime;
1143
1144 /* then free all the old pointers */
1145 free(clist->name);
1146 free(clist->value);
1147 free(clist->domain);
1148 free(clist->path);
1149 free(clist->spath);
1150
1151 *clist = *co; /* then store all the new data */
1152
1153 free(co); /* free the newly allocated memory */
1154 co = clist;
1155 }
1156
1157 if(c->running)
1158 /* Only show this when NOT reading the cookies from a file */
1159 infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1160 "expire %" CURL_FORMAT_CURL_OFF_T,
1161 replace_old?"Replaced":"Added", co->name, co->value,
1162 co->domain, co->path, co->expires);
1163
1164 if(!replace_old) {
1165 /* then make the last item point on this new one */
1166 if(lastc)
1167 lastc->next = co;
1168 else
1169 c->cookies[myhash] = co;
1170 c->numcookies++; /* one more cookie in the jar */
1171 }
1172
1173 /*
1174 * Now that we've added a new cookie to the jar, update the expiration
1175 * tracker in case it is the next one to expire.
1176 */
1177 if(co->expires && (co->expires < c->next_expiration))
1178 c->next_expiration = co->expires;
1179
1180 return co;
1181}
1182
1183
1184/*
1185 * Curl_cookie_init()
1186 *
1187 * Inits a cookie struct to read data from a local file. This is always
1188 * called before any cookies are set. File may be NULL in which case only the
1189 * struct is initialized. Is file is "-" then STDIN is read.
1190 *
1191 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1192 *
1193 * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1194 * will be ignored.
1195 *
1196 * Returns NULL on out of memory. Invalid cookies are ignored.
1197 */
1198struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
1199 const char *file,
1200 struct CookieInfo *inc,
1201 bool newsession)
1202{
1203 struct CookieInfo *c;
1204 char *line = NULL;
1205 FILE *handle = NULL;
1206
1207 if(!inc) {
1208 /* we didn't get a struct, create one */
1209 c = calloc(1, sizeof(struct CookieInfo));
1210 if(!c)
1211 return NULL; /* failed to get memory */
1212 /*
1213 * Initialize the next_expiration time to signal that we don't have enough
1214 * information yet.
1215 */
1216 c->next_expiration = CURL_OFF_T_MAX;
1217 }
1218 else {
1219 /* we got an already existing one, use that */
1220 c = inc;
1221 }
1222 c->newsession = newsession; /* new session? */
1223
1224 if(data) {
1225 FILE *fp = NULL;
1226 if(file) {
1227 if(!strcmp(s1: file, s2: "-"))
1228 fp = stdin;
1229 else {
1230 fp = fopen(filename: file, modes: "rb");
1231 if(!fp)
1232 infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1233 else
1234 handle = fp;
1235 }
1236 }
1237
1238 c->running = FALSE; /* this is not running, this is init */
1239 if(fp) {
1240
1241 line = malloc(MAX_COOKIE_LINE);
1242 if(!line)
1243 goto fail;
1244 while(Curl_get_line(buf: line, MAX_COOKIE_LINE, input: fp)) {
1245 char *lineptr = line;
1246 bool headerline = FALSE;
1247 if(checkprefix("Set-Cookie:", line)) {
1248 /* This is a cookie line, get it! */
1249 lineptr = &line[11];
1250 headerline = TRUE;
1251 while(*lineptr && ISBLANK(*lineptr))
1252 lineptr++;
1253 }
1254
1255 Curl_cookie_add(data, c, httpheader: headerline, TRUE, lineptr, NULL, NULL, TRUE);
1256 }
1257 free(line); /* free the line buffer */
1258
1259 /*
1260 * Remove expired cookies from the hash. We must make sure to run this
1261 * after reading the file, and not on every cookie.
1262 */
1263 remove_expired(cookies: c);
1264
1265 if(handle)
1266 fclose(stream: handle);
1267 }
1268 data->state.cookie_engine = TRUE;
1269 }
1270 c->running = TRUE; /* now, we're running */
1271
1272 return c;
1273
1274fail:
1275 free(line);
1276 /*
1277 * Only clean up if we allocated it here, as the original could still be in
1278 * use by a share handle.
1279 */
1280 if(!inc)
1281 Curl_cookie_cleanup(c);
1282 if(handle)
1283 fclose(stream: handle);
1284 return NULL; /* out of memory */
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 */
1295static 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(s: c1->path) : 0;
1303 l2 = c2->path ? strlen(s: 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(s: c1->domain) : 0;
1310 l2 = c2->domain ? strlen(s: 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(s: c1->name) : 0;
1317 l2 = c2->name ? strlen(s: 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 */
1331static 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#define CLONE(field) \
1340 do { \
1341 if(src->field) { \
1342 d->field = strdup(src->field); \
1343 if(!d->field) \
1344 goto fail; \
1345 } \
1346 } while(0)
1347
1348static struct Cookie *dup_cookie(struct Cookie *src)
1349{
1350 struct Cookie *d = calloc(sizeof(struct Cookie), 1);
1351 if(d) {
1352 CLONE(domain);
1353 CLONE(path);
1354 CLONE(spath);
1355 CLONE(name);
1356 CLONE(value);
1357 d->expires = src->expires;
1358 d->tailmatch = src->tailmatch;
1359 d->secure = src->secure;
1360 d->livecookie = src->livecookie;
1361 d->httponly = src->httponly;
1362 d->creationtime = src->creationtime;
1363 }
1364 return d;
1365
1366fail:
1367 freecookie(co: d);
1368 return NULL;
1369}
1370
1371/*
1372 * Curl_cookie_getlist
1373 *
1374 * For a given host and path, return a linked list of cookies that the client
1375 * should send to the server if used now. The secure boolean informs the cookie
1376 * if a secure connection is achieved or not.
1377 *
1378 * It shall only return cookies that haven't expired.
1379 */
1380struct Cookie *Curl_cookie_getlist(struct Curl_easy *data,
1381 struct CookieInfo *c,
1382 const char *host, const char *path,
1383 bool secure)
1384{
1385 struct Cookie *newco;
1386 struct Cookie *co;
1387 struct Cookie *mainco = NULL;
1388 size_t matches = 0;
1389 bool is_ip;
1390 const size_t myhash = cookiehash(domain: host);
1391
1392 if(!c || !c->cookies[myhash])
1393 return NULL; /* no cookie struct or no cookies in the struct */
1394
1395 /* at first, remove expired cookies */
1396 remove_expired(cookies: c);
1397
1398 /* check if host is an IP(v4|v6) address */
1399 is_ip = Curl_host_is_ipnum(hostname: host);
1400
1401 co = c->cookies[myhash];
1402
1403 while(co) {
1404 /* if the cookie requires we're secure we must only continue if we are! */
1405 if(co->secure?secure:TRUE) {
1406
1407 /* now check if the domain is correct */
1408 if(!co->domain ||
1409 (co->tailmatch && !is_ip &&
1410 cookie_tailmatch(cookie_domain: co->domain, cookie_domain_len: strlen(s: co->domain), hostname: host)) ||
1411 ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
1412 /*
1413 * the right part of the host matches the domain stuff in the
1414 * cookie data
1415 */
1416
1417 /*
1418 * now check the left part of the path with the cookies path
1419 * requirement
1420 */
1421 if(!co->spath || pathmatch(cookie_path: co->spath, request_uri: path) ) {
1422
1423 /*
1424 * and now, we know this is a match and we should create an
1425 * entry for the return-linked-list
1426 */
1427
1428 newco = dup_cookie(src: co);
1429 if(newco) {
1430 /* then modify our next */
1431 newco->next = mainco;
1432
1433 /* point the main to us */
1434 mainco = newco;
1435
1436 matches++;
1437 if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1438 infof(data, "Included max number of cookies (%zu) in request!",
1439 matches);
1440 break;
1441 }
1442 }
1443 else
1444 goto fail;
1445 }
1446 }
1447 }
1448 co = co->next;
1449 }
1450
1451 if(matches) {
1452 /*
1453 * Now we need to make sure that if there is a name appearing more than
1454 * once, the longest specified path version comes first. To make this
1455 * the swiftest way, we just sort them all based on path length.
1456 */
1457 struct Cookie **array;
1458 size_t i;
1459
1460 /* alloc an array and store all cookie pointers */
1461 array = malloc(sizeof(struct Cookie *) * matches);
1462 if(!array)
1463 goto fail;
1464
1465 co = mainco;
1466
1467 for(i = 0; co; co = co->next)
1468 array[i++] = co;
1469
1470 /* now sort the cookie pointers in path length order */
1471 qsort(base: array, nmemb: matches, size: sizeof(struct Cookie *), compar: cookie_sort);
1472
1473 /* remake the linked list order according to the new order */
1474
1475 mainco = array[0]; /* start here */
1476 for(i = 0; i<matches-1; i++)
1477 array[i]->next = array[i + 1];
1478 array[matches-1]->next = NULL; /* terminate the list */
1479
1480 free(array); /* remove the temporary data again */
1481 }
1482
1483 return mainco; /* return the new list */
1484
1485fail:
1486 /* failure, clear up the allocated chain and return NULL */
1487 Curl_cookie_freelist(cookies: mainco);
1488 return NULL;
1489}
1490
1491/*
1492 * Curl_cookie_clearall
1493 *
1494 * Clear all existing cookies and reset the counter.
1495 */
1496void Curl_cookie_clearall(struct CookieInfo *cookies)
1497{
1498 if(cookies) {
1499 unsigned int i;
1500 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1501 Curl_cookie_freelist(cookies: cookies->cookies[i]);
1502 cookies->cookies[i] = NULL;
1503 }
1504 cookies->numcookies = 0;
1505 }
1506}
1507
1508/*
1509 * Curl_cookie_freelist
1510 *
1511 * Free a list of cookies previously returned by Curl_cookie_getlist();
1512 */
1513void Curl_cookie_freelist(struct Cookie *co)
1514{
1515 struct Cookie *next;
1516 while(co) {
1517 next = co->next;
1518 freecookie(co);
1519 co = next;
1520 }
1521}
1522
1523/*
1524 * Curl_cookie_clearsess
1525 *
1526 * Free all session cookies in the cookies list.
1527 */
1528void Curl_cookie_clearsess(struct CookieInfo *cookies)
1529{
1530 struct Cookie *first, *curr, *next, *prev = NULL;
1531 unsigned int i;
1532
1533 if(!cookies)
1534 return;
1535
1536 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1537 if(!cookies->cookies[i])
1538 continue;
1539
1540 first = curr = prev = cookies->cookies[i];
1541
1542 for(; curr; curr = next) {
1543 next = curr->next;
1544 if(!curr->expires) {
1545 if(first == curr)
1546 first = next;
1547
1548 if(prev == curr)
1549 prev = next;
1550 else
1551 prev->next = next;
1552
1553 freecookie(co: curr);
1554 cookies->numcookies--;
1555 }
1556 else
1557 prev = curr;
1558 }
1559
1560 cookies->cookies[i] = first;
1561 }
1562}
1563
1564/*
1565 * Curl_cookie_cleanup()
1566 *
1567 * Free a "cookie object" previous created with Curl_cookie_init().
1568 */
1569void Curl_cookie_cleanup(struct CookieInfo *c)
1570{
1571 if(c) {
1572 unsigned int i;
1573 for(i = 0; i < COOKIE_HASH_SIZE; i++)
1574 Curl_cookie_freelist(co: c->cookies[i]);
1575 free(c); /* free the base struct as well */
1576 }
1577}
1578
1579/*
1580 * get_netscape_format()
1581 *
1582 * Formats a string for Netscape output file, w/o a newline at the end.
1583 * Function returns a char * to a formatted line. The caller is responsible
1584 * for freeing the returned pointer.
1585 */
1586static char *get_netscape_format(const struct Cookie *co)
1587{
1588 return aprintf(
1589 format: "%s" /* httponly preamble */
1590 "%s%s\t" /* domain */
1591 "%s\t" /* tailmatch */
1592 "%s\t" /* path */
1593 "%s\t" /* secure */
1594 "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */
1595 "%s\t" /* name */
1596 "%s", /* value */
1597 co->httponly?"#HttpOnly_":"",
1598 /*
1599 * Make sure all domains are prefixed with a dot if they allow
1600 * tailmatching. This is Mozilla-style.
1601 */
1602 (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
1603 co->domain?co->domain:"unknown",
1604 co->tailmatch?"TRUE":"FALSE",
1605 co->path?co->path:"/",
1606 co->secure?"TRUE":"FALSE",
1607 co->expires,
1608 co->name,
1609 co->value?co->value:"");
1610}
1611
1612/*
1613 * cookie_output()
1614 *
1615 * Writes all internally known cookies to the specified file. Specify
1616 * "-" as file name to write to stdout.
1617 *
1618 * The function returns non-zero on write failure.
1619 */
1620static CURLcode cookie_output(struct Curl_easy *data,
1621 struct CookieInfo *c, const char *filename)
1622{
1623 struct Cookie *co;
1624 FILE *out = NULL;
1625 bool use_stdout = FALSE;
1626 char *tempstore = NULL;
1627 CURLcode error = CURLE_OK;
1628
1629 if(!c)
1630 /* no cookie engine alive */
1631 return CURLE_OK;
1632
1633 /* at first, remove expired cookies */
1634 remove_expired(cookies: c);
1635
1636 if(!strcmp(s1: "-", s2: filename)) {
1637 /* use stdout */
1638 out = stdout;
1639 use_stdout = TRUE;
1640 }
1641 else {
1642 error = Curl_fopen(data, filename, fh: &out, tempname: &tempstore);
1643 if(error)
1644 goto error;
1645 }
1646
1647 fputs(s: "# Netscape HTTP Cookie File\n"
1648 "# https://curl.se/docs/http-cookies.html\n"
1649 "# This file was generated by libcurl! Edit at your own risk.\n\n",
1650 stream: out);
1651
1652 if(c->numcookies) {
1653 unsigned int i;
1654 size_t nvalid = 0;
1655 struct Cookie **array;
1656
1657 array = calloc(1, sizeof(struct Cookie *) * c->numcookies);
1658 if(!array) {
1659 error = CURLE_OUT_OF_MEMORY;
1660 goto error;
1661 }
1662
1663 /* only sort the cookies with a domain property */
1664 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1665 for(co = c->cookies[i]; co; co = co->next) {
1666 if(!co->domain)
1667 continue;
1668 array[nvalid++] = co;
1669 }
1670 }
1671
1672 qsort(base: array, nmemb: nvalid, size: sizeof(struct Cookie *), compar: cookie_sort_ct);
1673
1674 for(i = 0; i < nvalid; i++) {
1675 char *format_ptr = get_netscape_format(co: array[i]);
1676 if(!format_ptr) {
1677 free(array);
1678 error = CURLE_OUT_OF_MEMORY;
1679 goto error;
1680 }
1681 fprintf(fd: out, format: "%s\n", format_ptr);
1682 free(format_ptr);
1683 }
1684
1685 free(array);
1686 }
1687
1688 if(!use_stdout) {
1689 fclose(stream: out);
1690 out = NULL;
1691 if(tempstore && Curl_rename(oldpath: tempstore, newpath: filename)) {
1692 unlink(name: tempstore);
1693 error = CURLE_WRITE_ERROR;
1694 goto error;
1695 }
1696 }
1697
1698 /*
1699 * If we reach here we have successfully written a cookie file so there is
1700 * no need to inspect the error, any error case should have jumped into the
1701 * error block below.
1702 */
1703 free(tempstore);
1704 return CURLE_OK;
1705
1706error:
1707 if(out && !use_stdout)
1708 fclose(stream: out);
1709 free(tempstore);
1710 return error;
1711}
1712
1713static struct curl_slist *cookie_list(struct Curl_easy *data)
1714{
1715 struct curl_slist *list = NULL;
1716 struct curl_slist *beg;
1717 struct Cookie *c;
1718 char *line;
1719 unsigned int i;
1720
1721 if(!data->cookies || (data->cookies->numcookies == 0))
1722 return NULL;
1723
1724 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1725 for(c = data->cookies->cookies[i]; c; c = c->next) {
1726 if(!c->domain)
1727 continue;
1728 line = get_netscape_format(co: c);
1729 if(!line) {
1730 curl_slist_free_all(list);
1731 return NULL;
1732 }
1733 beg = Curl_slist_append_nodup(list, data: line);
1734 if(!beg) {
1735 free(line);
1736 curl_slist_free_all(list);
1737 return NULL;
1738 }
1739 list = beg;
1740 }
1741 }
1742
1743 return list;
1744}
1745
1746struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1747{
1748 struct curl_slist *list;
1749 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1750 list = cookie_list(data);
1751 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1752 return list;
1753}
1754
1755void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1756{
1757 CURLcode res;
1758
1759 if(data->set.str[STRING_COOKIEJAR]) {
1760 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1761
1762 /* if we have a destination file for all the cookies to get dumped to */
1763 res = cookie_output(data, c: data->cookies, filename: data->set.str[STRING_COOKIEJAR]);
1764 if(res)
1765 infof(data, "WARNING: failed to save cookies in %s: %s",
1766 data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
1767 }
1768 else {
1769 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1770 }
1771
1772 if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1773 Curl_cookie_cleanup(c: data->cookies);
1774 data->cookies = NULL;
1775 }
1776 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1777}
1778
1779#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
1780