xref: /curl/lib/cookie.c (revision 0b4401a4)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 /***
26 
27 
28 RECEIVING COOKIE INFORMATION
29 ============================
30 
31 struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
32                     const char *file, struct CookieInfo *inc, bool newsession);
33 
34         Inits a cookie struct to store data in a local file. This is always
35         called before any cookies are set.
36 
37 struct Cookie *Curl_cookie_add(struct Curl_easy *data,
38                  struct CookieInfo *c, bool httpheader, bool noexpire,
39                  char *lineptr, const char *domain, const char *path,
40                  bool secure);
41 
42         The 'lineptr' parameter is a full "Set-cookie:" line as
43         received from a server.
44 
45         The function need to replace previously stored lines that this new
46         line supersedes.
47 
48         It may remove lines that are expired.
49 
50         It should return an indication of success/error.
51 
52 
53 SENDING COOKIE INFORMATION
54 ==========================
55 
56 struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie,
57                                     char *host, char *path, bool secure);
58 
59         For a given host and path, return a linked list of cookies that
60         the client should send to the server if used now. The secure
61         boolean informs the cookie if a secure connection is achieved or
62         not.
63 
64         It shall only return cookies that haven't expired.
65 
66 
67 Example set of cookies:
68 
69     Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure
70     Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
71     domain=.fidelity.com; path=/ftgw; secure
72     Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
73     domain=.fidelity.com; path=/; secure
74     Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
75     domain=.fidelity.com; path=/; secure
76     Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
77     domain=.fidelity.com; path=/; secure
78     Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
79     domain=.fidelity.com; path=/; secure
80     Set-cookie:
81     Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
82     13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
83 ****/
84 
85 
86 #include "curl_setup.h"
87 
88 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
89 
90 #include "urldata.h"
91 #include "cookie.h"
92 #include "psl.h"
93 #include "strtok.h"
94 #include "sendf.h"
95 #include "slist.h"
96 #include "share.h"
97 #include "strtoofft.h"
98 #include "strcase.h"
99 #include "curl_get_line.h"
100 #include "curl_memrchr.h"
101 #include "parsedate.h"
102 #include "rename.h"
103 #include "fopen.h"
104 #include "strdup.h"
105 
106 /* The last 3 #include files should be in this order */
107 #include "curl_printf.h"
108 #include "curl_memory.h"
109 #include "memdebug.h"
110 
111 static void strstore(char **str, const char *newstr, size_t len);
112 
freecookie(struct Cookie * co)113 static void freecookie(struct Cookie *co)
114 {
115   free(co->domain);
116   free(co->path);
117   free(co->spath);
118   free(co->name);
119   free(co->value);
120   free(co);
121 }
122 
cookie_tailmatch(const char * cookie_domain,size_t cookie_domain_len,const char * hostname)123 static bool cookie_tailmatch(const char *cookie_domain,
124                              size_t cookie_domain_len,
125                              const char *hostname)
126 {
127   size_t hostname_len = strlen(hostname);
128 
129   if(hostname_len < cookie_domain_len)
130     return FALSE;
131 
132   if(!strncasecompare(cookie_domain,
133                       hostname + hostname_len-cookie_domain_len,
134                       cookie_domain_len))
135     return FALSE;
136 
137   /*
138    * A lead char of cookie_domain is not '.'.
139    * RFC6265 4.1.2.3. The Domain Attribute says:
140    * For example, if the value of the Domain attribute is
141    * "example.com", the user agent will include the cookie in the Cookie
142    * header when making HTTP requests to example.com, www.example.com, and
143    * www.corp.example.com.
144    */
145   if(hostname_len == cookie_domain_len)
146     return TRUE;
147   if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
148     return TRUE;
149   return FALSE;
150 }
151 
152 /*
153  * matching cookie path and url path
154  * RFC6265 5.1.4 Paths and Path-Match
155  */
pathmatch(const char * cookie_path,const char * request_uri)156 static bool pathmatch(const char *cookie_path, const char *request_uri)
157 {
158   size_t cookie_path_len;
159   size_t uri_path_len;
160   char *uri_path = NULL;
161   char *pos;
162   bool ret = FALSE;
163 
164   /* cookie_path must not have last '/' separator. ex: /sample */
165   cookie_path_len = strlen(cookie_path);
166   if(1 == cookie_path_len) {
167     /* cookie_path must be '/' */
168     return TRUE;
169   }
170 
171   uri_path = strdup(request_uri);
172   if(!uri_path)
173     return FALSE;
174   pos = strchr(uri_path, '?');
175   if(pos)
176     *pos = 0x0;
177 
178   /* #-fragments are already cut off! */
179   if(0 == strlen(uri_path) || uri_path[0] != '/') {
180     strstore(&uri_path, "/", 1);
181     if(!uri_path)
182       return FALSE;
183   }
184 
185   /*
186    * here, RFC6265 5.1.4 says
187    *  4. Output the characters of the uri-path from the first character up
188    *     to, but not including, the right-most %x2F ("/").
189    *  but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site
190    *  without redirect.
191    *  Ignore this algorithm because /hoge is uri path for this case
192    *  (uri path is not /).
193    */
194 
195   uri_path_len = strlen(uri_path);
196 
197   if(uri_path_len < cookie_path_len) {
198     ret = FALSE;
199     goto pathmatched;
200   }
201 
202   /* not using checkprefix() because matching should be case-sensitive */
203   if(strncmp(cookie_path, uri_path, cookie_path_len)) {
204     ret = FALSE;
205     goto pathmatched;
206   }
207 
208   /* The cookie-path and the uri-path are identical. */
209   if(cookie_path_len == uri_path_len) {
210     ret = TRUE;
211     goto pathmatched;
212   }
213 
214   /* here, cookie_path_len < uri_path_len */
215   if(uri_path[cookie_path_len] == '/') {
216     ret = TRUE;
217     goto pathmatched;
218   }
219 
220   ret = FALSE;
221 
222 pathmatched:
223   free(uri_path);
224   return ret;
225 }
226 
227 /*
228  * Return the top-level domain, for optimal hashing.
229  */
get_top_domain(const char * const domain,size_t * outlen)230 static const char *get_top_domain(const char * const domain, size_t *outlen)
231 {
232   size_t len = 0;
233   const char *first = NULL, *last;
234 
235   if(domain) {
236     len = strlen(domain);
237     last = memrchr(domain, '.', len);
238     if(last) {
239       first = memrchr(domain, '.', (last - domain));
240       if(first)
241         len -= (++first - domain);
242     }
243   }
244 
245   if(outlen)
246     *outlen = len;
247 
248   return first? first: domain;
249 }
250 
251 /* Avoid C1001, an "internal error" with MSVC14 */
252 #if defined(_MSC_VER) && (_MSC_VER == 1900)
253 #pragma optimize("", off)
254 #endif
255 
256 /*
257  * A case-insensitive hash for the cookie domains.
258  */
cookie_hash_domain(const char * domain,const size_t len)259 static size_t cookie_hash_domain(const char *domain, const size_t len)
260 {
261   const char *end = domain + len;
262   size_t h = 5381;
263 
264   while(domain < end) {
265     h += h << 5;
266     h ^= Curl_raw_toupper(*domain++);
267   }
268 
269   return (h % COOKIE_HASH_SIZE);
270 }
271 
272 #if defined(_MSC_VER) && (_MSC_VER == 1900)
273 #pragma optimize("", on)
274 #endif
275 
276 /*
277  * Hash this domain.
278  */
cookiehash(const char * const domain)279 static size_t cookiehash(const char * const domain)
280 {
281   const char *top;
282   size_t len;
283 
284   if(!domain || Curl_host_is_ipnum(domain))
285     return 0;
286 
287   top = get_top_domain(domain, &len);
288   return cookie_hash_domain(top, len);
289 }
290 
291 /*
292  * cookie path sanitize
293  */
sanitize_cookie_path(const char * cookie_path)294 static char *sanitize_cookie_path(const char *cookie_path)
295 {
296   size_t len;
297   char *new_path = strdup(cookie_path);
298   if(!new_path)
299     return NULL;
300 
301   /* some stupid site sends path attribute with '"'. */
302   len = strlen(new_path);
303   if(new_path[0] == '\"') {
304     memmove(new_path, new_path + 1, len);
305     len--;
306   }
307   if(len && (new_path[len - 1] == '\"')) {
308     new_path[--len] = 0x0;
309   }
310 
311   /* RFC6265 5.2.4 The Path Attribute */
312   if(new_path[0] != '/') {
313     /* Let cookie-path be the default-path. */
314     strstore(&new_path, "/", 1);
315     return new_path;
316   }
317 
318   /* convert /hoge/ to /hoge */
319   if(len && new_path[len - 1] == '/') {
320     new_path[len - 1] = 0x0;
321   }
322 
323   return new_path;
324 }
325 
326 /*
327  * Load cookies from all given cookie files (CURLOPT_COOKIEFILE).
328  *
329  * NOTE: OOM or cookie parsing failures are ignored.
330  */
Curl_cookie_loadfiles(struct Curl_easy * data)331 void Curl_cookie_loadfiles(struct Curl_easy *data)
332 {
333   struct curl_slist *list = data->state.cookielist;
334   if(list) {
335     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
336     while(list) {
337       struct CookieInfo *newcookies =
338         Curl_cookie_init(data, list->data, data->cookies,
339                          data->set.cookiesession);
340       if(!newcookies)
341         /*
342          * Failure may be due to OOM or a bad cookie; both are ignored
343          * but only the first should be
344          */
345         infof(data, "ignoring failed cookie_init for %s", list->data);
346       else
347         data->cookies = newcookies;
348       list = list->next;
349     }
350     Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
351   }
352 }
353 
354 /*
355  * strstore
356  *
357  * A thin wrapper around strdup which ensures that any memory allocated at
358  * *str will be freed before the string allocated by strdup is stored there.
359  * The intended usecase is repeated assignments to the same variable during
360  * parsing in a last-wins scenario. The caller is responsible for checking
361  * for OOM errors.
362  */
strstore(char ** str,const char * newstr,size_t len)363 static void strstore(char **str, const char *newstr, size_t len)
364 {
365   DEBUGASSERT(newstr);
366   DEBUGASSERT(str);
367   free(*str);
368   *str = Curl_memdup0(newstr, len);
369 }
370 
371 /*
372  * remove_expired
373  *
374  * Remove expired cookies from the hash by inspecting the expires timestamp on
375  * each cookie in the hash, freeing and deleting any where the timestamp is in
376  * the past.  If the cookiejar has recorded the next timestamp at which one or
377  * more cookies expire, then processing will exit early in case this timestamp
378  * is in the future.
379  */
remove_expired(struct CookieInfo * cookies)380 static void remove_expired(struct CookieInfo *cookies)
381 {
382   struct Cookie *co, *nx;
383   curl_off_t now = (curl_off_t)time(NULL);
384   unsigned int i;
385 
386   /*
387    * If the earliest expiration timestamp in the jar is in the future we can
388    * skip scanning the whole jar and instead exit early as there won't be any
389    * cookies to evict.  If we need to evict however, reset the next_expiration
390    * counter in order to track the next one. In case the recorded first
391    * expiration is the max offset, then perform the safe fallback of checking
392    * all cookies.
393    */
394   if(now < cookies->next_expiration &&
395       cookies->next_expiration != CURL_OFF_T_MAX)
396     return;
397   else
398     cookies->next_expiration = CURL_OFF_T_MAX;
399 
400   for(i = 0; i < COOKIE_HASH_SIZE; i++) {
401     struct Cookie *pv = NULL;
402     co = cookies->cookies[i];
403     while(co) {
404       nx = co->next;
405       if(co->expires && co->expires < now) {
406         if(!pv) {
407           cookies->cookies[i] = co->next;
408         }
409         else {
410           pv->next = co->next;
411         }
412         cookies->numcookies--;
413         freecookie(co);
414       }
415       else {
416         /*
417          * If this cookie has an expiration timestamp earlier than what we've
418          * seen so far then record it for the next round of expirations.
419          */
420         if(co->expires && co->expires < cookies->next_expiration)
421           cookies->next_expiration = co->expires;
422         pv = co;
423       }
424       co = nx;
425     }
426   }
427 }
428 
429 #ifndef USE_LIBPSL
430 /* Make sure domain contains a dot or is localhost. */
bad_domain(const char * domain,size_t len)431 static 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 */
invalid_octets(const char * p)458 static 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  */
482 struct Cookie *
Curl_cookie_add(struct Curl_easy * data,struct CookieInfo * c,bool httpheader,bool noexpire,const char * lineptr,const char * domain,const char * path,bool secure)483 Curl_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     fields = 0;
890     for(ptr = firstptr; ptr && !badcookie;
891         ptr = strtok_r(NULL, "\t", &tok_buf), fields++) {
892       switch(fields) {
893       case 0:
894         if(ptr[0]=='.') /* skip preceding dots */
895           ptr++;
896         co->domain = strdup(ptr);
897         if(!co->domain)
898           badcookie = TRUE;
899         break;
900       case 1:
901         /*
902          * flag: A TRUE/FALSE value indicating if all machines within a given
903          * domain can access the variable. Set TRUE when the cookie says
904          * .domain.com and to false when the domain is complete www.domain.com
905          */
906         co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
907         break;
908       case 2:
909         /* The file format allows the path field to remain not filled in */
910         if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
911           /* only if the path doesn't look like a boolean option! */
912           co->path = strdup(ptr);
913           if(!co->path)
914             badcookie = TRUE;
915           else {
916             co->spath = sanitize_cookie_path(co->path);
917             if(!co->spath) {
918               badcookie = TRUE; /* out of memory bad */
919             }
920           }
921           break;
922         }
923         /* this doesn't look like a path, make one up! */
924         co->path = strdup("/");
925         if(!co->path)
926           badcookie = TRUE;
927         co->spath = strdup("/");
928         if(!co->spath)
929           badcookie = TRUE;
930         fields++; /* add a field and fall down to secure */
931         FALLTHROUGH();
932       case 3:
933         co->secure = FALSE;
934         if(strcasecompare(ptr, "TRUE")) {
935           if(secure || c->running)
936             co->secure = TRUE;
937           else
938             badcookie = TRUE;
939         }
940         break;
941       case 4:
942         if(curlx_strtoofft(ptr, NULL, 10, &co->expires))
943           badcookie = TRUE;
944         break;
945       case 5:
946         co->name = strdup(ptr);
947         if(!co->name)
948           badcookie = TRUE;
949         else {
950           /* For Netscape file format cookies we check prefix on the name */
951           if(strncasecompare("__Secure-", co->name, 9))
952             co->prefix |= COOKIE_PREFIX__SECURE;
953           else if(strncasecompare("__Host-", co->name, 7))
954             co->prefix |= COOKIE_PREFIX__HOST;
955         }
956         break;
957       case 6:
958         co->value = strdup(ptr);
959         if(!co->value)
960           badcookie = TRUE;
961         break;
962       }
963     }
964     if(6 == fields) {
965       /* we got a cookie with blank contents, fix it */
966       co->value = strdup("");
967       if(!co->value)
968         badcookie = TRUE;
969       else
970         fields++;
971     }
972 
973     if(!badcookie && (7 != fields))
974       /* we did not find the sufficient number of fields */
975       badcookie = TRUE;
976 
977     if(badcookie) {
978       freecookie(co);
979       return NULL;
980     }
981 
982   }
983 
984   if(co->prefix & COOKIE_PREFIX__SECURE) {
985     /* The __Secure- prefix only requires that the cookie be set secure */
986     if(!co->secure) {
987       freecookie(co);
988       return NULL;
989     }
990   }
991   if(co->prefix & COOKIE_PREFIX__HOST) {
992     /*
993      * The __Host- prefix requires the cookie to be secure, have a "/" path
994      * and not have a domain set.
995      */
996     if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
997       ;
998     else {
999       freecookie(co);
1000       return NULL;
1001     }
1002   }
1003 
1004   if(!c->running &&    /* read from a file */
1005      c->newsession &&  /* clean session cookies */
1006      !co->expires) {   /* this is a session cookie since it doesn't expire! */
1007     freecookie(co);
1008     return NULL;
1009   }
1010 
1011   co->livecookie = c->running;
1012   co->creationtime = ++c->lastct;
1013 
1014   /*
1015    * Now we have parsed the incoming line, we must now check if this supersedes
1016    * an already existing cookie, which it may if the previous have the same
1017    * domain and path as this.
1018    */
1019 
1020   /* at first, remove expired cookies */
1021   if(!noexpire)
1022     remove_expired(c);
1023 
1024 #ifdef USE_LIBPSL
1025   /*
1026    * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
1027    * must also check that the data handle isn't NULL since the psl code will
1028    * dereference it.
1029    */
1030   if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
1031     bool acceptable = FALSE;
1032     char lcase[256];
1033     char lcookie[256];
1034     size_t dlen = strlen(domain);
1035     size_t clen = strlen(co->domain);
1036     if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {
1037       const psl_ctx_t *psl = Curl_psl_use(data);
1038       if(psl) {
1039         /* the PSL check requires lowercase domain name and pattern */
1040         Curl_strntolower(lcase, domain, dlen + 1);
1041         Curl_strntolower(lcookie, co->domain, clen + 1);
1042         acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);
1043         Curl_psl_release(data);
1044       }
1045       else
1046         infof(data, "libpsl problem, rejecting cookie for satety");
1047     }
1048 
1049     if(!acceptable) {
1050       infof(data, "cookie '%s' dropped, domain '%s' must not "
1051                   "set cookies for '%s'", co->name, domain, co->domain);
1052       freecookie(co);
1053       return NULL;
1054     }
1055   }
1056 #endif
1057 
1058   /* A non-secure cookie may not overlay an existing secure cookie. */
1059   myhash = cookiehash(co->domain);
1060   clist = c->cookies[myhash];
1061   while(clist) {
1062     if(strcasecompare(clist->name, co->name)) {
1063       /* the names are identical */
1064       bool matching_domains = FALSE;
1065 
1066       if(clist->domain && co->domain) {
1067         if(strcasecompare(clist->domain, co->domain))
1068           /* The domains are identical */
1069           matching_domains = TRUE;
1070       }
1071       else if(!clist->domain && !co->domain)
1072         matching_domains = TRUE;
1073 
1074       if(matching_domains && /* the domains were identical */
1075          clist->spath && co->spath && /* both have paths */
1076          clist->secure && !co->secure && !secure) {
1077         size_t cllen;
1078         const char *sep;
1079 
1080         /*
1081          * A non-secure cookie may not overlay an existing secure cookie.
1082          * For an existing cookie "a" with path "/login", refuse a new
1083          * cookie "a" with for example path "/login/en", while the path
1084          * "/loginhelper" is ok.
1085          */
1086 
1087         sep = strchr(clist->spath + 1, '/');
1088 
1089         if(sep)
1090           cllen = sep - clist->spath;
1091         else
1092           cllen = strlen(clist->spath);
1093 
1094         if(strncasecompare(clist->spath, co->spath, cllen)) {
1095           infof(data, "cookie '%s' for domain '%s' dropped, would "
1096                 "overlay an existing cookie", co->name, co->domain);
1097           freecookie(co);
1098           return NULL;
1099         }
1100       }
1101     }
1102 
1103     if(!replace_co && strcasecompare(clist->name, co->name)) {
1104       /* the names are identical */
1105 
1106       if(clist->domain && co->domain) {
1107         if(strcasecompare(clist->domain, co->domain) &&
1108           (clist->tailmatch == co->tailmatch))
1109           /* The domains are identical */
1110           replace_old = TRUE;
1111       }
1112       else if(!clist->domain && !co->domain)
1113         replace_old = TRUE;
1114 
1115       if(replace_old) {
1116         /* the domains were identical */
1117 
1118         if(clist->spath && co->spath &&
1119            !strcasecompare(clist->spath, co->spath))
1120           replace_old = FALSE;
1121         else if(!clist->spath != !co->spath)
1122           replace_old = FALSE;
1123       }
1124 
1125       if(replace_old && !co->livecookie && clist->livecookie) {
1126         /*
1127          * Both cookies matched fine, except that the already present cookie is
1128          * "live", which means it was set from a header, while the new one was
1129          * read from a file and thus isn't "live". "live" cookies are preferred
1130          * so the new cookie is freed.
1131          */
1132         freecookie(co);
1133         return NULL;
1134       }
1135       if(replace_old) {
1136         replace_co = co;
1137         replace_clist = clist;
1138       }
1139     }
1140     lastc = clist;
1141     clist = clist->next;
1142   }
1143   if(replace_co) {
1144     co = replace_co;
1145     clist = replace_clist;
1146     co->next = clist->next; /* get the next-pointer first */
1147 
1148     /* when replacing, creationtime is kept from old */
1149     co->creationtime = clist->creationtime;
1150 
1151     /* then free all the old pointers */
1152     free(clist->name);
1153     free(clist->value);
1154     free(clist->domain);
1155     free(clist->path);
1156     free(clist->spath);
1157 
1158     *clist = *co;  /* then store all the new data */
1159 
1160     free(co);   /* free the newly allocated memory */
1161     co = clist;
1162   }
1163 
1164   if(c->running)
1165     /* Only show this when NOT reading the cookies from a file */
1166     infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1167           "expire %" CURL_FORMAT_CURL_OFF_T,
1168           replace_old?"Replaced":"Added", co->name, co->value,
1169           co->domain, co->path, co->expires);
1170 
1171   if(!replace_old) {
1172     /* then make the last item point on this new one */
1173     if(lastc)
1174       lastc->next = co;
1175     else
1176       c->cookies[myhash] = co;
1177     c->numcookies++; /* one more cookie in the jar */
1178   }
1179 
1180   /*
1181    * Now that we've added a new cookie to the jar, update the expiration
1182    * tracker in case it is the next one to expire.
1183    */
1184   if(co->expires && (co->expires < c->next_expiration))
1185     c->next_expiration = co->expires;
1186 
1187   return co;
1188 }
1189 
1190 
1191 /*
1192  * Curl_cookie_init()
1193  *
1194  * Inits a cookie struct to read data from a local file. This is always
1195  * called before any cookies are set. File may be NULL in which case only the
1196  * struct is initialized. Is file is "-" then STDIN is read.
1197  *
1198  * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1199  *
1200  * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1201  * will be ignored.
1202  *
1203  * Returns NULL on out of memory. Invalid cookies are ignored.
1204  */
Curl_cookie_init(struct Curl_easy * data,const char * file,struct CookieInfo * inc,bool newsession)1205 struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
1206                                     const char *file,
1207                                     struct CookieInfo *inc,
1208                                     bool newsession)
1209 {
1210   struct CookieInfo *c;
1211   FILE *handle = NULL;
1212 
1213   if(!inc) {
1214     /* we didn't get a struct, create one */
1215     c = calloc(1, sizeof(struct CookieInfo));
1216     if(!c)
1217       return NULL; /* failed to get memory */
1218     /*
1219      * Initialize the next_expiration time to signal that we don't have enough
1220      * information yet.
1221      */
1222     c->next_expiration = CURL_OFF_T_MAX;
1223   }
1224   else {
1225     /* we got an already existing one, use that */
1226     c = inc;
1227   }
1228   c->newsession = newsession; /* new session? */
1229 
1230   if(data) {
1231     FILE *fp = NULL;
1232     if(file && *file) {
1233       if(!strcmp(file, "-"))
1234         fp = stdin;
1235       else {
1236         fp = fopen(file, "rb");
1237         if(!fp)
1238           infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1239         else
1240           handle = fp;
1241       }
1242     }
1243 
1244     c->running = FALSE; /* this is not running, this is init */
1245     if(fp) {
1246       struct dynbuf buf;
1247       Curl_dyn_init(&buf, MAX_COOKIE_LINE);
1248       while(Curl_get_line(&buf, fp)) {
1249         char *lineptr = Curl_dyn_ptr(&buf);
1250         bool headerline = FALSE;
1251         if(checkprefix("Set-Cookie:", lineptr)) {
1252           /* This is a cookie line, get it! */
1253           lineptr += 11;
1254           headerline = TRUE;
1255           while(*lineptr && ISBLANK(*lineptr))
1256             lineptr++;
1257         }
1258 
1259         Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE);
1260       }
1261       Curl_dyn_free(&buf); /* free the line buffer */
1262 
1263       /*
1264        * Remove expired cookies from the hash. We must make sure to run this
1265        * after reading the file, and not on every cookie.
1266        */
1267       remove_expired(c);
1268 
1269       if(handle)
1270         fclose(handle);
1271     }
1272     data->state.cookie_engine = TRUE;
1273   }
1274   c->running = TRUE;          /* now, we're running */
1275 
1276   return c;
1277 }
1278 
1279 /*
1280  * cookie_sort
1281  *
1282  * Helper function to sort cookies such that the longest path gets before the
1283  * shorter path. Path, domain and name lengths are considered in that order,
1284  * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1285  * be unique per cookie, so we know we will get an ordering at that point.
1286  */
cookie_sort(const void * p1,const void * p2)1287 static int cookie_sort(const void *p1, const void *p2)
1288 {
1289   struct Cookie *c1 = *(struct Cookie **)p1;
1290   struct Cookie *c2 = *(struct Cookie **)p2;
1291   size_t l1, l2;
1292 
1293   /* 1 - compare cookie path lengths */
1294   l1 = c1->path ? strlen(c1->path) : 0;
1295   l2 = c2->path ? strlen(c2->path) : 0;
1296 
1297   if(l1 != l2)
1298     return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
1299 
1300   /* 2 - compare cookie domain lengths */
1301   l1 = c1->domain ? strlen(c1->domain) : 0;
1302   l2 = c2->domain ? strlen(c2->domain) : 0;
1303 
1304   if(l1 != l2)
1305     return (l2 > l1) ? 1 : -1 ;  /* avoid size_t <=> int conversions */
1306 
1307   /* 3 - compare cookie name lengths */
1308   l1 = c1->name ? strlen(c1->name) : 0;
1309   l2 = c2->name ? strlen(c2->name) : 0;
1310 
1311   if(l1 != l2)
1312     return (l2 > l1) ? 1 : -1;
1313 
1314   /* 4 - compare cookie creation time */
1315   return (c2->creationtime > c1->creationtime) ? 1 : -1;
1316 }
1317 
1318 /*
1319  * cookie_sort_ct
1320  *
1321  * Helper function to sort cookies according to creation time.
1322  */
cookie_sort_ct(const void * p1,const void * p2)1323 static int cookie_sort_ct(const void *p1, const void *p2)
1324 {
1325   struct Cookie *c1 = *(struct Cookie **)p1;
1326   struct Cookie *c2 = *(struct Cookie **)p2;
1327 
1328   return (c2->creationtime > c1->creationtime) ? 1 : -1;
1329 }
1330 
1331 #define CLONE(field)                     \
1332   do {                                   \
1333     if(src->field) {                     \
1334       d->field = strdup(src->field);     \
1335       if(!d->field)                      \
1336         goto fail;                       \
1337     }                                    \
1338   } while(0)
1339 
dup_cookie(struct Cookie * src)1340 static struct Cookie *dup_cookie(struct Cookie *src)
1341 {
1342   struct Cookie *d = calloc(1, sizeof(struct Cookie));
1343   if(d) {
1344     CLONE(domain);
1345     CLONE(path);
1346     CLONE(spath);
1347     CLONE(name);
1348     CLONE(value);
1349     d->expires = src->expires;
1350     d->tailmatch = src->tailmatch;
1351     d->secure = src->secure;
1352     d->livecookie = src->livecookie;
1353     d->httponly = src->httponly;
1354     d->creationtime = src->creationtime;
1355   }
1356   return d;
1357 
1358 fail:
1359   freecookie(d);
1360   return NULL;
1361 }
1362 
1363 /*
1364  * Curl_cookie_getlist
1365  *
1366  * For a given host and path, return a linked list of cookies that the client
1367  * should send to the server if used now. The secure boolean informs the cookie
1368  * if a secure connection is achieved or not.
1369  *
1370  * It shall only return cookies that haven't expired.
1371  */
Curl_cookie_getlist(struct Curl_easy * data,struct CookieInfo * c,const char * host,const char * path,bool secure)1372 struct Cookie *Curl_cookie_getlist(struct Curl_easy *data,
1373                                    struct CookieInfo *c,
1374                                    const char *host, const char *path,
1375                                    bool secure)
1376 {
1377   struct Cookie *newco;
1378   struct Cookie *co;
1379   struct Cookie *mainco = NULL;
1380   size_t matches = 0;
1381   bool is_ip;
1382   const size_t myhash = cookiehash(host);
1383 
1384   if(!c || !c->cookies[myhash])
1385     return NULL; /* no cookie struct or no cookies in the struct */
1386 
1387   /* at first, remove expired cookies */
1388   remove_expired(c);
1389 
1390   /* check if host is an IP(v4|v6) address */
1391   is_ip = Curl_host_is_ipnum(host);
1392 
1393   co = c->cookies[myhash];
1394 
1395   while(co) {
1396     /* if the cookie requires we're secure we must only continue if we are! */
1397     if(co->secure?secure:TRUE) {
1398 
1399       /* now check if the domain is correct */
1400       if(!co->domain ||
1401          (co->tailmatch && !is_ip &&
1402           cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1403          ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
1404         /*
1405          * the right part of the host matches the domain stuff in the
1406          * cookie data
1407          */
1408 
1409         /*
1410          * now check the left part of the path with the cookies path
1411          * requirement
1412          */
1413         if(!co->spath || pathmatch(co->spath, path) ) {
1414 
1415           /*
1416            * and now, we know this is a match and we should create an
1417            * entry for the return-linked-list
1418            */
1419 
1420           newco = dup_cookie(co);
1421           if(newco) {
1422             /* then modify our next */
1423             newco->next = mainco;
1424 
1425             /* point the main to us */
1426             mainco = newco;
1427 
1428             matches++;
1429             if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1430               infof(data, "Included max number of cookies (%zu) in request!",
1431                     matches);
1432               break;
1433             }
1434           }
1435           else
1436             goto fail;
1437         }
1438       }
1439     }
1440     co = co->next;
1441   }
1442 
1443   if(matches) {
1444     /*
1445      * Now we need to make sure that if there is a name appearing more than
1446      * once, the longest specified path version comes first. To make this
1447      * the swiftest way, we just sort them all based on path length.
1448      */
1449     struct Cookie **array;
1450     size_t i;
1451 
1452     /* alloc an array and store all cookie pointers */
1453     array = malloc(sizeof(struct Cookie *) * matches);
1454     if(!array)
1455       goto fail;
1456 
1457     co = mainco;
1458 
1459     for(i = 0; co; co = co->next)
1460       array[i++] = co;
1461 
1462     /* now sort the cookie pointers in path length order */
1463     qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1464 
1465     /* remake the linked list order according to the new order */
1466 
1467     mainco = array[0]; /* start here */
1468     for(i = 0; i<matches-1; i++)
1469       array[i]->next = array[i + 1];
1470     array[matches-1]->next = NULL; /* terminate the list */
1471 
1472     free(array); /* remove the temporary data again */
1473   }
1474 
1475   return mainco; /* return the new list */
1476 
1477 fail:
1478   /* failure, clear up the allocated chain and return NULL */
1479   Curl_cookie_freelist(mainco);
1480   return NULL;
1481 }
1482 
1483 /*
1484  * Curl_cookie_clearall
1485  *
1486  * Clear all existing cookies and reset the counter.
1487  */
Curl_cookie_clearall(struct CookieInfo * cookies)1488 void Curl_cookie_clearall(struct CookieInfo *cookies)
1489 {
1490   if(cookies) {
1491     unsigned int i;
1492     for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1493       Curl_cookie_freelist(cookies->cookies[i]);
1494       cookies->cookies[i] = NULL;
1495     }
1496     cookies->numcookies = 0;
1497   }
1498 }
1499 
1500 /*
1501  * Curl_cookie_freelist
1502  *
1503  * Free a list of cookies previously returned by Curl_cookie_getlist();
1504  */
Curl_cookie_freelist(struct Cookie * co)1505 void Curl_cookie_freelist(struct Cookie *co)
1506 {
1507   struct Cookie *next;
1508   while(co) {
1509     next = co->next;
1510     freecookie(co);
1511     co = next;
1512   }
1513 }
1514 
1515 /*
1516  * Curl_cookie_clearsess
1517  *
1518  * Free all session cookies in the cookies list.
1519  */
Curl_cookie_clearsess(struct CookieInfo * cookies)1520 void Curl_cookie_clearsess(struct CookieInfo *cookies)
1521 {
1522   struct Cookie *first, *curr, *next, *prev = NULL;
1523   unsigned int i;
1524 
1525   if(!cookies)
1526     return;
1527 
1528   for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1529     if(!cookies->cookies[i])
1530       continue;
1531 
1532     first = curr = prev = cookies->cookies[i];
1533 
1534     for(; curr; curr = next) {
1535       next = curr->next;
1536       if(!curr->expires) {
1537         if(first == curr)
1538           first = next;
1539 
1540         if(prev == curr)
1541           prev = next;
1542         else
1543           prev->next = next;
1544 
1545         freecookie(curr);
1546         cookies->numcookies--;
1547       }
1548       else
1549         prev = curr;
1550     }
1551 
1552     cookies->cookies[i] = first;
1553   }
1554 }
1555 
1556 /*
1557  * Curl_cookie_cleanup()
1558  *
1559  * Free a "cookie object" previous created with Curl_cookie_init().
1560  */
Curl_cookie_cleanup(struct CookieInfo * c)1561 void Curl_cookie_cleanup(struct CookieInfo *c)
1562 {
1563   if(c) {
1564     unsigned int i;
1565     for(i = 0; i < COOKIE_HASH_SIZE; i++)
1566       Curl_cookie_freelist(c->cookies[i]);
1567     free(c); /* free the base struct as well */
1568   }
1569 }
1570 
1571 /*
1572  * get_netscape_format()
1573  *
1574  * Formats a string for Netscape output file, w/o a newline at the end.
1575  * Function returns a char * to a formatted line. The caller is responsible
1576  * for freeing the returned pointer.
1577  */
get_netscape_format(const struct Cookie * co)1578 static char *get_netscape_format(const struct Cookie *co)
1579 {
1580   return aprintf(
1581     "%s"     /* httponly preamble */
1582     "%s%s\t" /* domain */
1583     "%s\t"   /* tailmatch */
1584     "%s\t"   /* path */
1585     "%s\t"   /* secure */
1586     "%" CURL_FORMAT_CURL_OFF_T "\t"   /* expires */
1587     "%s\t"   /* name */
1588     "%s",    /* value */
1589     co->httponly?"#HttpOnly_":"",
1590     /*
1591      * Make sure all domains are prefixed with a dot if they allow
1592      * tailmatching. This is Mozilla-style.
1593      */
1594     (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
1595     co->domain?co->domain:"unknown",
1596     co->tailmatch?"TRUE":"FALSE",
1597     co->path?co->path:"/",
1598     co->secure?"TRUE":"FALSE",
1599     co->expires,
1600     co->name,
1601     co->value?co->value:"");
1602 }
1603 
1604 /*
1605  * cookie_output()
1606  *
1607  * Writes all internally known cookies to the specified file. Specify
1608  * "-" as file name to write to stdout.
1609  *
1610  * The function returns non-zero on write failure.
1611  */
cookie_output(struct Curl_easy * data,struct CookieInfo * c,const char * filename)1612 static CURLcode cookie_output(struct Curl_easy *data,
1613                               struct CookieInfo *c, const char *filename)
1614 {
1615   struct Cookie *co;
1616   FILE *out = NULL;
1617   bool use_stdout = FALSE;
1618   char *tempstore = NULL;
1619   CURLcode error = CURLE_OK;
1620 
1621   if(!c)
1622     /* no cookie engine alive */
1623     return CURLE_OK;
1624 
1625   /* at first, remove expired cookies */
1626   remove_expired(c);
1627 
1628   if(!strcmp("-", filename)) {
1629     /* use stdout */
1630     out = stdout;
1631     use_stdout = TRUE;
1632   }
1633   else {
1634     error = Curl_fopen(data, filename, &out, &tempstore);
1635     if(error)
1636       goto error;
1637   }
1638 
1639   fputs("# Netscape HTTP Cookie File\n"
1640         "# https://curl.se/docs/http-cookies.html\n"
1641         "# This file was generated by libcurl! Edit at your own risk.\n\n",
1642         out);
1643 
1644   if(c->numcookies) {
1645     unsigned int i;
1646     size_t nvalid = 0;
1647     struct Cookie **array;
1648 
1649     array = calloc(1, sizeof(struct Cookie *) * c->numcookies);
1650     if(!array) {
1651       error = CURLE_OUT_OF_MEMORY;
1652       goto error;
1653     }
1654 
1655     /* only sort the cookies with a domain property */
1656     for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1657       for(co = c->cookies[i]; co; co = co->next) {
1658         if(!co->domain)
1659           continue;
1660         array[nvalid++] = co;
1661       }
1662     }
1663 
1664     qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1665 
1666     for(i = 0; i < nvalid; i++) {
1667       char *format_ptr = get_netscape_format(array[i]);
1668       if(!format_ptr) {
1669         free(array);
1670         error = CURLE_OUT_OF_MEMORY;
1671         goto error;
1672       }
1673       fprintf(out, "%s\n", format_ptr);
1674       free(format_ptr);
1675     }
1676 
1677     free(array);
1678   }
1679 
1680   if(!use_stdout) {
1681     fclose(out);
1682     out = NULL;
1683     if(tempstore && Curl_rename(tempstore, filename)) {
1684       unlink(tempstore);
1685       error = CURLE_WRITE_ERROR;
1686       goto error;
1687     }
1688   }
1689 
1690   /*
1691    * If we reach here we have successfully written a cookie file so there is
1692    * no need to inspect the error, any error case should have jumped into the
1693    * error block below.
1694    */
1695   free(tempstore);
1696   return CURLE_OK;
1697 
1698 error:
1699   if(out && !use_stdout)
1700     fclose(out);
1701   free(tempstore);
1702   return error;
1703 }
1704 
cookie_list(struct Curl_easy * data)1705 static struct curl_slist *cookie_list(struct Curl_easy *data)
1706 {
1707   struct curl_slist *list = NULL;
1708   struct curl_slist *beg;
1709   struct Cookie *c;
1710   char *line;
1711   unsigned int i;
1712 
1713   if(!data->cookies || (data->cookies->numcookies == 0))
1714     return NULL;
1715 
1716   for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1717     for(c = data->cookies->cookies[i]; c; c = c->next) {
1718       if(!c->domain)
1719         continue;
1720       line = get_netscape_format(c);
1721       if(!line) {
1722         curl_slist_free_all(list);
1723         return NULL;
1724       }
1725       beg = Curl_slist_append_nodup(list, line);
1726       if(!beg) {
1727         free(line);
1728         curl_slist_free_all(list);
1729         return NULL;
1730       }
1731       list = beg;
1732     }
1733   }
1734 
1735   return list;
1736 }
1737 
Curl_cookie_list(struct Curl_easy * data)1738 struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1739 {
1740   struct curl_slist *list;
1741   Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1742   list = cookie_list(data);
1743   Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1744   return list;
1745 }
1746 
Curl_flush_cookies(struct Curl_easy * data,bool cleanup)1747 void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1748 {
1749   CURLcode res;
1750 
1751   if(data->set.str[STRING_COOKIEJAR]) {
1752     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1753 
1754     /* if we have a destination file for all the cookies to get dumped to */
1755     res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);
1756     if(res)
1757       infof(data, "WARNING: failed to save cookies in %s: %s",
1758             data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
1759   }
1760   else {
1761     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1762   }
1763 
1764   if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1765     Curl_cookie_cleanup(data->cookies);
1766     data->cookies = NULL;
1767   }
1768   Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1769 }
1770 
1771 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
1772