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