Coverage Report

Created: 2026-10-22 06:51

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
1.98k
#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
16.6k
{
55
16.6k
  if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {
56
990
    timediff_t cap = now + COOKIES_MAXAGE;
57
990
    if(co->expires > cap) {
58
378
      cap += 30;
59
378
      co->expires = (cap / 60) * 60;
60
378
    }
61
990
  }
62
16.6k
}
63
64
static void freecookie(struct Cookie *co, bool maintoo)
65
37.0k
{
66
37.0k
  curlx_free(co->domain);
67
37.0k
  curlx_free(co->path);
68
37.0k
  curlx_free(co->name);
69
37.0k
  curlx_free(co->value);
70
37.0k
  if(maintoo)
71
8.39k
    curlx_free(co);
72
37.0k
}
73
74
static bool cookie_tailmatch(const char *cookie_domain,
75
                             const size_t cookie_domain_len,
76
                             const char *hostname)
77
5.44k
{
78
5.44k
  size_t hostname_len = strlen(hostname);
79
80
5.44k
  if(hostname_len < cookie_domain_len)
81
22
    return FALSE;
82
83
5.42k
  if(!curl_strnequal(cookie_domain,
84
5.42k
                     hostname + hostname_len - cookie_domain_len,
85
5.42k
                     cookie_domain_len))
86
22
    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
5.40k
  if(hostname_len == cookie_domain_len)
97
5.37k
    return TRUE;
98
30
  if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
99
20
    return TRUE;
100
10
  return FALSE;
101
30
}
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
2.33k
{
109
2.33k
  size_t cookie_path_len;
110
2.33k
  size_t uri_path_len;
111
2.33k
  bool ret = FALSE;
112
113
  /* cookie_path must not have last '/' separator. ex: /sample */
114
2.33k
  cookie_path_len = strlen(cookie_path);
115
2.33k
  if(cookie_path_len == 1) {
116
    /* cookie_path must be '/' */
117
1.75k
    return TRUE;
118
1.75k
  }
119
120
  /* #-fragments are already cut off! */
121
577
  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
577
  uri_path_len = strlen(uri_path);
135
136
577
  if(uri_path_len < cookie_path_len)
137
102
    goto pathmatched;
138
139
  /* not using checkprefix() because matching should be case-sensitive */
140
475
  if(strncmp(cookie_path, uri_path, cookie_path_len))
141
171
    goto pathmatched;
142
143
  /* The cookie-path and the uri-path are identical. */
144
304
  if(cookie_path_len == uri_path_len) {
145
6
    ret = TRUE;
146
6
    goto pathmatched;
147
6
  }
148
149
  /* here, cookie_path_len < uri_path_len */
150
298
  if(uri_path[cookie_path_len] == '/') {
151
280
    ret = TRUE;
152
280
    goto pathmatched;
153
280
  }
154
155
577
pathmatched:
156
577
  return ret;
157
298
}
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
21.8k
{
164
21.8k
  size_t len = 0;
165
21.8k
  const char *first = NULL, *last;
166
167
21.8k
  if(domain) {
168
21.8k
    len = strlen(domain);
169
21.8k
    last = memrchr(domain, '.', len);
170
21.8k
    if(last) {
171
3.18k
      first = memrchr(domain, '.', (last - domain));
172
3.18k
      if(first)
173
2.06k
        len -= (++first - domain);
174
3.18k
    }
175
21.8k
  }
176
177
21.8k
  if(outlen)
178
21.8k
    *outlen = len;
179
180
21.8k
  return first ? first : domain;
181
21.8k
}
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
21.8k
{
193
21.8k
  const char *end = domain + len;
194
21.8k
  size_t h = 5381;
195
196
105k
  while(domain < end) {
197
83.7k
    size_t j = (size_t)Curl_raw_toupper(*domain++);
198
83.7k
    h += h << 5;
199
83.7k
    h ^= j;
200
83.7k
  }
201
202
21.8k
  return (h % COOKIE_HASH_SIZE);
203
21.8k
}
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
38.9k
{
214
38.9k
  const char *top;
215
38.9k
  size_t len;
216
217
38.9k
  if(!domain || Curl_host_is_ipnum(domain))
218
17.1k
    return 0;
219
220
21.8k
  top = get_top_domain(domain, &len);
221
21.8k
  return cookie_hash_domain(top, len);
222
38.9k
}
223
224
/*
225
 * cookie path sanitize
226
 */
227
static char *sanitize_cookie_path(const char *cookie_path, size_t len)
228
6.85k
{
229
  /* some sites send path attribute within '"'. */
230
6.85k
  if(len && (cookie_path[0] == '\"')) {
231
75
    cookie_path++;
232
75
    len--;
233
234
75
    if(len && (cookie_path[len - 1] == '\"'))
235
2
      len--;
236
75
  }
237
238
  /* RFC6265 5.2.4 The Path Attribute */
239
6.85k
  if(!len || (cookie_path[0] != '/'))
240
    /* Let cookie-path be the default-path. */
241
182
    return curlx_strdup("/");
242
243
  /* remove trailing slash when path is non-empty */
244
  /* convert /hoge/ to /hoge */
245
6.67k
  if(len > 1 && cookie_path[len - 1] == '/')
246
673
    len--;
247
248
6.67k
  return curlx_memdup0(cookie_path, len);
249
6.85k
}
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
16.2k
{
258
16.2k
  DEBUGASSERT(str);
259
16.2k
  *str = curlx_memdup0(newstr, len);
260
16.2k
  if(!*str)
261
0
    return CURLE_OUT_OF_MEMORY;
262
16.2k
  return CURLE_OK;
263
16.2k
}
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
55.7k
{
276
55.7k
  struct Cookie *co;
277
55.7k
  curl_off_t now = (curl_off_t)time(NULL);
278
55.7k
  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
55.7k
  if(now < ci->next_expiration &&
289
55.1k
     ci->next_expiration != CURL_OFF_T_MAX)
290
1.45k
    return;
291
54.3k
  else
292
54.3k
    ci->next_expiration = CURL_OFF_T_MAX;
293
294
3.47M
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
295
3.42M
    struct Curl_llist_node *n;
296
3.42M
    struct Curl_llist_node *e = NULL;
297
298
3.45M
    for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {
299
33.2k
      co = Curl_node_elem(n);
300
33.2k
      e = Curl_node_next(n);
301
33.2k
      if(co->expires) {
302
1.15k
        if(co->expires < now) {
303
605
          Curl_node_remove(n);
304
605
          freecookie(co, TRUE);
305
605
          ci->numcookies--;
306
605
        }
307
545
        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
293
          ci->next_expiration = co->expires;
313
1.15k
      }
314
33.2k
    }
315
3.42M
  }
316
54.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
0
{
322
0
  if((len == 9) && curl_strnequal(domain, "localhost", 9))
323
0
    return FALSE;
324
0
  else {
325
    /* there must be a dot present, but that dot must not be a trailing dot */
326
0
    const char *dot = memchr(domain, '.', len);
327
0
    if(dot) {
328
0
      size_t i = dot - domain;
329
0
      if((len - i) > 1)
330
        /* the dot is not the last byte */
331
0
        return FALSE;
332
0
    }
333
0
  }
334
0
  return TRUE;
335
0
}
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
79.4k
{
348
79.4k
  const unsigned char *p = (const unsigned char *)ptr;
349
  /* Reject all bytes \x01 - \x1f + \x7f */
350
820k
  while(len && *p) {
351
742k
    if((*p < 0x20) || (*p == 0x7f))
352
1.16k
      return TRUE;
353
741k
    p++;
354
741k
    len--;
355
741k
  }
356
78.2k
  return FALSE;
357
79.4k
}
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
16.7k
#define MAX_DATE_LENGTH 80
363
364
8.71k
#define COOKIE_NAME   0
365
8.71k
#define COOKIE_VALUE  1
366
0
#define COOKIE_DOMAIN 2
367
2.11k
#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.10k
{
374
8.10k
  CURLcode result;
375
8.10k
  result = strstore(&co->name, curlx_str(&cp[COOKIE_NAME]),
376
8.10k
                    curlx_strlen(&cp[COOKIE_NAME]));
377
8.10k
  if(!result)
378
8.10k
    result = strstore(&co->value, curlx_str(&cp[COOKIE_VALUE]),
379
8.10k
                      curlx_strlen(&cp[COOKIE_VALUE]));
380
8.10k
  if(!result) {
381
8.10k
    size_t plen = 0;
382
8.10k
    if(curlx_strlen(&cp[COOKIE_PATH])) {
383
1.82k
      path = curlx_str(&cp[COOKIE_PATH]);
384
1.82k
      plen = curlx_strlen(&cp[COOKIE_PATH]);
385
1.82k
    }
386
6.28k
    else if(path) {
387
      /* No path was given in the header line, set the default */
388
4.96k
      const char *endslash = strrchr(path, '/');
389
4.96k
      if(endslash)
390
4.96k
        plen = endslash - path + 1; /* include end slash */
391
0
      else
392
0
        plen = strlen(path);
393
4.96k
    }
394
395
8.10k
    if(path) {
396
6.79k
      co->path = sanitize_cookie_path(path, plen);
397
6.79k
      if(!co->path)
398
0
        result = CURLE_OUT_OF_MEMORY;
399
6.79k
    }
400
8.10k
  }
401
8.10k
  if(!result) {
402
8.10k
    if(curlx_strlen(&cp[COOKIE_DOMAIN]))
403
0
      result = strstore(&co->domain, curlx_str(&cp[COOKIE_DOMAIN]),
404
0
                        curlx_strlen(&cp[COOKIE_DOMAIN]));
405
8.10k
    else if(domain) {
406
      /* no domain was given in the header line, set the default */
407
6.76k
      co->domain = curlx_strdup(domain);
408
6.76k
      if(!co->domain)
409
0
        result = CURLE_OUT_OF_MEMORY;
410
6.76k
    }
411
8.10k
  }
412
8.10k
  return result;
413
8.10k
}
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
12.2k
{
425
  /* The first name/value pair is the actual cookie name */
426
12.2k
  if(!sep || !curlx_strlen(name)) {
427
3.53k
    infof(data, "invalid cookie, dropped");
428
3.53k
    return FALSE;
429
3.53k
  }
430
431
  /*
432
   * Check for too long individual name or contents. Chrome and Firefox
433
   * support 4095 or 4096 bytes combo
434
   */
435
8.72k
  if((curlx_strlen(name) + curlx_strlen(val)) > MAX_NAME) {
436
5
    infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
437
5
          curlx_strlen(name), curlx_strlen(val));
438
5
    return FALSE;
439
5
  }
440
441
  /* Check if we have a reserved prefix set. */
442
8.71k
  if(!strncmp("__Secure-", curlx_str(name), 9))
443
73
    co->prefix_secure = TRUE;
444
8.64k
  else if(!strncmp("__Host-", curlx_str(name), 7))
445
79
    co->prefix_host = TRUE;
446
447
8.71k
  cookie[COOKIE_NAME] = *name;
448
8.71k
  cookie[COOKIE_VALUE] = *val;
449
8.71k
  return TRUE;
450
8.72k
}
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
0
{
457
0
  bool is_ip;
458
0
  const char *domain = *domainp;
459
0
  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
0
  if('.' == *v)
466
0
    curlx_str_nudge(val, 1);
467
468
0
#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
0
  if(bad_domain(curlx_str(val), curlx_strlen(val))) {
475
0
    *domainp = ":";
476
0
    domain = ":";
477
0
  }
478
0
#endif
479
480
0
  is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(val));
481
482
0
  if(!domain ||
483
0
     (is_ip &&
484
0
      !strncmp(curlx_str(val), domain, curlx_strlen(val)) &&
485
0
      (curlx_strlen(val) == strlen(domain))) ||
486
0
     (!is_ip && cookie_tailmatch(curlx_str(val),
487
0
                                  curlx_strlen(val), domain))) {
488
0
    *cookie_domain = *val;
489
0
    if(!is_ip)
490
0
      co->tailmatch = TRUE; /* we always do that if the domain name was
491
                               given */
492
0
  }
493
0
  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
0
    infof(data, "skipped cookie with bad tailmatch domain: %s",
499
0
          curlx_str(val));
500
0
    return FALSE;
501
0
  }
502
0
  return TRUE;
503
0
}
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
17.0k
{
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
17.0k
  if(!co->expires && (curlx_strlen(val) < MAX_DATE_LENGTH)) {
547
16.6k
    char dbuf[MAX_DATE_LENGTH + 1];
548
16.6k
    time_t date = 0;
549
16.6k
    memcpy(dbuf, curlx_str(val), curlx_strlen(val));
550
16.6k
    dbuf[curlx_strlen(val)] = 0;
551
16.6k
    if(!Curl_getdate_capped(dbuf, &date)) {
552
990
      if(!date)
553
1
        date++;
554
990
      co->expires = (curl_off_t)date;
555
990
    }
556
15.6k
    else
557
15.6k
      co->expires = 0;
558
16.6k
    if(!*nowp)
559
2.30k
      *nowp = time(NULL);
560
16.6k
    cap_expires(*nowp, co);
561
16.6k
  }
562
17.0k
}
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
12.8k
{
575
  /* This line was read off an HTTP-header */
576
12.8k
  time_t now = 0;
577
12.8k
  size_t linelength = strlen(ptr);
578
12.8k
  CURLcode result = CURLE_OK;
579
12.8k
  struct Curl_str cookie[COOKIE_PIECES];
580
12.8k
  *okay = FALSE;
581
12.8k
  if(linelength > MAX_COOKIE_LINE)
582
    /* discard overly long lines at once */
583
1
    return CURLE_OK;
584
585
  /* memset instead of initializer because gcc 4.8.1 is silly */
586
12.8k
  memset(cookie, 0, sizeof(cookie));
587
44.8k
  do {
588
44.8k
    struct Curl_str name;
589
590
    /* we have a <name>=<value> pair or a stand-alone word here */
591
44.8k
    int rc = curlx_str_cspn(&ptr, &name, ";\r\n=");
592
44.8k
    if(!rc || (rc == STRE_SHORT)) {
593
44.8k
      struct Curl_str val;
594
44.8k
      bool sep = FALSE;
595
44.8k
      curlx_str_trimblanks(&name);
596
597
44.8k
      if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) {
598
643
        infof(data, "invalid octets in name, cookie dropped");
599
643
        return CURLE_OK;
600
643
      }
601
602
44.1k
      if(!curlx_str_single(&ptr, '=')) {
603
33.7k
        sep = TRUE; /* a '=' was used */
604
33.7k
        if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
605
29.6k
          curlx_str_trimblanks(&val);
606
607
33.7k
        if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) {
608
505
          infof(data, "invalid octets in value, cookie dropped");
609
505
          return CURLE_OK;
610
505
        }
611
33.7k
      }
612
10.4k
      else
613
10.4k
        curlx_str_init(&val);
614
615
43.6k
      if(!curlx_strlen(&cookie[COOKIE_NAME])) {
616
12.2k
        if(!parse_first_pair(data, co, cookie, &name, &val, sep))
617
3.54k
          return CURLE_OK;
618
12.2k
      }
619
620
      /* whether there's separator or not */
621
31.4k
      else if(curlx_str_casecompare(&name, "secure")) {
622
580
        if(secure_origin)
623
498
          co->secure = TRUE;
624
82
        else {
625
82
          infof(data, "skipped cookie because not 'secure'");
626
82
          return CURLE_OK;
627
82
        }
628
580
      }
629
30.8k
      else if(curlx_str_casecompare(&name, "httponly"))
630
226
        co->httponly = TRUE;
631
30.6k
      else if(sep) {
632
        /* only if a separating equals sign is present */
633
24.1k
        if(curlx_str_casecompare(&name, "path"))
634
2.11k
          cookie[COOKIE_PATH] = val;
635
22.0k
        else if(curlx_str_casecompare(&name, "domain")) {
636
0
          if(curlx_strlen(&val) &&
637
0
             !parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain))
638
0
            return CURLE_OK;
639
0
        }
640
22.0k
        else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val))
641
0
          parse_maxage(co, &val, &now);
642
22.0k
        else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val))
643
17.0k
          parse_expires(co, &val, &now);
644
24.1k
      }
645
43.6k
    }
646
44.8k
  } while(!curlx_str_single(&ptr, ';'));
647
648
8.10k
  if(curlx_strlen(&cookie[COOKIE_NAME])) {
649
    /* the header was fine, now store the data */
650
8.10k
    result = storecookie(co, &cookie[0], path, domain);
651
8.10k
    if(!result)
652
8.10k
      *okay = TRUE;
653
8.10k
  }
654
8.10k
  return result;
655
12.8k
}
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
24.1k
{
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
24.1k
  const char *ptr, *next;
668
24.1k
  int fields;
669
24.1k
  size_t len;
670
24.1k
  *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
24.1k
  if(!strncmp(lineptr, "#HttpOnly_", 10)) {
679
1
    lineptr += 10;
680
1
    co->httponly = TRUE;
681
1
  }
682
683
24.1k
  if(lineptr[0] == '#')
684
    /* do not even try the comments */
685
51
    return CURLE_OK;
686
687
  /*
688
   * Now loop through the fields and init the struct we already have
689
   * allocated
690
   */
691
24.0k
  fields = 0;
692
50.5k
  for(next = lineptr; next; fields++) {
693
26.6k
    ptr = next;
694
26.6k
    len = strcspn(ptr, "\t\r\n");
695
26.6k
    next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);
696
26.6k
    switch(fields) {
697
24.0k
    case 0:
698
24.0k
      if(ptr[0] == '.') { /* skip preceding dots */
699
6
        ptr++;
700
6
        len--;
701
6
      }
702
24.0k
      co->domain = curlx_memdup0(ptr, len);
703
24.0k
      if(!co->domain)
704
0
        return CURLE_OUT_OF_MEMORY;
705
24.0k
      break;
706
24.0k
    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
617
      co->tailmatch = !!curl_strnequal(ptr, "TRUE", len);
713
617
      break;
714
613
    case 2:
715
      /* The file format allows the path field to remain not filled in */
716
613
      if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {
717
        /* only if the path does not look like a boolean option! */
718
68
        co->path = sanitize_cookie_path(ptr, len);
719
68
        if(!co->path)
720
0
          return CURLE_OUT_OF_MEMORY;
721
68
        break;
722
68
      }
723
545
      else {
724
        /* this does not look like a path, make one up! */
725
545
        co->path = curlx_strdup("/");
726
545
        if(!co->path)
727
0
          return CURLE_OUT_OF_MEMORY;
728
545
      }
729
545
      fields++; /* add a field and fall down to secure */
730
545
      FALLTHROUGH();
731
562
    case 3:
732
562
      co->secure = FALSE;
733
562
      if(curl_strnequal(ptr, "TRUE", len)) {
734
519
        if(secure_origin || ci->running)
735
519
          co->secure = TRUE;
736
0
        else
737
0
          return CURLE_OK;
738
519
      }
739
562
      break;
740
562
    case 4:
741
553
      if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX))
742
78
        return CURLE_OK;
743
475
      break;
744
475
    case 5:
745
461
      co->name = curlx_memdup0(ptr, len);
746
461
      if(!co->name)
747
0
        return CURLE_OUT_OF_MEMORY;
748
461
      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
461
        if(!strncmp("__Secure-", co->name, 9))
753
1
          co->prefix_secure = TRUE;
754
460
        else if(!strncmp("__Host-", co->name, 7))
755
3
          co->prefix_host = TRUE;
756
461
      }
757
461
      break;
758
461
    case 6:
759
33
      co->value = curlx_memdup0(ptr, len);
760
33
      if(!co->value)
761
0
        return CURLE_OUT_OF_MEMORY;
762
33
      break;
763
26.6k
    }
764
26.6k
  }
765
23.9k
  if(fields == 6) {
766
    /* we got a cookie with blank contents, fix it */
767
428
    co->value = curlx_strdup("");
768
428
    if(!co->value)
769
0
      return CURLE_OUT_OF_MEMORY;
770
428
    else
771
428
      fields++;
772
428
  }
773
774
23.9k
  if(fields != 7)
775
    /* we did not find the sufficient number of fields */
776
23.5k
    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
449
  if(invalid_octets(co->name, strlen(co->name)) ||
783
438
     invalid_octets(co->value, strlen(co->value)))
784
12
    return CURLE_OK;
785
786
437
  *okay = TRUE;
787
437
  return CURLE_OK;
788
449
}
789
790
static bool is_public_suffix(struct Curl_easy *data,
791
                             struct Cookie *co,
792
                             const char *domain)
793
6.69k
{
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
6.69k
  (void)data;
861
6.69k
  (void)co;
862
6.69k
  (void)domain;
863
6.69k
  DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",
864
6.69k
               co->name, co->domain, domain ? domain : "[file]"));
865
6.69k
#endif
866
6.69k
  return FALSE;
867
6.69k
}
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
8.39k
{
876
8.39k
  bool replace_old = FALSE;
877
8.39k
  struct Curl_llist_node *replace_n = NULL;
878
8.39k
  struct Curl_llist_node *n;
879
8.39k
  size_t myhash = cookiehash(co->domain);
880
42.6k
  for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
881
34.2k
    struct Cookie *clist = Curl_node_elem(n);
882
34.2k
    if(!strcmp(clist->name, co->name)) {
883
      /* the names are identical */
884
5.44k
      bool matching_domains = FALSE;
885
886
5.44k
      if(clist->domain && co->domain) {
887
5.36k
        if(cookie_tailmatch(clist->domain, strlen(clist->domain),
888
5.36k
                            co->domain) ||
889
10
           cookie_tailmatch(co->domain, strlen(co->domain), clist->domain))
890
          /* The existing one is a tail of the new or vice versa */
891
5.35k
          matching_domains = TRUE;
892
5.36k
      }
893
77
      else if(!clist->domain && !co->domain)
894
0
        matching_domains = TRUE;
895
896
5.44k
      if(matching_domains && /* the domains were identical */
897
5.35k
         clist->path && co->path && /* both have paths */
898
5.35k
         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
5.44k
    }
924
925
34.2k
    if(!replace_n && !strcmp(clist->name, co->name)) {
926
      /* the names are identical */
927
928
4.89k
      if(clist->domain && co->domain) {
929
4.82k
        if(curl_strequal(clist->domain, co->domain) &&
930
4.81k
           (clist->tailmatch == co->tailmatch))
931
          /* The domains are identical */
932
4.81k
          replace_old = TRUE;
933
4.82k
      }
934
77
      else if(!clist->domain && !co->domain)
935
0
        replace_old = TRUE;
936
937
4.89k
      if(replace_old) {
938
        /* the domains were identical */
939
940
4.81k
        if(clist->path && co->path &&
941
4.81k
           strcmp(clist->path, co->path))
942
1.33k
          replace_old = FALSE;
943
3.47k
        else if(!clist->path != !co->path)
944
0
          replace_old = FALSE;
945
4.81k
      }
946
947
4.89k
      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
4.89k
      if(replace_old)
957
3.47k
        replace_n = n;
958
4.89k
    }
959
34.2k
  }
960
8.39k
  if(replace_n) {
961
3.47k
    struct Cookie *repl = Curl_node_elem(replace_n);
962
963
    /* when replacing, creationtime is kept from old */
964
3.47k
    co->creationtime = repl->creationtime;
965
966
    /* unlink the old */
967
3.47k
    Curl_node_remove(replace_n);
968
969
    /* free the old cookie */
970
3.47k
    freecookie(repl, TRUE);
971
3.47k
  }
972
8.39k
  *replacep = replace_old;
973
8.39k
  return TRUE;
974
8.39k
}
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
37.7k
{
994
37.7k
  struct Cookie comem;
995
37.7k
  struct Cookie *co;
996
37.7k
  size_t myhash;
997
37.7k
  CURLcode result;
998
37.7k
  bool replaces = FALSE;
999
37.7k
  bool okay;
1000
1001
37.7k
  DEBUGASSERT(data);
1002
37.7k
  DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
1003
37.7k
  if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
1004
713
    return CURLE_OK; /* silently ignore */
1005
1006
37.0k
  co = &comem;
1007
37.0k
  memset(co, 0, sizeof(comem));
1008
1009
37.0k
  if(flags & COOKIE_HTTPHEADER)
1010
12.8k
    result = parse_cookie_header(data, co, &okay,
1011
12.8k
                                 lineptr, domain, path, flags & COOKIE_SECURE);
1012
24.1k
  else
1013
24.1k
    result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE);
1014
1015
37.0k
  if(result || !okay)
1016
28.4k
    goto fail;
1017
1018
8.54k
  if(co->prefix_secure && !co->secure)
1019
    /* The __Secure- prefix only requires that the cookie be set secure */
1020
73
    goto fail;
1021
1022
8.47k
  if(!(flags & COOKIE_NOPSL) && is_public_suffix(data, co, domain))
1023
0
    goto fail;
1024
1025
8.47k
  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
82
    if(co->secure && co->path && !strcmp(co->path, "/") && !co->tailmatch)
1031
1
      ;
1032
81
    else
1033
81
      goto fail;
1034
82
  }
1035
1036
8.39k
  if(!ci->running &&    /* read from a file */
1037
1.76k
     ci->newsession &&  /* clean session cookies */
1038
0
     !co->expires)      /* this is a session cookie */
1039
0
    goto fail;
1040
1041
8.39k
  co->livecookie = ci->running;
1042
8.39k
  co->creationtime = ++ci->lastct;
1043
1044
8.39k
  if(!(flags & COOKIE_NOEXPIRE))
1045
8.39k
    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
8.39k
  if(!replace_existing(data, co, ci, flags & COOKIE_SECURE, &replaces))
1053
0
    goto fail;
1054
1055
  /* clone the stack struct into heap */
1056
8.39k
  co = curlx_memdup(&comem, sizeof(comem));
1057
8.39k
  if(!co) {
1058
0
    co = &comem;
1059
0
    result = CURLE_OUT_OF_MEMORY;
1060
0
    goto fail; /* bail out if we are this low on memory */
1061
0
  }
1062
1063
  /* add this cookie to the list */
1064
8.39k
  myhash = cookiehash(co->domain);
1065
8.39k
  Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
1066
1067
8.39k
  if(ci->running)
1068
    /* Only show this when NOT reading the cookies from a file */
1069
6.62k
    infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1070
8.39k
          "expire %" FMT_OFF_T,
1071
8.39k
          replaces ? "Replaced" : "Added", co->name, co->value,
1072
8.39k
          co->domain, co->path, co->expires);
1073
1074
8.39k
  if(!replaces)
1075
4.91k
    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
8.39k
  if(co->expires && (co->expires < ci->next_expiration))
1082
1.08k
    ci->next_expiration = co->expires;
1083
1084
8.39k
  if(flags & COOKIE_HTTPHEADER)
1085
7.95k
    data->req.setcookies++;
1086
1087
8.39k
  return result;
1088
28.6k
fail:
1089
28.6k
  freecookie(co, FALSE);
1090
28.6k
  return result;
1091
8.39k
}
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
25.9k
{
1109
25.9k
  int i;
1110
25.9k
  struct CookieInfo *ci = curlx_calloc(1, sizeof(struct CookieInfo));
1111
25.9k
  if(!ci)
1112
0
    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.65M
  for(i = 0; i < COOKIE_HASH_SIZE; i++)
1117
1.63M
    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
25.9k
  ci->next_expiration = CURL_OFF_T_MAX;
1123
1124
25.9k
  return ci;
1125
25.9k
}
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
23.3k
{
1139
23.3k
  FILE *handle = NULL;
1140
23.3k
  CURLcode result = CURLE_OK;
1141
23.3k
  FILE *fp = NULL;
1142
23.3k
  DEBUGASSERT(ci);
1143
23.3k
  DEBUGASSERT(data);
1144
23.3k
  DEBUGASSERT(file);
1145
1146
23.3k
  ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */
1147
23.3k
  ci->running = FALSE; /* this is not running, this is init */
1148
1149
23.3k
  if(file && *file) {
1150
23.3k
    if(!strcmp(file, "-"))
1151
0
      fp = stdin;
1152
23.3k
    else {
1153
23.3k
      fp = curlx_fopen(file, "rb");
1154
23.3k
      if(!fp)
1155
0
        infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1156
23.3k
      else {
1157
23.3k
        curlx_struct_stat stat;
1158
23.3k
        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
23.3k
        else
1165
23.3k
          handle = fp;
1166
23.3k
      }
1167
23.3k
    }
1168
23.3k
  }
1169
1170
23.3k
  if(fp) {
1171
23.3k
    struct dynbuf buf;
1172
23.3k
    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
23.3k
    curlx_dyn_init(&buf, DYN_APRINTF + 2);
1178
23.3k
    do {
1179
23.3k
      result = Curl_get_line(&buf, fp, &eof);
1180
23.3k
      if(!result) {
1181
23.3k
        const char *lineptr = curlx_dyn_ptr(&buf);
1182
23.3k
        bool headerline = FALSE;
1183
23.3k
        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
23.3k
        result = Curl_cookie_add(data, ci, lineptr, NULL, NULL,
1191
23.3k
                                 (headerline ? COOKIE_HTTPHEADER : 0) |
1192
23.3k
                                 COOKIE_NOEXPIRE | COOKIE_SECURE |
1193
23.3k
                                 (flags & COOKIE_NOPSL));
1194
        /* Ignore individual cookie problems unless we ran out of memory */
1195
23.3k
        if(result != CURLE_OUT_OF_MEMORY)
1196
23.3k
          result = CURLE_OK;
1197
23.3k
      }
1198
23.3k
    } while(!result && !eof);
1199
23.3k
    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
23.3k
    remove_expired(ci);
1206
1207
23.3k
    if(handle)
1208
23.3k
      curlx_fclose(handle);
1209
23.3k
  }
1210
23.3k
  data->state.cookie_engine = TRUE;
1211
23.3k
  ci->running = TRUE; /* now, we are running */
1212
1213
23.3k
  return result;
1214
23.3k
}
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
23.7k
{
1222
23.7k
  CURLcode result = CURLE_OK;
1223
23.7k
  struct curl_slist *list = data->state.cookielist;
1224
23.7k
  if(list) {
1225
23.3k
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1226
23.3k
    if(!data->cookies)
1227
0
      data->cookies = Curl_cookie_init();
1228
23.3k
    if(!data->cookies)
1229
0
      result = CURLE_OUT_OF_MEMORY;
1230
23.3k
    else {
1231
23.3k
      data->state.cookie_engine = TRUE;
1232
46.6k
      while(list) {
1233
23.3k
        result = cookie_load(data, list->data, data->cookies, flags);
1234
23.3k
        if(result)
1235
0
          break;
1236
23.3k
        list = list->next;
1237
23.3k
      }
1238
23.3k
    }
1239
23.3k
    Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1240
23.3k
  }
1241
23.7k
  return result;
1242
23.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
3.46k
{
1254
3.46k
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1255
3.46k
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1256
3.46k
  size_t l1, l2;
1257
1258
  /* 1 - compare cookie path lengths */
1259
3.46k
  l1 = c1->path ? strlen(c1->path) : 0;
1260
3.46k
  l2 = c2->path ? strlen(c2->path) : 0;
1261
1262
3.46k
  if(l1 != l2)
1263
289
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1264
1265
  /* 2 - compare cookie domain lengths */
1266
3.18k
  l1 = c1->domain ? strlen(c1->domain) : 0;
1267
3.18k
  l2 = c2->domain ? strlen(c2->domain) : 0;
1268
1269
3.18k
  if(l1 != l2)
1270
24
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1271
1272
  /* 3 - compare cookie name lengths */
1273
3.15k
  l1 = c1->name ? strlen(c1->name) : 0;
1274
3.15k
  l2 = c2->name ? strlen(c2->name) : 0;
1275
1276
3.15k
  if(l1 != l2)
1277
2.07k
    return (l2 > l1) ? 1 : -1;
1278
1279
  /* 4 - compare cookie creation time */
1280
1.07k
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1281
3.15k
}
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
4.54k
{
1290
4.54k
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1291
4.54k
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1292
1293
4.54k
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1294
4.54k
}
1295
1296
bool Curl_secure_context(struct Curl_easy *data, const char *host)
1297
34.3k
{
1298
34.3k
  return Curl_xfer_is_secure(data) ||
1299
34.3k
    curl_strequal("localhost", host) ||
1300
34.3k
    !strcmp(host, "127.0.0.1") ||
1301
33.2k
    !strcmp(host, "::1");
1302
34.3k
}
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
22.1k
{
1319
22.1k
  size_t matches = 0;
1320
22.1k
  const bool is_ip = Curl_host_is_ipnum(host);
1321
22.1k
  const size_t myhash = cookiehash(host);
1322
22.1k
  struct Curl_llist_node *n;
1323
22.1k
  const bool secure = Curl_secure_context(data, host);
1324
22.1k
  struct CookieInfo *ci = data->cookies;
1325
22.1k
  const char *path = data->state.up.path;
1326
22.1k
  CURLcode result = CURLE_OK;
1327
22.1k
  *okay = FALSE;
1328
1329
22.1k
  Curl_llist_init(list, NULL);
1330
1331
22.1k
  if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
1332
21.2k
    return CURLE_OK; /* no cookie struct or no cookies in the struct */
1333
1334
  /* at first, remove expired cookies */
1335
903
  remove_expired(ci);
1336
1337
3.57k
  for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
1338
2.67k
    struct Cookie *co = Curl_node_elem(n);
1339
1340
    /* if the cookie requires we are secure we must only continue if we are! */
1341
2.67k
    if(co->secure ? secure : TRUE) {
1342
1343
      /* now check if the domain is correct */
1344
2.64k
      if(!co->domain ||
1345
2.41k
         (co->tailmatch && !is_ip &&
1346
74
          cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1347
2.52k
         ((!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
2.52k
        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
2.23k
          Curl_llist_append(list, co, &co->getnode);
1363
2.23k
          matches++;
1364
2.23k
          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
2.23k
        }
1370
2.52k
      }
1371
2.64k
    }
1372
2.67k
  }
1373
1374
903
  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
795
    struct Cookie **array;
1381
795
    size_t i;
1382
1383
    /* alloc an array and store all cookie pointers */
1384
795
    array = curlx_malloc(sizeof(struct Cookie *) * matches);
1385
795
    if(!array) {
1386
0
      result = CURLE_OUT_OF_MEMORY;
1387
0
      goto fail;
1388
0
    }
1389
1390
795
    n = Curl_llist_head(list);
1391
1392
3.03k
    for(i = 0; n; n = Curl_node_next(n))
1393
2.23k
      array[i++] = Curl_node_elem(n);
1394
1395
    /* now sort the cookie pointers in path length order */
1396
795
    qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1397
1398
    /* remake the linked list order according to the new order */
1399
795
    Curl_llist_destroy(list, NULL);
1400
1401
3.03k
    for(i = 0; i < matches; i++)
1402
2.23k
      Curl_llist_append(list, array[i], &array[i]->getnode);
1403
1404
795
    curlx_free(array); /* remove the temporary data again */
1405
795
  }
1406
1407
903
  *okay = TRUE;
1408
903
  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
903
}
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
25.9k
{
1423
25.9k
  if(ci) {
1424
25.9k
    unsigned int i;
1425
1.65M
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1426
1.63M
      struct Curl_llist_node *n;
1427
1.63M
      for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
1428
4.31k
        struct Cookie *c = Curl_node_elem(n);
1429
4.31k
        struct Curl_llist_node *e = Curl_node_next(n);
1430
4.31k
        Curl_node_remove(n);
1431
4.31k
        freecookie(c, TRUE);
1432
4.31k
        n = e;
1433
4.31k
      }
1434
1.63M
    }
1435
25.9k
    ci->numcookies = 0;
1436
25.9k
  }
1437
25.9k
}
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
1
{
1446
1
  unsigned int i;
1447
1448
1
  if(!ci)
1449
1
    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
25.9k
{
1474
25.9k
  if(ci) {
1475
25.9k
    Curl_cookie_clearall(ci);
1476
25.9k
    curlx_free(ci); /* free the base struct as well */
1477
25.9k
  }
1478
25.9k
}
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.92k
{
1489
2.92k
  return curl_maprintf(
1490
2.92k
    "%s"               /* httponly preamble */
1491
2.92k
    "%s%s\t"           /* domain */
1492
2.92k
    "%s\t"             /* tailmatch */
1493
2.92k
    "%s\t"             /* path */
1494
2.92k
    "%s\t"             /* secure */
1495
2.92k
    "%" FMT_OFF_T "\t" /* expires */
1496
2.92k
    "%s\t"             /* name */
1497
2.92k
    "%s",              /* value */
1498
2.92k
    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.92k
    (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
1504
2.92k
    co->domain ? co->domain : "unknown",
1505
2.92k
    co->tailmatch ? "TRUE" : "FALSE",
1506
2.92k
    co->path ? co->path : "/",
1507
2.92k
    co->secure ? "TRUE" : "FALSE",
1508
2.92k
    co->expires,
1509
2.92k
    co->name,
1510
2.92k
    co->value ? co->value : "");
1511
2.92k
}
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
23.1k
{
1525
23.1k
  FILE *out = NULL;
1526
23.1k
  bool use_stdout = FALSE;
1527
23.1k
  char *tempstore = NULL;
1528
23.1k
  CURLcode result = CURLE_OK;
1529
1530
23.1k
  if(!ci)
1531
    /* no cookie engine alive */
1532
0
    return CURLE_OK;
1533
1534
  /* at first, remove expired cookies */
1535
23.1k
  remove_expired(ci);
1536
1537
23.1k
  if(!strcmp("-", filename)) {
1538
    /* use stdout */
1539
0
    out = stdout;
1540
0
    use_stdout = TRUE;
1541
0
  }
1542
23.1k
  else {
1543
23.1k
    result = Curl_fopen(data, filename, &out, &tempstore);
1544
23.1k
    if(result)
1545
0
      goto error;
1546
23.1k
  }
1547
1548
23.1k
  fputs("# Netscape HTTP Cookie File\n"
1549
23.1k
        "# https://curl.se/docs/http-cookies.html\n"
1550
23.1k
        "# This file was generated by libcurl! Edit at your own risk.\n\n",
1551
23.1k
        out);
1552
1553
23.1k
  if(ci->numcookies) {
1554
1.22k
    unsigned int i;
1555
1.22k
    size_t nvalid = 0;
1556
1.22k
    struct Cookie **array;
1557
1.22k
    struct Curl_llist_node *n;
1558
1559
1.22k
    array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies);
1560
1.22k
    if(!array) {
1561
0
      result = CURLE_OUT_OF_MEMORY;
1562
0
      goto error;
1563
0
    }
1564
1565
    /* only sort the cookies with a domain property */
1566
78.2k
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1567
80.3k
      for(n = Curl_llist_head(&ci->cookielist[i]); n; n = Curl_node_next(n)) {
1568
3.38k
        struct Cookie *co = Curl_node_elem(n);
1569
3.38k
        if(!co->domain)
1570
465
          continue;
1571
2.92k
        array[nvalid++] = co;
1572
2.92k
      }
1573
76.9k
    }
1574
1575
1.22k
    qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1576
1577
4.14k
    for(i = 0; i < nvalid; i++) {
1578
2.92k
      char *format_ptr = get_netscape_format(array[i]);
1579
2.92k
      if(!format_ptr) {
1580
0
        curlx_free(array);
1581
0
        result = CURLE_OUT_OF_MEMORY;
1582
0
        goto error;
1583
0
      }
1584
2.92k
      curl_mfprintf(out, "%s\n", format_ptr);
1585
2.92k
      curlx_free(format_ptr);
1586
2.92k
    }
1587
1588
1.22k
    curlx_free(array);
1589
1.22k
  }
1590
1591
23.1k
  if(!use_stdout) {
1592
23.1k
    curlx_fclose(out);
1593
23.1k
    out = NULL;
1594
23.1k
    if(tempstore && curlx_rename(tempstore, filename)) {
1595
0
      result = CURLE_WRITE_ERROR;
1596
0
      goto error;
1597
0
    }
1598
23.1k
  }
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
23.1k
  curlx_free(tempstore);
1606
23.1k
  return CURLE_OK;
1607
1608
0
error:
1609
0
  if(out && !use_stdout)
1610
0
    curlx_fclose(out);
1611
0
  if(tempstore) {
1612
0
    unlink(tempstore);
1613
0
    curlx_free(tempstore);
1614
0
  }
1615
0
  return result;
1616
23.1k
}
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
53.6k
{
1669
53.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
53.6k
  if(data->cookies) {
1675
25.9k
    const char *cookiejar = CURL_EASY_STR(data, STRING_COOKIEJAR);
1676
25.9k
    if(cookiejar && data->cookies->running) {
1677
      /* if we have a destination file for all the cookies to get dumped to */
1678
23.1k
      CURLcode result = cookie_output(data, data->cookies, cookiejar);
1679
23.1k
      if(result)
1680
0
        infof(data, "WARNING: failed to save cookies in %s: %s",
1681
23.1k
              cookiejar, curl_easy_strerror(result));
1682
23.1k
    }
1683
1684
25.9k
    if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1685
25.9k
      Curl_cookie_cleanup(data->cookies);
1686
25.9k
      data->cookies = NULL;
1687
25.9k
    }
1688
25.9k
  }
1689
53.6k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1690
53.6k
}
1691
1692
void Curl_cookie_run(struct Curl_easy *data)
1693
23.7k
{
1694
23.7k
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1695
23.7k
  if(data->cookies)
1696
23.3k
    data->cookies->running = TRUE;
1697
23.7k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1698
23.7k
}
1699
1700
#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */