Coverage Report

Created: 2026-10-22 06:49

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