Coverage Report

Created: 2025-10-10 06:31

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