Coverage Report

Created: 2025-08-26 07:08

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