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 /* Make sure domain contains a dot or is localhost. */
bad_domain(const char *domain, size_t len)430 static bool bad_domain(const char *domain, size_t len)
431 {
432 if((len == 9) && strncasecompare(domain, "localhost", 9))
433 return FALSE;
434 else {
435 /* there must be a dot present, but that dot must not be a trailing dot */
436 char *dot = memchr(domain, '.', len);
437 if(dot) {
438 size_t i = dot - domain;
439 if((len - i) > 1)
440 /* the dot is not the last byte */
441 return FALSE;
442 }
443 }
444 return TRUE;
445 }
446
447 /*
448 RFC 6265 section 4.1.1 says a server should accept this range:
449
450 cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
451
452 But Firefox and Chrome as of June 2022 accept space, comma and double-quotes
453 fine. The prime reason for filtering out control bytes is that some HTTP
454 servers return 400 for requests that contain such.
455 */
invalid_octets(const char *p)456 static int invalid_octets(const char *p)
457 {
458 /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */
459 static const char badoctets[] = {
460 "\x01\x02\x03\x04\x05\x06\x07\x08\x0a"
461 "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
462 "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"
463 };
464 size_t len;
465 /* scan for all the octets that are *not* in cookie-octet */
466 len = strcspn(p, badoctets);
467 return (p[len] != '\0');
468 }
469
470 /*
471 * Curl_cookie_add
472 *
473 * Add a single cookie line to the cookie keeping object. Be aware that
474 * sometimes we get an IP-only host name, and that might also be a numerical
475 * IPv6 address.
476 *
477 * Returns NULL on out of memory or invalid cookie. This is suboptimal,
478 * as they should be treated separately.
479 */
480 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)481 Curl_cookie_add(struct Curl_easy *data,
482 struct CookieInfo *c,
483 bool httpheader, /* TRUE if HTTP header-style line */
484 bool noexpire, /* if TRUE, skip remove_expired() */
485 const char *lineptr, /* first character of the line */
486 const char *domain, /* default domain */
487 const char *path, /* full path used when this cookie is set,
488 used to get default path for the cookie
489 unless set */
490 bool secure) /* TRUE if connection is over secure origin */
491 {
492 struct Cookie *clist;
493 struct Cookie *co;
494 struct Cookie *lastc = NULL;
495 struct Cookie *replace_co = NULL;
496 struct Cookie *replace_clist = NULL;
497 time_t now = time(NULL);
498 bool replace_old = FALSE;
499 bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
500 size_t myhash;
501
502 DEBUGASSERT(data);
503 DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
504 if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
505 return NULL;
506
507 /* First, alloc and init a new struct for it */
508 co = calloc(1, sizeof(struct Cookie));
509 if(!co)
510 return NULL; /* bail out if we're this low on memory */
511
512 if(httpheader) {
513 /* This line was read off an HTTP-header */
514 const char *ptr;
515
516 size_t linelength = strlen(lineptr);
517 if(linelength > MAX_COOKIE_LINE) {
518 /* discard overly long lines at once */
519 free(co);
520 return NULL;
521 }
522
523 ptr = lineptr;
524 do {
525 size_t vlen;
526 size_t nlen;
527
528 while(*ptr && ISBLANK(*ptr))
529 ptr++;
530
531 /* we have a <name>=<value> pair or a stand-alone word here */
532 nlen = strcspn(ptr, ";\t\r\n=");
533 if(nlen) {
534 bool done = FALSE;
535 bool sep = FALSE;
536 const char *namep = ptr;
537 const char *valuep;
538
539 ptr += nlen;
540
541 /* trim trailing spaces and tabs after name */
542 while(nlen && ISBLANK(namep[nlen - 1]))
543 nlen--;
544
545 if(*ptr == '=') {
546 vlen = strcspn(++ptr, ";\r\n");
547 valuep = ptr;
548 sep = TRUE;
549 ptr = &valuep[vlen];
550
551 /* Strip off trailing whitespace from the value */
552 while(vlen && ISBLANK(valuep[vlen-1]))
553 vlen--;
554
555 /* Skip leading whitespace from the value */
556 while(vlen && ISBLANK(*valuep)) {
557 valuep++;
558 vlen--;
559 }
560
561 /* Reject cookies with a TAB inside the value */
562 if(memchr(valuep, '\t', vlen)) {
563 freecookie(co);
564 infof(data, "cookie contains TAB, dropping");
565 return NULL;
566 }
567 }
568 else {
569 valuep = NULL;
570 vlen = 0;
571 }
572
573 /*
574 * Check for too long individual name or contents, or too long
575 * combination of name + contents. Chrome and Firefox support 4095 or
576 * 4096 bytes combo
577 */
578 if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) ||
579 ((nlen + vlen) > MAX_NAME)) {
580 freecookie(co);
581 infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
582 nlen, vlen);
583 return NULL;
584 }
585
586 /*
587 * Check if we have a reserved prefix set before anything else, as we
588 * otherwise have to test for the prefix in both the cookie name and
589 * "the rest". Prefixes must start with '__' and end with a '-', so
590 * only test for names where that can possibly be true.
591 */
592 if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') {
593 if(strncasecompare("__Secure-", namep, 9))
594 co->prefix |= COOKIE_PREFIX__SECURE;
595 else if(strncasecompare("__Host-", namep, 7))
596 co->prefix |= COOKIE_PREFIX__HOST;
597 }
598
599 /*
600 * Use strstore() below to properly deal with received cookie
601 * headers that have the same string property set more than once,
602 * and then we use the last one.
603 */
604
605 if(!co->name) {
606 /* The very first name/value pair is the actual cookie name */
607 if(!sep) {
608 /* Bad name/value pair. */
609 badcookie = TRUE;
610 break;
611 }
612 strstore(&co->name, namep, nlen);
613 strstore(&co->value, valuep, vlen);
614 done = TRUE;
615 if(!co->name || !co->value) {
616 badcookie = TRUE;
617 break;
618 }
619 if(invalid_octets(co->value) || invalid_octets(co->name)) {
620 infof(data, "invalid octets in name/value, cookie dropped");
621 badcookie = TRUE;
622 break;
623 }
624 }
625 else if(!vlen) {
626 /*
627 * this was a "<name>=" with no content, and we must allow
628 * 'secure' and 'httponly' specified this weirdly
629 */
630 done = TRUE;
631 /*
632 * secure cookies are only allowed to be set when the connection is
633 * using a secure protocol, or when the cookie is being set by
634 * reading from file
635 */
636 if((nlen == 6) && strncasecompare("secure", namep, 6)) {
637 if(secure || !c->running) {
638 co->secure = TRUE;
639 }
640 else {
641 badcookie = TRUE;
642 break;
643 }
644 }
645 else if((nlen == 8) && strncasecompare("httponly", namep, 8))
646 co->httponly = TRUE;
647 else if(sep)
648 /* there was a '=' so we're not done parsing this field */
649 done = FALSE;
650 }
651 if(done)
652 ;
653 else if((nlen == 4) && strncasecompare("path", namep, 4)) {
654 strstore(&co->path, valuep, vlen);
655 if(!co->path) {
656 badcookie = TRUE; /* out of memory bad */
657 break;
658 }
659 free(co->spath); /* if this is set again */
660 co->spath = sanitize_cookie_path(co->path);
661 if(!co->spath) {
662 badcookie = TRUE; /* out of memory bad */
663 break;
664 }
665 }
666 else if((nlen == 6) &&
667 strncasecompare("domain", namep, 6) && vlen) {
668 bool is_ip;
669
670 /*
671 * Now, we make sure that our host is within the given domain, or
672 * the given domain is not valid and thus cannot be set.
673 */
674
675 if('.' == valuep[0]) {
676 valuep++; /* ignore preceding dot */
677 vlen--;
678 }
679
680 #ifndef USE_LIBPSL
681 /*
682 * Without PSL we don't know when the incoming cookie is set on a
683 * TLD or otherwise "protected" suffix. To reduce risk, we require a
684 * dot OR the exact host name being "localhost".
685 */
686 if(bad_domain(valuep, vlen))
687 domain = ":";
688 #endif
689
690 is_ip = Curl_host_is_ipnum(domain ? domain : valuep);
691
692 if(!domain
693 || (is_ip && !strncmp(valuep, domain, vlen) &&
694 (vlen == strlen(domain)))
695 || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) {
696 strstore(&co->domain, valuep, vlen);
697 if(!co->domain) {
698 badcookie = TRUE;
699 break;
700 }
701 if(!is_ip)
702 co->tailmatch = TRUE; /* we always do that if the domain name was
703 given */
704 }
705 else {
706 /*
707 * We did not get a tailmatch and then the attempted set domain is
708 * not a domain to which the current host belongs. Mark as bad.
709 */
710 badcookie = TRUE;
711 infof(data, "skipped cookie with bad tailmatch domain: %s",
712 valuep);
713 }
714 }
715 else if((nlen == 7) && strncasecompare("version", namep, 7)) {
716 /* just ignore */
717 }
718 else if((nlen == 7) && strncasecompare("max-age", namep, 7)) {
719 /*
720 * Defined in RFC2109:
721 *
722 * Optional. The Max-Age attribute defines the lifetime of the
723 * cookie, in seconds. The delta-seconds value is a decimal non-
724 * negative integer. After delta-seconds seconds elapse, the
725 * client should discard the cookie. A value of zero means the
726 * cookie should be discarded immediately.
727 */
728 CURLofft offt;
729 const char *maxage = valuep;
730 offt = curlx_strtoofft((*maxage == '\"')?
731 &maxage[1]:&maxage[0], NULL, 10,
732 &co->expires);
733 switch(offt) {
734 case CURL_OFFT_FLOW:
735 /* overflow, used max value */
736 co->expires = CURL_OFF_T_MAX;
737 break;
738 case CURL_OFFT_INVAL:
739 /* negative or otherwise bad, expire */
740 co->expires = 1;
741 break;
742 case CURL_OFFT_OK:
743 if(!co->expires)
744 /* already expired */
745 co->expires = 1;
746 else if(CURL_OFF_T_MAX - now < co->expires)
747 /* would overflow */
748 co->expires = CURL_OFF_T_MAX;
749 else
750 co->expires += now;
751 break;
752 }
753 }
754 else if((nlen == 7) && strncasecompare("expires", namep, 7)) {
755 char date[128];
756 if(!co->expires && (vlen < sizeof(date))) {
757 /* copy the date so that it can be null terminated */
758 memcpy(date, valuep, vlen);
759 date[vlen] = 0;
760 /*
761 * Let max-age have priority.
762 *
763 * If the date cannot get parsed for whatever reason, the cookie
764 * will be treated as a session cookie
765 */
766 co->expires = Curl_getdate_capped(date);
767
768 /*
769 * Session cookies have expires set to 0 so if we get that back
770 * from the date parser let's add a second to make it a
771 * non-session cookie
772 */
773 if(co->expires == 0)
774 co->expires = 1;
775 else if(co->expires < 0)
776 co->expires = 0;
777 }
778 }
779
780 /*
781 * Else, this is the second (or more) name we don't know about!
782 */
783 }
784 else {
785 /* this is an "illegal" <what>=<this> pair */
786 }
787
788 while(*ptr && ISBLANK(*ptr))
789 ptr++;
790 if(*ptr == ';')
791 ptr++;
792 else
793 break;
794 } while(1);
795
796 if(!badcookie && !co->domain) {
797 if(domain) {
798 /* no domain was given in the header line, set the default */
799 co->domain = strdup(domain);
800 if(!co->domain)
801 badcookie = TRUE;
802 }
803 }
804
805 if(!badcookie && !co->path && path) {
806 /*
807 * No path was given in the header line, set the default. Note that the
808 * passed-in path to this function MAY have a '?' and following part that
809 * MUST NOT be stored as part of the path.
810 */
811 char *queryp = strchr(path, '?');
812
813 /*
814 * queryp is where the interesting part of the path ends, so now we
815 * want to the find the last
816 */
817 char *endslash;
818 if(!queryp)
819 endslash = strrchr(path, '/');
820 else
821 endslash = memrchr(path, '/', (queryp - path));
822 if(endslash) {
823 size_t pathlen = (endslash-path + 1); /* include end slash */
824 co->path = Curl_memdup0(path, pathlen);
825 if(co->path) {
826 co->spath = sanitize_cookie_path(co->path);
827 if(!co->spath)
828 badcookie = TRUE; /* out of memory bad */
829 }
830 else
831 badcookie = TRUE;
832 }
833 }
834
835 /*
836 * If we didn't get a cookie name, or a bad one, the this is an illegal
837 * line so bail out.
838 */
839 if(badcookie || !co->name) {
840 freecookie(co);
841 return NULL;
842 }
843 data->req.setcookies++;
844 }
845 else {
846 /*
847 * This line is NOT an HTTP header style line, we do offer support for
848 * reading the odd netscape cookies-file format here
849 */
850 char *ptr;
851 char *firstptr;
852 char *tok_buf = NULL;
853 int fields;
854
855 /*
856 * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked
857 * with httpOnly after the domain name are not accessible from javascripts,
858 * but since curl does not operate at javascript level, we include them
859 * anyway. In Firefox's cookie files, these lines are preceded with
860 * #HttpOnly_ and then everything is as usual, so we skip 10 characters of
861 * the line..
862 */
863 if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
864 lineptr += 10;
865 co->httponly = TRUE;
866 }
867
868 if(lineptr[0]=='#') {
869 /* don't even try the comments */
870 free(co);
871 return NULL;
872 }
873 /* strip off the possible end-of-line characters */
874 ptr = strchr(lineptr, '\r');
875 if(ptr)
876 *ptr = 0; /* clear it */
877 ptr = strchr(lineptr, '\n');
878 if(ptr)
879 *ptr = 0; /* clear it */
880
881 firstptr = strtok_r((char *)lineptr, "\t", &tok_buf); /* tokenize on TAB */
882
883 /*
884 * Now loop through the fields and init the struct we already have
885 * allocated
886 */
887 for(ptr = firstptr, fields = 0; ptr && !badcookie;
888 ptr = strtok_r(NULL, "\t", &tok_buf), fields++) {
889 switch(fields) {
890 case 0:
891 if(ptr[0]=='.') /* skip preceding dots */
892 ptr++;
893 co->domain = strdup(ptr);
894 if(!co->domain)
895 badcookie = TRUE;
896 break;
897 case 1:
898 /*
899 * flag: A TRUE/FALSE value indicating if all machines within a given
900 * domain can access the variable. Set TRUE when the cookie says
901 * .domain.com and to false when the domain is complete www.domain.com
902 */
903 co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
904 break;
905 case 2:
906 /* The file format allows the path field to remain not filled in */
907 if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
908 /* only if the path doesn't look like a boolean option! */
909 co->path = strdup(ptr);
910 if(!co->path)
911 badcookie = TRUE;
912 else {
913 co->spath = sanitize_cookie_path(co->path);
914 if(!co->spath) {
915 badcookie = TRUE; /* out of memory bad */
916 }
917 }
918 break;
919 }
920 /* this doesn't look like a path, make one up! */
921 co->path = strdup("/");
922 if(!co->path)
923 badcookie = TRUE;
924 co->spath = strdup("/");
925 if(!co->spath)
926 badcookie = TRUE;
927 fields++; /* add a field and fall down to secure */
928 FALLTHROUGH();
929 case 3:
930 co->secure = FALSE;
931 if(strcasecompare(ptr, "TRUE")) {
932 if(secure || c->running)
933 co->secure = TRUE;
934 else
935 badcookie = TRUE;
936 }
937 break;
938 case 4:
939 if(curlx_strtoofft(ptr, NULL, 10, &co->expires))
940 badcookie = TRUE;
941 break;
942 case 5:
943 co->name = strdup(ptr);
944 if(!co->name)
945 badcookie = TRUE;
946 else {
947 /* For Netscape file format cookies we check prefix on the name */
948 if(strncasecompare("__Secure-", co->name, 9))
949 co->prefix |= COOKIE_PREFIX__SECURE;
950 else if(strncasecompare("__Host-", co->name, 7))
951 co->prefix |= COOKIE_PREFIX__HOST;
952 }
953 break;
954 case 6:
955 co->value = strdup(ptr);
956 if(!co->value)
957 badcookie = TRUE;
958 break;
959 }
960 }
961 if(6 == fields) {
962 /* we got a cookie with blank contents, fix it */
963 co->value = strdup("");
964 if(!co->value)
965 badcookie = TRUE;
966 else
967 fields++;
968 }
969
970 if(!badcookie && (7 != fields))
971 /* we did not find the sufficient number of fields */
972 badcookie = TRUE;
973
974 if(badcookie) {
975 freecookie(co);
976 return NULL;
977 }
978
979 }
980
981 if(co->prefix & COOKIE_PREFIX__SECURE) {
982 /* The __Secure- prefix only requires that the cookie be set secure */
983 if(!co->secure) {
984 freecookie(co);
985 return NULL;
986 }
987 }
988 if(co->prefix & COOKIE_PREFIX__HOST) {
989 /*
990 * The __Host- prefix requires the cookie to be secure, have a "/" path
991 * and not have a domain set.
992 */
993 if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
994 ;
995 else {
996 freecookie(co);
997 return NULL;
998 }
999 }
1000
1001 if(!c->running && /* read from a file */
1002 c->newsession && /* clean session cookies */
1003 !co->expires) { /* this is a session cookie since it doesn't expire! */
1004 freecookie(co);
1005 return NULL;
1006 }
1007
1008 co->livecookie = c->running;
1009 co->creationtime = ++c->lastct;
1010
1011 /*
1012 * Now we have parsed the incoming line, we must now check if this supersedes
1013 * an already existing cookie, which it may if the previous have the same
1014 * domain and path as this.
1015 */
1016
1017 /* at first, remove expired cookies */
1018 if(!noexpire)
1019 remove_expired(c);
1020
1021 #ifdef USE_LIBPSL
1022 /*
1023 * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
1024 * must also check that the data handle isn't NULL since the psl code will
1025 * dereference it.
1026 */
1027 if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
1028 bool acceptable = FALSE;
1029 char lcase[256];
1030 char lcookie[256];
1031 size_t dlen = strlen(domain);
1032 size_t clen = strlen(co->domain);
1033 if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {
1034 const psl_ctx_t *psl = Curl_psl_use(data);
1035 if(psl) {
1036 /* the PSL check requires lowercase domain name and pattern */
1037 Curl_strntolower(lcase, domain, dlen + 1);
1038 Curl_strntolower(lcookie, co->domain, clen + 1);
1039 acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);
1040 Curl_psl_release(data);
1041 }
1042 else
1043 acceptable = !bad_domain(domain, strlen(domain));
1044 }
1045
1046 if(!acceptable) {
1047 infof(data, "cookie '%s' dropped, domain '%s' must not "
1048 "set cookies for '%s'", co->name, domain, co->domain);
1049 freecookie(co);
1050 return NULL;
1051 }
1052 }
1053 #endif
1054
1055 /* A non-secure cookie may not overlay an existing secure cookie. */
1056 myhash = cookiehash(co->domain);
1057 clist = c->cookies[myhash];
1058 while(clist) {
1059 if(strcasecompare(clist->name, co->name)) {
1060 /* the names are identical */
1061 bool matching_domains = FALSE;
1062
1063 if(clist->domain && co->domain) {
1064 if(strcasecompare(clist->domain, co->domain))
1065 /* The domains are identical */
1066 matching_domains = TRUE;
1067 }
1068 else if(!clist->domain && !co->domain)
1069 matching_domains = TRUE;
1070
1071 if(matching_domains && /* the domains were identical */
1072 clist->spath && co->spath && /* both have paths */
1073 clist->secure && !co->secure && !secure) {
1074 size_t cllen;
1075 const char *sep;
1076
1077 /*
1078 * A non-secure cookie may not overlay an existing secure cookie.
1079 * For an existing cookie "a" with path "/login", refuse a new
1080 * cookie "a" with for example path "/login/en", while the path
1081 * "/loginhelper" is ok.
1082 */
1083
1084 sep = strchr(clist->spath + 1, '/');
1085
1086 if(sep)
1087 cllen = sep - clist->spath;
1088 else
1089 cllen = strlen(clist->spath);
1090
1091 if(strncasecompare(clist->spath, co->spath, cllen)) {
1092 infof(data, "cookie '%s' for domain '%s' dropped, would "
1093 "overlay an existing cookie", co->name, co->domain);
1094 freecookie(co);
1095 return NULL;
1096 }
1097 }
1098 }
1099
1100 if(!replace_co && strcasecompare(clist->name, co->name)) {
1101 /* the names are identical */
1102
1103 if(clist->domain && co->domain) {
1104 if(strcasecompare(clist->domain, co->domain) &&
1105 (clist->tailmatch == co->tailmatch))
1106 /* The domains are identical */
1107 replace_old = TRUE;
1108 }
1109 else if(!clist->domain && !co->domain)
1110 replace_old = TRUE;
1111
1112 if(replace_old) {
1113 /* the domains were identical */
1114
1115 if(clist->spath && co->spath &&
1116 !strcasecompare(clist->spath, co->spath))
1117 replace_old = FALSE;
1118 else if(!clist->spath != !co->spath)
1119 replace_old = FALSE;
1120 }
1121
1122 if(replace_old && !co->livecookie && clist->livecookie) {
1123 /*
1124 * Both cookies matched fine, except that the already present cookie is
1125 * "live", which means it was set from a header, while the new one was
1126 * read from a file and thus isn't "live". "live" cookies are preferred
1127 * so the new cookie is freed.
1128 */
1129 freecookie(co);
1130 return NULL;
1131 }
1132 if(replace_old) {
1133 replace_co = co;
1134 replace_clist = clist;
1135 }
1136 }
1137 lastc = clist;
1138 clist = clist->next;
1139 }
1140 if(replace_co) {
1141 co = replace_co;
1142 clist = replace_clist;
1143 co->next = clist->next; /* get the next-pointer first */
1144
1145 /* when replacing, creationtime is kept from old */
1146 co->creationtime = clist->creationtime;
1147
1148 /* then free all the old pointers */
1149 free(clist->name);
1150 free(clist->value);
1151 free(clist->domain);
1152 free(clist->path);
1153 free(clist->spath);
1154
1155 *clist = *co; /* then store all the new data */
1156
1157 free(co); /* free the newly allocated memory */
1158 co = clist;
1159 }
1160
1161 if(c->running)
1162 /* Only show this when NOT reading the cookies from a file */
1163 infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1164 "expire %" CURL_FORMAT_CURL_OFF_T,
1165 replace_old?"Replaced":"Added", co->name, co->value,
1166 co->domain, co->path, co->expires);
1167
1168 if(!replace_old) {
1169 /* then make the last item point on this new one */
1170 if(lastc)
1171 lastc->next = co;
1172 else
1173 c->cookies[myhash] = co;
1174 c->numcookies++; /* one more cookie in the jar */
1175 }
1176
1177 /*
1178 * Now that we've added a new cookie to the jar, update the expiration
1179 * tracker in case it is the next one to expire.
1180 */
1181 if(co->expires && (co->expires < c->next_expiration))
1182 c->next_expiration = co->expires;
1183
1184 return co;
1185 }
1186
1187
1188 /*
1189 * Curl_cookie_init()
1190 *
1191 * Inits a cookie struct to read data from a local file. This is always
1192 * called before any cookies are set. File may be NULL in which case only the
1193 * struct is initialized. Is file is "-" then STDIN is read.
1194 *
1195 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1196 *
1197 * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1198 * will be ignored.
1199 *
1200 * Returns NULL on out of memory. Invalid cookies are ignored.
1201 */
Curl_cookie_init(struct Curl_easy *data, const char *file, struct CookieInfo *inc, bool newsession)1202 struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
1203 const char *file,
1204 struct CookieInfo *inc,
1205 bool newsession)
1206 {
1207 struct CookieInfo *c;
1208 char *line = NULL;
1209 FILE *handle = NULL;
1210
1211 if(!inc) {
1212 /* we didn't get a struct, create one */
1213 c = calloc(1, sizeof(struct CookieInfo));
1214 if(!c)
1215 return NULL; /* failed to get memory */
1216 /*
1217 * Initialize the next_expiration time to signal that we don't have enough
1218 * information yet.
1219 */
1220 c->next_expiration = CURL_OFF_T_MAX;
1221 }
1222 else {
1223 /* we got an already existing one, use that */
1224 c = inc;
1225 }
1226 c->newsession = newsession; /* new session? */
1227
1228 if(data) {
1229 FILE *fp = NULL;
1230 if(file && *file) {
1231 if(!strcmp(file, "-"))
1232 fp = stdin;
1233 else {
1234 fp = fopen(file, "rb");
1235 if(!fp)
1236 infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1237 else
1238 handle = fp;
1239 }
1240 }
1241
1242 c->running = FALSE; /* this is not running, this is init */
1243 if(fp) {
1244
1245 line = malloc(MAX_COOKIE_LINE);
1246 if(!line)
1247 goto fail;
1248 while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
1249 char *lineptr = line;
1250 bool headerline = FALSE;
1251 if(checkprefix("Set-Cookie:", line)) {
1252 /* This is a cookie line, get it! */
1253 lineptr = &line[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 free(line); /* 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 fail:
1279 free(line);
1280 /*
1281 * Only clean up if we allocated it here, as the original could still be in
1282 * use by a share handle.
1283 */
1284 if(!inc)
1285 Curl_cookie_cleanup(c);
1286 if(handle)
1287 fclose(handle);
1288 return NULL; /* out of memory */
1289 }
1290
1291 /*
1292 * cookie_sort
1293 *
1294 * Helper function to sort cookies such that the longest path gets before the
1295 * shorter path. Path, domain and name lengths are considered in that order,
1296 * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1297 * be unique per cookie, so we know we will get an ordering at that point.
1298 */
cookie_sort(const void *p1, const void *p2)1299 static int cookie_sort(const void *p1, const void *p2)
1300 {
1301 struct Cookie *c1 = *(struct Cookie **)p1;
1302 struct Cookie *c2 = *(struct Cookie **)p2;
1303 size_t l1, l2;
1304
1305 /* 1 - compare cookie path lengths */
1306 l1 = c1->path ? strlen(c1->path) : 0;
1307 l2 = c2->path ? strlen(c2->path) : 0;
1308
1309 if(l1 != l2)
1310 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
1311
1312 /* 2 - compare cookie domain lengths */
1313 l1 = c1->domain ? strlen(c1->domain) : 0;
1314 l2 = c2->domain ? strlen(c2->domain) : 0;
1315
1316 if(l1 != l2)
1317 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
1318
1319 /* 3 - compare cookie name lengths */
1320 l1 = c1->name ? strlen(c1->name) : 0;
1321 l2 = c2->name ? strlen(c2->name) : 0;
1322
1323 if(l1 != l2)
1324 return (l2 > l1) ? 1 : -1;
1325
1326 /* 4 - compare cookie creation time */
1327 return (c2->creationtime > c1->creationtime) ? 1 : -1;
1328 }
1329
1330 /*
1331 * cookie_sort_ct
1332 *
1333 * Helper function to sort cookies according to creation time.
1334 */
cookie_sort_ct(const void *p1, const void *p2)1335 static int cookie_sort_ct(const void *p1, const void *p2)
1336 {
1337 struct Cookie *c1 = *(struct Cookie **)p1;
1338 struct Cookie *c2 = *(struct Cookie **)p2;
1339
1340 return (c2->creationtime > c1->creationtime) ? 1 : -1;
1341 }
1342
1343 #define CLONE(field) \
1344 do { \
1345 if(src->field) { \
1346 d->field = strdup(src->field); \
1347 if(!d->field) \
1348 goto fail; \
1349 } \
1350 } while(0)
1351
dup_cookie(struct Cookie *src)1352 static struct Cookie *dup_cookie(struct Cookie *src)
1353 {
1354 struct Cookie *d = calloc(1, sizeof(struct Cookie));
1355 if(d) {
1356 CLONE(domain);
1357 CLONE(path);
1358 CLONE(spath);
1359 CLONE(name);
1360 CLONE(value);
1361 d->expires = src->expires;
1362 d->tailmatch = src->tailmatch;
1363 d->secure = src->secure;
1364 d->livecookie = src->livecookie;
1365 d->httponly = src->httponly;
1366 d->creationtime = src->creationtime;
1367 }
1368 return d;
1369
1370 fail:
1371 freecookie(d);
1372 return NULL;
1373 }
1374
1375 /*
1376 * Curl_cookie_getlist
1377 *
1378 * For a given host and path, return a linked list of cookies that the client
1379 * should send to the server if used now. The secure boolean informs the cookie
1380 * if a secure connection is achieved or not.
1381 *
1382 * It shall only return cookies that haven't expired.
1383 */
Curl_cookie_getlist(struct Curl_easy *data, struct CookieInfo *c, const char *host, const char *path, bool secure)1384 struct Cookie *Curl_cookie_getlist(struct Curl_easy *data,
1385 struct CookieInfo *c,
1386 const char *host, const char *path,
1387 bool secure)
1388 {
1389 struct Cookie *newco;
1390 struct Cookie *co;
1391 struct Cookie *mainco = NULL;
1392 size_t matches = 0;
1393 bool is_ip;
1394 const size_t myhash = cookiehash(host);
1395
1396 if(!c || !c->cookies[myhash])
1397 return NULL; /* no cookie struct or no cookies in the struct */
1398
1399 /* at first, remove expired cookies */
1400 remove_expired(c);
1401
1402 /* check if host is an IP(v4|v6) address */
1403 is_ip = Curl_host_is_ipnum(host);
1404
1405 co = c->cookies[myhash];
1406
1407 while(co) {
1408 /* if the cookie requires we're secure we must only continue if we are! */
1409 if(co->secure?secure:TRUE) {
1410
1411 /* now check if the domain is correct */
1412 if(!co->domain ||
1413 (co->tailmatch && !is_ip &&
1414 cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1415 ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
1416 /*
1417 * the right part of the host matches the domain stuff in the
1418 * cookie data
1419 */
1420
1421 /*
1422 * now check the left part of the path with the cookies path
1423 * requirement
1424 */
1425 if(!co->spath || pathmatch(co->spath, path) ) {
1426
1427 /*
1428 * and now, we know this is a match and we should create an
1429 * entry for the return-linked-list
1430 */
1431
1432 newco = dup_cookie(co);
1433 if(newco) {
1434 /* then modify our next */
1435 newco->next = mainco;
1436
1437 /* point the main to us */
1438 mainco = newco;
1439
1440 matches++;
1441 if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1442 infof(data, "Included max number of cookies (%zu) in request!",
1443 matches);
1444 break;
1445 }
1446 }
1447 else
1448 goto fail;
1449 }
1450 }
1451 }
1452 co = co->next;
1453 }
1454
1455 if(matches) {
1456 /*
1457 * Now we need to make sure that if there is a name appearing more than
1458 * once, the longest specified path version comes first. To make this
1459 * the swiftest way, we just sort them all based on path length.
1460 */
1461 struct Cookie **array;
1462 size_t i;
1463
1464 /* alloc an array and store all cookie pointers */
1465 array = malloc(sizeof(struct Cookie *) * matches);
1466 if(!array)
1467 goto fail;
1468
1469 co = mainco;
1470
1471 for(i = 0; co; co = co->next)
1472 array[i++] = co;
1473
1474 /* now sort the cookie pointers in path length order */
1475 qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1476
1477 /* remake the linked list order according to the new order */
1478
1479 mainco = array[0]; /* start here */
1480 for(i = 0; i<matches-1; i++)
1481 array[i]->next = array[i + 1];
1482 array[matches-1]->next = NULL; /* terminate the list */
1483
1484 free(array); /* remove the temporary data again */
1485 }
1486
1487 return mainco; /* return the new list */
1488
1489 fail:
1490 /* failure, clear up the allocated chain and return NULL */
1491 Curl_cookie_freelist(mainco);
1492 return NULL;
1493 }
1494
1495 /*
1496 * Curl_cookie_clearall
1497 *
1498 * Clear all existing cookies and reset the counter.
1499 */
Curl_cookie_clearall(struct CookieInfo *cookies)1500 void Curl_cookie_clearall(struct CookieInfo *cookies)
1501 {
1502 if(cookies) {
1503 unsigned int i;
1504 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1505 Curl_cookie_freelist(cookies->cookies[i]);
1506 cookies->cookies[i] = NULL;
1507 }
1508 cookies->numcookies = 0;
1509 }
1510 }
1511
1512 /*
1513 * Curl_cookie_freelist
1514 *
1515 * Free a list of cookies previously returned by Curl_cookie_getlist();
1516 */
Curl_cookie_freelist(struct Cookie *co)1517 void Curl_cookie_freelist(struct Cookie *co)
1518 {
1519 struct Cookie *next;
1520 while(co) {
1521 next = co->next;
1522 freecookie(co);
1523 co = next;
1524 }
1525 }
1526
1527 /*
1528 * Curl_cookie_clearsess
1529 *
1530 * Free all session cookies in the cookies list.
1531 */
Curl_cookie_clearsess(struct CookieInfo *cookies)1532 void Curl_cookie_clearsess(struct CookieInfo *cookies)
1533 {
1534 struct Cookie *first, *curr, *next, *prev = NULL;
1535 unsigned int i;
1536
1537 if(!cookies)
1538 return;
1539
1540 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1541 if(!cookies->cookies[i])
1542 continue;
1543
1544 first = curr = prev = cookies->cookies[i];
1545
1546 for(; curr; curr = next) {
1547 next = curr->next;
1548 if(!curr->expires) {
1549 if(first == curr)
1550 first = next;
1551
1552 if(prev == curr)
1553 prev = next;
1554 else
1555 prev->next = next;
1556
1557 freecookie(curr);
1558 cookies->numcookies--;
1559 }
1560 else
1561 prev = curr;
1562 }
1563
1564 cookies->cookies[i] = first;
1565 }
1566 }
1567
1568 /*
1569 * Curl_cookie_cleanup()
1570 *
1571 * Free a "cookie object" previous created with Curl_cookie_init().
1572 */
Curl_cookie_cleanup(struct CookieInfo *c)1573 void Curl_cookie_cleanup(struct CookieInfo *c)
1574 {
1575 if(c) {
1576 unsigned int i;
1577 for(i = 0; i < COOKIE_HASH_SIZE; i++)
1578 Curl_cookie_freelist(c->cookies[i]);
1579 free(c); /* free the base struct as well */
1580 }
1581 }
1582
1583 /*
1584 * get_netscape_format()
1585 *
1586 * Formats a string for Netscape output file, w/o a newline at the end.
1587 * Function returns a char * to a formatted line. The caller is responsible
1588 * for freeing the returned pointer.
1589 */
get_netscape_format(const struct Cookie *co)1590 static char *get_netscape_format(const struct Cookie *co)
1591 {
1592 return aprintf(
1593 "%s" /* httponly preamble */
1594 "%s%s\t" /* domain */
1595 "%s\t" /* tailmatch */
1596 "%s\t" /* path */
1597 "%s\t" /* secure */
1598 "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */
1599 "%s\t" /* name */
1600 "%s", /* value */
1601 co->httponly?"#HttpOnly_":"",
1602 /*
1603 * Make sure all domains are prefixed with a dot if they allow
1604 * tailmatching. This is Mozilla-style.
1605 */
1606 (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
1607 co->domain?co->domain:"unknown",
1608 co->tailmatch?"TRUE":"FALSE",
1609 co->path?co->path:"/",
1610 co->secure?"TRUE":"FALSE",
1611 co->expires,
1612 co->name,
1613 co->value?co->value:"");
1614 }
1615
1616 /*
1617 * cookie_output()
1618 *
1619 * Writes all internally known cookies to the specified file. Specify
1620 * "-" as file name to write to stdout.
1621 *
1622 * The function returns non-zero on write failure.
1623 */
cookie_output(struct Curl_easy *data, struct CookieInfo *c, const char *filename)1624 static CURLcode cookie_output(struct Curl_easy *data,
1625 struct CookieInfo *c, const char *filename)
1626 {
1627 struct Cookie *co;
1628 FILE *out = NULL;
1629 bool use_stdout = FALSE;
1630 char *tempstore = NULL;
1631 CURLcode error = CURLE_OK;
1632
1633 if(!c)
1634 /* no cookie engine alive */
1635 return CURLE_OK;
1636
1637 /* at first, remove expired cookies */
1638 remove_expired(c);
1639
1640 if(!strcmp("-", filename)) {
1641 /* use stdout */
1642 out = stdout;
1643 use_stdout = TRUE;
1644 }
1645 else {
1646 error = Curl_fopen(data, filename, &out, &tempstore);
1647 if(error)
1648 goto error;
1649 }
1650
1651 fputs("# Netscape HTTP Cookie File\n"
1652 "# https://curl.se/docs/http-cookies.html\n"
1653 "# This file was generated by libcurl! Edit at your own risk.\n\n",
1654 out);
1655
1656 if(c->numcookies) {
1657 unsigned int i;
1658 size_t nvalid = 0;
1659 struct Cookie **array;
1660
1661 array = calloc(1, sizeof(struct Cookie *) * c->numcookies);
1662 if(!array) {
1663 error = CURLE_OUT_OF_MEMORY;
1664 goto error;
1665 }
1666
1667 /* only sort the cookies with a domain property */
1668 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1669 for(co = c->cookies[i]; co; co = co->next) {
1670 if(!co->domain)
1671 continue;
1672 array[nvalid++] = co;
1673 }
1674 }
1675
1676 qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1677
1678 for(i = 0; i < nvalid; i++) {
1679 char *format_ptr = get_netscape_format(array[i]);
1680 if(!format_ptr) {
1681 free(array);
1682 error = CURLE_OUT_OF_MEMORY;
1683 goto error;
1684 }
1685 fprintf(out, "%s\n", format_ptr);
1686 free(format_ptr);
1687 }
1688
1689 free(array);
1690 }
1691
1692 if(!use_stdout) {
1693 fclose(out);
1694 out = NULL;
1695 if(tempstore && Curl_rename(tempstore, filename)) {
1696 unlink(tempstore);
1697 error = CURLE_WRITE_ERROR;
1698 goto error;
1699 }
1700 }
1701
1702 /*
1703 * If we reach here we have successfully written a cookie file so there is
1704 * no need to inspect the error, any error case should have jumped into the
1705 * error block below.
1706 */
1707 free(tempstore);
1708 return CURLE_OK;
1709
1710 error:
1711 if(out && !use_stdout)
1712 fclose(out);
1713 free(tempstore);
1714 return error;
1715 }
1716
cookie_list(struct Curl_easy *data)1717 static struct curl_slist *cookie_list(struct Curl_easy *data)
1718 {
1719 struct curl_slist *list = NULL;
1720 struct curl_slist *beg;
1721 struct Cookie *c;
1722 char *line;
1723 unsigned int i;
1724
1725 if(!data->cookies || (data->cookies->numcookies == 0))
1726 return NULL;
1727
1728 for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1729 for(c = data->cookies->cookies[i]; c; c = c->next) {
1730 if(!c->domain)
1731 continue;
1732 line = get_netscape_format(c);
1733 if(!line) {
1734 curl_slist_free_all(list);
1735 return NULL;
1736 }
1737 beg = Curl_slist_append_nodup(list, line);
1738 if(!beg) {
1739 free(line);
1740 curl_slist_free_all(list);
1741 return NULL;
1742 }
1743 list = beg;
1744 }
1745 }
1746
1747 return list;
1748 }
1749
Curl_cookie_list(struct Curl_easy *data)1750 struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1751 {
1752 struct curl_slist *list;
1753 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1754 list = cookie_list(data);
1755 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1756 return list;
1757 }
1758
Curl_flush_cookies(struct Curl_easy *data, bool cleanup)1759 void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1760 {
1761 CURLcode res;
1762
1763 if(data->set.str[STRING_COOKIEJAR]) {
1764 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1765
1766 /* if we have a destination file for all the cookies to get dumped to */
1767 res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);
1768 if(res)
1769 infof(data, "WARNING: failed to save cookies in %s: %s",
1770 data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
1771 }
1772 else {
1773 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1774 }
1775
1776 if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1777 Curl_cookie_cleanup(data->cookies);
1778 data->cookies = NULL;
1779 }
1780 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1781 }
1782
1783 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
1784