Coverage Report

Created: 2025-06-09 08:44

/src/gdal/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(!strncasecompare(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(1 == cookie_path_len) {
171
    /* cookie_path must be '/' */
172
0
    return TRUE;
173
0
  }
174
175
  /* #-fragments are already cut off! */
176
0
  if(0 == strlen(uri_path) || 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 */
300
  /* convert /hoge/ to /hoge */
301
0
  if(len && 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
268k
{
314
268k
  struct curl_slist *list = data->state.cookielist;
315
268k
  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
268k
}
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) && strncasecompare(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 = !!strncasecompare(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(strncasecompare(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(strncasecompare("__Secure-", co->name, 9))
863
0
          co->prefix_secure = TRUE;
864
0
        else if(strncasecompare("__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(6 == fields) {
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(7 != fields)
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 satety");
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(strcasecompare(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;
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
        sep = strchr(clist->spath + 1, '/');
978
979
0
        if(sep)
980
0
          cllen = sep - clist->spath;
981
0
        else
982
0
          cllen = strlen(clist->spath);
983
984
0
        if(strncasecompare(clist->spath, co->spath, cllen)) {
985
0
          infof(data, "cookie '%s' for domain '%s' dropped, would "
986
0
                "overlay an existing cookie", co->name, co->domain);
987
0
          return CERR_BAD_SECURE;
988
0
        }
989
0
      }
990
0
    }
991
992
0
    if(!replace_n && !strcmp(clist->name, co->name)) {
993
      /* the names are identical */
994
995
0
      if(clist->domain && co->domain) {
996
0
        if(strcasecompare(clist->domain, co->domain) &&
997
0
          (clist->tailmatch == co->tailmatch))
998
          /* The domains are identical */
999
0
          replace_old = TRUE;
1000
0
      }
1001
0
      else if(!clist->domain && !co->domain)
1002
0
        replace_old = TRUE;
1003
1004
0
      if(replace_old) {
1005
        /* the domains were identical */
1006
1007
0
        if(clist->spath && co->spath &&
1008
0
           !strcasecompare(clist->spath, co->spath))
1009
0
          replace_old = FALSE;
1010
0
        else if(!clist->spath != !co->spath)
1011
0
          replace_old = FALSE;
1012
0
      }
1013
1014
0
      if(replace_old && !co->livecookie && clist->livecookie) {
1015
        /*
1016
         * Both cookies matched fine, except that the already present cookie
1017
         * is "live", which means it was set from a header, while the new one
1018
         * was read from a file and thus is not "live". "live" cookies are
1019
         * preferred so the new cookie is freed.
1020
         */
1021
0
        return CERR_LIVE_WINS;
1022
0
      }
1023
0
      if(replace_old)
1024
0
        replace_n = n;
1025
0
    }
1026
0
  }
1027
0
  if(replace_n) {
1028
0
    struct Cookie *repl = Curl_node_elem(replace_n);
1029
1030
    /* when replacing, creationtime is kept from old */
1031
0
    co->creationtime = repl->creationtime;
1032
1033
    /* unlink the old */
1034
0
    Curl_node_remove(replace_n);
1035
1036
    /* free the old cookie */
1037
0
    freecookie(repl);
1038
0
  }
1039
0
  *replacep = replace_old;
1040
0
  return CERR_OK;
1041
0
}
1042
1043
/*
1044
 * Curl_cookie_add
1045
 *
1046
 * Add a single cookie line to the cookie keeping object. Be aware that
1047
 * sometimes we get an IP-only hostname, and that might also be a numerical
1048
 * IPv6 address.
1049
 *
1050
 * Returns NULL on out of memory or invalid cookie. This is suboptimal,
1051
 * as they should be treated separately.
1052
 */
1053
struct Cookie *
1054
Curl_cookie_add(struct Curl_easy *data,
1055
                struct CookieInfo *ci,
1056
                bool httpheader, /* TRUE if HTTP header-style line */
1057
                bool noexpire, /* if TRUE, skip remove_expired() */
1058
                const char *lineptr,   /* first character of the line */
1059
                const char *domain, /* default domain */
1060
                const char *path,   /* full path used when this cookie is set,
1061
                                       used to get default path for the cookie
1062
                                       unless set */
1063
                bool secure)  /* TRUE if connection is over secure origin */
1064
0
{
1065
0
  struct Cookie *co;
1066
0
  size_t myhash;
1067
0
  int rc;
1068
0
  bool replaces = FALSE;
1069
1070
0
  DEBUGASSERT(data);
1071
0
  DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
1072
0
  if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
1073
0
    return NULL;
1074
1075
  /* First, alloc and init a new struct for it */
1076
0
  co = calloc(1, sizeof(struct Cookie));
1077
0
  if(!co)
1078
0
    return NULL; /* bail out if we are this low on memory */
1079
1080
0
  if(httpheader)
1081
0
    rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure);
1082
0
  else
1083
0
    rc = parse_netscape(co, ci, lineptr, secure);
1084
1085
0
  if(rc)
1086
0
    goto fail;
1087
1088
0
  if(co->prefix_secure && !co->secure)
1089
    /* The __Secure- prefix only requires that the cookie be set secure */
1090
0
    goto fail;
1091
1092
0
  if(co->prefix_host) {
1093
    /*
1094
     * The __Host- prefix requires the cookie to be secure, have a "/" path
1095
     * and not have a domain set.
1096
     */
1097
0
    if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
1098
0
      ;
1099
0
    else
1100
0
      goto fail;
1101
0
  }
1102
1103
0
  if(!ci->running &&    /* read from a file */
1104
0
     ci->newsession &&  /* clean session cookies */
1105
0
     !co->expires)      /* this is a session cookie since it does not expire */
1106
0
    goto fail;
1107
1108
0
  co->livecookie = ci->running;
1109
0
  co->creationtime = ++ci->lastct;
1110
1111
  /*
1112
   * Now we have parsed the incoming line, we must now check if this supersedes
1113
   * an already existing cookie, which it may if the previous have the same
1114
   * domain and path as this.
1115
   */
1116
1117
  /* remove expired cookies */
1118
0
  if(!noexpire)
1119
0
    remove_expired(ci);
1120
1121
0
  if(is_public_suffix(data, co, domain))
1122
0
    goto fail;
1123
1124
0
  if(replace_existing(data, co, ci, secure, &replaces))
1125
0
    goto fail;
1126
1127
  /* add this cookie to the list */
1128
0
  myhash = cookiehash(co->domain);
1129
0
  Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
1130
1131
0
  if(ci->running)
1132
    /* Only show this when NOT reading the cookies from a file */
1133
0
    infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1134
0
          "expire %" FMT_OFF_T,
1135
0
          replaces ? "Replaced":"Added", co->name, co->value,
1136
0
          co->domain, co->path, co->expires);
1137
1138
0
  if(!replaces)
1139
0
    ci->numcookies++; /* one more cookie in the jar */
1140
1141
  /*
1142
   * Now that we have added a new cookie to the jar, update the expiration
1143
   * tracker in case it is the next one to expire.
1144
   */
1145
0
  if(co->expires && (co->expires < ci->next_expiration))
1146
0
    ci->next_expiration = co->expires;
1147
1148
0
  return co;
1149
0
fail:
1150
0
  freecookie(co);
1151
0
  return NULL;
1152
0
}
1153
1154
1155
/*
1156
 * Curl_cookie_init()
1157
 *
1158
 * Inits a cookie struct to read data from a local file. This is always
1159
 * called before any cookies are set. File may be NULL in which case only the
1160
 * struct is initialized. Is file is "-" then STDIN is read.
1161
 *
1162
 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1163
 *
1164
 * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1165
 * will be ignored.
1166
 *
1167
 * Returns NULL on out of memory. Invalid cookies are ignored.
1168
 */
1169
struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
1170
                                    const char *file,
1171
                                    struct CookieInfo *ci,
1172
                                    bool newsession)
1173
0
{
1174
0
  FILE *handle = NULL;
1175
1176
0
  if(!ci) {
1177
0
    int i;
1178
1179
    /* we did not get a struct, create one */
1180
0
    ci = calloc(1, sizeof(struct CookieInfo));
1181
0
    if(!ci)
1182
0
      return NULL; /* failed to get memory */
1183
1184
    /* This does not use the destructor callback since we want to add
1185
       and remove to lists while keeping the cookie struct intact */
1186
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++)
1187
0
      Curl_llist_init(&ci->cookielist[i], NULL);
1188
    /*
1189
     * Initialize the next_expiration time to signal that we do not have enough
1190
     * information yet.
1191
     */
1192
0
    ci->next_expiration = CURL_OFF_T_MAX;
1193
0
  }
1194
0
  ci->newsession = newsession; /* new session? */
1195
1196
0
  if(data) {
1197
0
    FILE *fp = NULL;
1198
0
    if(file && *file) {
1199
0
      if(!strcmp(file, "-"))
1200
0
        fp = stdin;
1201
0
      else {
1202
0
        fp = fopen(file, "rb");
1203
0
        if(!fp)
1204
0
          infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1205
0
        else
1206
0
          handle = fp;
1207
0
      }
1208
0
    }
1209
1210
0
    ci->running = FALSE; /* this is not running, this is init */
1211
0
    if(fp) {
1212
0
      struct dynbuf buf;
1213
0
      curlx_dyn_init(&buf, MAX_COOKIE_LINE);
1214
0
      while(Curl_get_line(&buf, fp)) {
1215
0
        const char *lineptr = curlx_dyn_ptr(&buf);
1216
0
        bool headerline = FALSE;
1217
0
        if(checkprefix("Set-Cookie:", lineptr)) {
1218
          /* This is a cookie line, get it! */
1219
0
          lineptr += 11;
1220
0
          headerline = TRUE;
1221
0
          curlx_str_passblanks(&lineptr);
1222
0
        }
1223
1224
0
        Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE);
1225
0
      }
1226
0
      curlx_dyn_free(&buf); /* free the line buffer */
1227
1228
      /*
1229
       * Remove expired cookies from the hash. We must make sure to run this
1230
       * after reading the file, and not on every cookie.
1231
       */
1232
0
      remove_expired(ci);
1233
1234
0
      if(handle)
1235
0
        fclose(handle);
1236
0
    }
1237
0
    data->state.cookie_engine = TRUE;
1238
0
  }
1239
0
  ci->running = TRUE;          /* now, we are running */
1240
1241
0
  return ci;
1242
0
}
1243
1244
/*
1245
 * cookie_sort
1246
 *
1247
 * Helper function to sort cookies such that the longest path gets before the
1248
 * shorter path. Path, domain and name lengths are considered in that order,
1249
 * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1250
 * be unique per cookie, so we know we will get an ordering at that point.
1251
 */
1252
static int cookie_sort(const void *p1, const void *p2)
1253
0
{
1254
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1255
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1256
0
  size_t l1, l2;
1257
1258
  /* 1 - compare cookie path lengths */
1259
0
  l1 = c1->path ? strlen(c1->path) : 0;
1260
0
  l2 = c2->path ? strlen(c2->path) : 0;
1261
1262
0
  if(l1 != l2)
1263
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1264
1265
  /* 2 - compare cookie domain lengths */
1266
0
  l1 = c1->domain ? strlen(c1->domain) : 0;
1267
0
  l2 = c2->domain ? strlen(c2->domain) : 0;
1268
1269
0
  if(l1 != l2)
1270
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1271
1272
  /* 3 - compare cookie name lengths */
1273
0
  l1 = c1->name ? strlen(c1->name) : 0;
1274
0
  l2 = c2->name ? strlen(c2->name) : 0;
1275
1276
0
  if(l1 != l2)
1277
0
    return (l2 > l1) ? 1 : -1;
1278
1279
  /* 4 - compare cookie creation time */
1280
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1281
0
}
1282
1283
/*
1284
 * cookie_sort_ct
1285
 *
1286
 * Helper function to sort cookies according to creation time.
1287
 */
1288
static int cookie_sort_ct(const void *p1, const void *p2)
1289
0
{
1290
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1291
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1292
1293
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1294
0
}
1295
1296
/*
1297
 * Curl_cookie_getlist
1298
 *
1299
 * For a given host and path, return a linked list of cookies that the client
1300
 * should send to the server if used now. The secure boolean informs the cookie
1301
 * if a secure connection is achieved or not.
1302
 *
1303
 * It shall only return cookies that have not expired.
1304
 *
1305
 * Returns 0 when there is a list returned. Otherwise non-zero.
1306
 */
1307
int Curl_cookie_getlist(struct Curl_easy *data,
1308
                        struct CookieInfo *ci,
1309
                        const char *host, const char *path,
1310
                        bool secure,
1311
                        struct Curl_llist *list)
1312
0
{
1313
0
  size_t matches = 0;
1314
0
  bool is_ip;
1315
0
  const size_t myhash = cookiehash(host);
1316
0
  struct Curl_llist_node *n;
1317
1318
0
  Curl_llist_init(list, NULL);
1319
1320
0
  if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
1321
0
    return 1; /* no cookie struct or no cookies in the struct */
1322
1323
  /* at first, remove expired cookies */
1324
0
  remove_expired(ci);
1325
1326
  /* check if host is an IP(v4|v6) address */
1327
0
  is_ip = Curl_host_is_ipnum(host);
1328
1329
0
  for(n = Curl_llist_head(&ci->cookielist[myhash]);
1330
0
      n; n = Curl_node_next(n)) {
1331
0
    struct Cookie *co = Curl_node_elem(n);
1332
1333
    /* if the cookie requires we are secure we must only continue if we are! */
1334
0
    if(co->secure ? secure : TRUE) {
1335
1336
      /* now check if the domain is correct */
1337
0
      if(!co->domain ||
1338
0
         (co->tailmatch && !is_ip &&
1339
0
          cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1340
0
         ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
1341
        /*
1342
         * the right part of the host matches the domain stuff in the
1343
         * cookie data
1344
         */
1345
1346
        /*
1347
         * now check the left part of the path with the cookies path
1348
         * requirement
1349
         */
1350
0
        if(!co->spath || pathmatch(co->spath, path) ) {
1351
1352
          /*
1353
           * This is a match and we add it to the return-linked-list
1354
           */
1355
0
          Curl_llist_append(list, co, &co->getnode);
1356
0
          matches++;
1357
0
          if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1358
0
            infof(data, "Included max number of cookies (%zu) in request!",
1359
0
                  matches);
1360
0
            break;
1361
0
          }
1362
0
        }
1363
0
      }
1364
0
    }
1365
0
  }
1366
1367
0
  if(matches) {
1368
    /*
1369
     * Now we need to make sure that if there is a name appearing more than
1370
     * once, the longest specified path version comes first. To make this
1371
     * the swiftest way, we just sort them all based on path length.
1372
     */
1373
0
    struct Cookie **array;
1374
0
    size_t i;
1375
1376
    /* alloc an array and store all cookie pointers */
1377
0
    array = malloc(sizeof(struct Cookie *) * matches);
1378
0
    if(!array)
1379
0
      goto fail;
1380
1381
0
    n = Curl_llist_head(list);
1382
1383
0
    for(i = 0; n; n = Curl_node_next(n))
1384
0
      array[i++] = Curl_node_elem(n);
1385
1386
    /* now sort the cookie pointers in path length order */
1387
0
    qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1388
1389
    /* remake the linked list order according to the new order */
1390
0
    Curl_llist_destroy(list, NULL);
1391
1392
0
    for(i = 0; i < matches; i++)
1393
0
      Curl_llist_append(list, array[i], &array[i]->getnode);
1394
1395
0
    free(array); /* remove the temporary data again */
1396
0
  }
1397
1398
0
  return 0; /* success */
1399
1400
0
fail:
1401
  /* failure, clear up the allocated chain and return NULL */
1402
0
  Curl_llist_destroy(list, NULL);
1403
0
  return 2; /* error */
1404
0
}
1405
1406
/*
1407
 * Curl_cookie_clearall
1408
 *
1409
 * Clear all existing cookies and reset the counter.
1410
 */
1411
void Curl_cookie_clearall(struct CookieInfo *ci)
1412
0
{
1413
0
  if(ci) {
1414
0
    unsigned int i;
1415
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1416
0
      struct Curl_llist_node *n;
1417
0
      for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
1418
0
        struct Cookie *c = Curl_node_elem(n);
1419
0
        struct Curl_llist_node *e = Curl_node_next(n);
1420
0
        Curl_node_remove(n);
1421
0
        freecookie(c);
1422
0
        n = e;
1423
0
      }
1424
0
    }
1425
0
    ci->numcookies = 0;
1426
0
  }
1427
0
}
1428
1429
/*
1430
 * Curl_cookie_clearsess
1431
 *
1432
 * Free all session cookies in the cookies list.
1433
 */
1434
void Curl_cookie_clearsess(struct CookieInfo *ci)
1435
0
{
1436
0
  unsigned int i;
1437
1438
0
  if(!ci)
1439
0
    return;
1440
1441
0
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1442
0
    struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);
1443
0
    struct Curl_llist_node *e = NULL;
1444
1445
0
    for(; n; n = e) {
1446
0
      struct Cookie *curr = Curl_node_elem(n);
1447
0
      e = Curl_node_next(n); /* in case the node is removed, get it early */
1448
0
      if(!curr->expires) {
1449
0
        Curl_node_remove(n);
1450
0
        freecookie(curr);
1451
0
        ci->numcookies--;
1452
0
      }
1453
0
    }
1454
0
  }
1455
0
}
1456
1457
/*
1458
 * Curl_cookie_cleanup()
1459
 *
1460
 * Free a "cookie object" previous created with Curl_cookie_init().
1461
 */
1462
void Curl_cookie_cleanup(struct CookieInfo *ci)
1463
458k
{
1464
458k
  if(ci) {
1465
0
    Curl_cookie_clearall(ci);
1466
0
    free(ci); /* free the base struct as well */
1467
0
  }
1468
458k
}
1469
1470
/*
1471
 * get_netscape_format()
1472
 *
1473
 * Formats a string for Netscape output file, w/o a newline at the end.
1474
 * Function returns a char * to a formatted line. The caller is responsible
1475
 * for freeing the returned pointer.
1476
 */
1477
static char *get_netscape_format(const struct Cookie *co)
1478
0
{
1479
0
  return aprintf(
1480
0
    "%s"     /* httponly preamble */
1481
0
    "%s%s\t" /* domain */
1482
0
    "%s\t"   /* tailmatch */
1483
0
    "%s\t"   /* path */
1484
0
    "%s\t"   /* secure */
1485
0
    "%" FMT_OFF_T "\t"   /* expires */
1486
0
    "%s\t"   /* name */
1487
0
    "%s",    /* value */
1488
0
    co->httponly ? "#HttpOnly_" : "",
1489
    /*
1490
     * Make sure all domains are prefixed with a dot if they allow
1491
     * tailmatching. This is Mozilla-style.
1492
     */
1493
0
    (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
1494
0
    co->domain ? co->domain : "unknown",
1495
0
    co->tailmatch ? "TRUE" : "FALSE",
1496
0
    co->path ? co->path : "/",
1497
0
    co->secure ? "TRUE" : "FALSE",
1498
0
    co->expires,
1499
0
    co->name,
1500
0
    co->value ? co->value : "");
1501
0
}
1502
1503
/*
1504
 * cookie_output()
1505
 *
1506
 * Writes all internally known cookies to the specified file. Specify
1507
 * "-" as filename to write to stdout.
1508
 *
1509
 * The function returns non-zero on write failure.
1510
 */
1511
static CURLcode cookie_output(struct Curl_easy *data,
1512
                              struct CookieInfo *ci,
1513
                              const char *filename)
1514
0
{
1515
0
  FILE *out = NULL;
1516
0
  bool use_stdout = FALSE;
1517
0
  char *tempstore = NULL;
1518
0
  CURLcode error = CURLE_OK;
1519
1520
0
  if(!ci)
1521
    /* no cookie engine alive */
1522
0
    return CURLE_OK;
1523
1524
  /* at first, remove expired cookies */
1525
0
  remove_expired(ci);
1526
1527
0
  if(!strcmp("-", filename)) {
1528
    /* use stdout */
1529
0
    out = stdout;
1530
0
    use_stdout = TRUE;
1531
0
  }
1532
0
  else {
1533
0
    error = Curl_fopen(data, filename, &out, &tempstore);
1534
0
    if(error)
1535
0
      goto error;
1536
0
  }
1537
1538
0
  fputs("# Netscape HTTP Cookie File\n"
1539
0
        "# https://curl.se/docs/http-cookies.html\n"
1540
0
        "# This file was generated by libcurl! Edit at your own risk.\n\n",
1541
0
        out);
1542
1543
0
  if(ci->numcookies) {
1544
0
    unsigned int i;
1545
0
    size_t nvalid = 0;
1546
0
    struct Cookie **array;
1547
0
    struct Curl_llist_node *n;
1548
1549
0
    array = calloc(1, sizeof(struct Cookie *) * ci->numcookies);
1550
0
    if(!array) {
1551
0
      error = CURLE_OUT_OF_MEMORY;
1552
0
      goto error;
1553
0
    }
1554
1555
    /* only sort the cookies with a domain property */
1556
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1557
0
      for(n = Curl_llist_head(&ci->cookielist[i]); n;
1558
0
          n = Curl_node_next(n)) {
1559
0
        struct Cookie *co = Curl_node_elem(n);
1560
0
        if(!co->domain)
1561
0
          continue;
1562
0
        array[nvalid++] = co;
1563
0
      }
1564
0
    }
1565
1566
0
    qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1567
1568
0
    for(i = 0; i < nvalid; i++) {
1569
0
      char *format_ptr = get_netscape_format(array[i]);
1570
0
      if(!format_ptr) {
1571
0
        free(array);
1572
0
        error = CURLE_OUT_OF_MEMORY;
1573
0
        goto error;
1574
0
      }
1575
0
      fprintf(out, "%s\n", format_ptr);
1576
0
      free(format_ptr);
1577
0
    }
1578
1579
0
    free(array);
1580
0
  }
1581
1582
0
  if(!use_stdout) {
1583
0
    fclose(out);
1584
0
    out = NULL;
1585
0
    if(tempstore && Curl_rename(tempstore, filename)) {
1586
0
      unlink(tempstore);
1587
0
      error = CURLE_WRITE_ERROR;
1588
0
      goto error;
1589
0
    }
1590
0
  }
1591
1592
  /*
1593
   * If we reach here we have successfully written a cookie file so there is
1594
   * no need to inspect the error, any error case should have jumped into the
1595
   * error block below.
1596
   */
1597
0
  free(tempstore);
1598
0
  return CURLE_OK;
1599
1600
0
error:
1601
0
  if(out && !use_stdout)
1602
0
    fclose(out);
1603
0
  free(tempstore);
1604
0
  return error;
1605
0
}
1606
1607
static struct curl_slist *cookie_list(struct Curl_easy *data)
1608
0
{
1609
0
  struct curl_slist *list = NULL;
1610
0
  struct curl_slist *beg;
1611
0
  unsigned int i;
1612
0
  struct Curl_llist_node *n;
1613
1614
0
  if(!data->cookies || (data->cookies->numcookies == 0))
1615
0
    return NULL;
1616
1617
0
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1618
0
    for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;
1619
0
        n = Curl_node_next(n)) {
1620
0
      struct Cookie *c = Curl_node_elem(n);
1621
0
      char *line;
1622
0
      if(!c->domain)
1623
0
        continue;
1624
0
      line = get_netscape_format(c);
1625
0
      if(!line) {
1626
0
        curl_slist_free_all(list);
1627
0
        return NULL;
1628
0
      }
1629
0
      beg = Curl_slist_append_nodup(list, line);
1630
0
      if(!beg) {
1631
0
        free(line);
1632
0
        curl_slist_free_all(list);
1633
0
        return NULL;
1634
0
      }
1635
0
      list = beg;
1636
0
    }
1637
0
  }
1638
1639
0
  return list;
1640
0
}
1641
1642
struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1643
0
{
1644
0
  struct curl_slist *list;
1645
0
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1646
0
  list = cookie_list(data);
1647
0
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1648
0
  return list;
1649
0
}
1650
1651
void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1652
458k
{
1653
458k
  CURLcode res;
1654
1655
458k
  if(data->set.str[STRING_COOKIEJAR]) {
1656
0
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1657
1658
    /* if we have a destination file for all the cookies to get dumped to */
1659
0
    res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);
1660
0
    if(res)
1661
0
      infof(data, "WARNING: failed to save cookies in %s: %s",
1662
0
            data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
1663
0
  }
1664
458k
  else {
1665
458k
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1666
458k
  }
1667
1668
458k
  if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1669
458k
    Curl_cookie_cleanup(data->cookies);
1670
458k
    data->cookies = NULL;
1671
458k
  }
1672
458k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1673
458k
}
1674
1675
#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */