VirtualBox

source: vbox/trunk/src/libs/curl-8.3.0/lib/cookie.c@ 101259

Last change on this file since 101259 was 101127, checked in by vboxsync, 19 months ago

curl-8.3.0: Applied and adjusted our curl changes to 8.0.1. bugref:10526

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette