VirtualBox

source: vbox/trunk/src/libs/curl-8.7.1/lib/cookie.c@ 107044

Last change on this file since 107044 was 104083, checked in by vboxsync, 11 months ago

curl-8.7.1: Applied and adjusted our curl changes to 8.4.0. bugref:10639

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

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