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
0
#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
0
{
55
0
  if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {
56
0
    timediff_t cap = now + COOKIES_MAXAGE;
57
0
    if(co->expires > cap) {
58
0
      cap += 30;
59
0
      co->expires = (cap / 60) * 60;
60
0
    }
61
0
  }
62
0
}
63
64
static void freecookie(struct Cookie *co, bool maintoo)
65
10.5k
{
66
10.5k
  curlx_free(co->domain);
67
10.5k
  curlx_free(co->path);
68
10.5k
  curlx_free(co->name);
69
10.5k
  curlx_free(co->value);
70
10.5k
  if(maintoo)
71
0
    curlx_free(co);
72
10.5k
}
73
74
static bool cookie_tailmatch(const char *cookie_domain,
75
                             const size_t cookie_domain_len,
76
                             const char *hostname)
77
0
{
78
0
  size_t hostname_len = strlen(hostname);
79
80
0
  if(hostname_len < cookie_domain_len)
81
0
    return FALSE;
82
83
0
  if(!curl_strnequal(cookie_domain,
84
0
                     hostname + hostname_len - cookie_domain_len,
85
0
                     cookie_domain_len))
86
0
    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
0
  if(hostname_len == cookie_domain_len)
97
0
    return TRUE;
98
0
  if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
99
0
    return TRUE;
100
0
  return FALSE;
101
0
}
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
0
{
164
0
  size_t len = 0;
165
0
  const char *first = NULL, *last;
166
167
0
  if(domain) {
168
0
    len = strlen(domain);
169
0
    last = memrchr(domain, '.', len);
170
0
    if(last) {
171
0
      first = memrchr(domain, '.', (last - domain));
172
0
      if(first)
173
0
        len -= (++first - domain);
174
0
    }
175
0
  }
176
177
0
  if(outlen)
178
0
    *outlen = len;
179
180
0
  return first ? first : domain;
181
0
}
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
0
{
193
0
  const char *end = domain + len;
194
0
  size_t h = 5381;
195
196
0
  while(domain < end) {
197
0
    size_t j = (size_t)Curl_raw_toupper(*domain++);
198
0
    h += h << 5;
199
0
    h ^= j;
200
0
  }
201
202
0
  return (h % COOKIE_HASH_SIZE);
203
0
}
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
0
{
214
0
  const char *top;
215
0
  size_t len;
216
217
0
  if(!domain || Curl_host_is_ipnum(domain))
218
0
    return 0;
219
220
0
  top = get_top_domain(domain, &len);
221
0
  return cookie_hash_domain(top, len);
222
0
}
223
224
/*
225
 * cookie path sanitize
226
 */
227
static char *sanitize_cookie_path(const char *cookie_path, size_t len)
228
0
{
229
  /* some sites send path attribute within '"'. */
230
0
  if(len && (cookie_path[0] == '\"')) {
231
0
    cookie_path++;
232
0
    len--;
233
234
0
    if(len && (cookie_path[len - 1] == '\"'))
235
0
      len--;
236
0
  }
237
238
  /* RFC6265 5.2.4 The Path Attribute */
239
0
  if(!len || (cookie_path[0] != '/'))
240
    /* Let cookie-path be the default-path. */
241
0
    return curlx_strdup("/");
242
243
  /* remove trailing slash when path is non-empty */
244
  /* convert /hoge/ to /hoge */
245
0
  if(len > 1 && cookie_path[len - 1] == '/')
246
0
    len--;
247
248
0
  return curlx_memdup0(cookie_path, len);
249
0
}
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
0
{
258
0
  DEBUGASSERT(str);
259
0
  *str = curlx_memdup0(newstr, len);
260
0
  if(!*str)
261
0
    return CURLE_OUT_OF_MEMORY;
262
0
  return CURLE_OK;
263
0
}
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
19.2k
{
276
19.2k
  struct Cookie *co;
277
19.2k
  curl_off_t now = (curl_off_t)time(NULL);
278
19.2k
  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
19.2k
  if(now < ci->next_expiration &&
289
19.2k
     ci->next_expiration != CURL_OFF_T_MAX)
290
0
    return;
291
19.2k
  else
292
19.2k
    ci->next_expiration = CURL_OFF_T_MAX;
293
294
1.23M
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
295
1.21M
    struct Curl_llist_node *n;
296
1.21M
    struct Curl_llist_node *e = NULL;
297
298
1.21M
    for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {
299
0
      co = Curl_node_elem(n);
300
0
      e = Curl_node_next(n);
301
0
      if(co->expires) {
302
0
        if(co->expires < now) {
303
0
          Curl_node_remove(n);
304
0
          freecookie(co, TRUE);
305
0
          ci->numcookies--;
306
0
        }
307
0
        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
0
          ci->next_expiration = co->expires;
313
0
      }
314
0
    }
315
1.21M
  }
316
19.2k
}
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
0
{
348
0
  const unsigned char *p = (const unsigned char *)ptr;
349
  /* Reject all bytes \x01 - \x1f + \x7f */
350
0
  while(len && *p) {
351
0
    if((*p < 0x20) || (*p == 0x7f))
352
0
      return TRUE;
353
0
    p++;
354
0
    len--;
355
0
  }
356
0
  return FALSE;
357
0
}
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
0
#define MAX_DATE_LENGTH 80
363
364
0
#define COOKIE_NAME   0
365
0
#define COOKIE_VALUE  1
366
0
#define COOKIE_DOMAIN 2
367
0
#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
0
{
374
0
  CURLcode result;
375
0
  result = strstore(&co->name, curlx_str(&cp[COOKIE_NAME]),
376
0
                    curlx_strlen(&cp[COOKIE_NAME]));
377
0
  if(!result)
378
0
    result = strstore(&co->value, curlx_str(&cp[COOKIE_VALUE]),
379
0
                      curlx_strlen(&cp[COOKIE_VALUE]));
380
0
  if(!result) {
381
0
    size_t plen = 0;
382
0
    if(curlx_strlen(&cp[COOKIE_PATH])) {
383
0
      path = curlx_str(&cp[COOKIE_PATH]);
384
0
      plen = curlx_strlen(&cp[COOKIE_PATH]);
385
0
    }
386
0
    else if(path) {
387
      /* No path was given in the header line, set the default */
388
0
      const char *endslash = strrchr(path, '/');
389
0
      if(endslash)
390
0
        plen = endslash - path + 1; /* include end slash */
391
0
      else
392
0
        plen = strlen(path);
393
0
    }
394
395
0
    if(path) {
396
0
      co->path = sanitize_cookie_path(path, plen);
397
0
      if(!co->path)
398
0
        result = CURLE_OUT_OF_MEMORY;
399
0
    }
400
0
  }
401
0
  if(!result) {
402
0
    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
0
    else if(domain) {
406
      /* no domain was given in the header line, set the default */
407
0
      co->domain = curlx_strdup(domain);
408
0
      if(!co->domain)
409
0
        result = CURLE_OUT_OF_MEMORY;
410
0
    }
411
0
  }
412
0
  return result;
413
0
}
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
0
{
425
  /* The first name/value pair is the actual cookie name */
426
0
  if(!sep || !curlx_strlen(name)) {
427
0
    infof(data, "invalid cookie, dropped");
428
0
    return FALSE;
429
0
  }
430
431
  /*
432
   * Check for too long individual name or contents. Chrome and Firefox
433
   * support 4095 or 4096 bytes combo
434
   */
435
0
  if((curlx_strlen(name) + curlx_strlen(val)) > MAX_NAME) {
436
0
    infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
437
0
          curlx_strlen(name), curlx_strlen(val));
438
0
    return FALSE;
439
0
  }
440
441
  /* Check if we have a reserved prefix set. */
442
0
  if(!strncmp("__Secure-", curlx_str(name), 9))
443
0
    co->prefix_secure = TRUE;
444
0
  else if(!strncmp("__Host-", curlx_str(name), 7))
445
0
    co->prefix_host = TRUE;
446
447
0
  cookie[COOKIE_NAME] = *name;
448
0
  cookie[COOKIE_VALUE] = *val;
449
0
  return TRUE;
450
0
}
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
0
{
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
0
  if(!co->expires && (curlx_strlen(val) < MAX_DATE_LENGTH)) {
547
0
    char dbuf[MAX_DATE_LENGTH + 1];
548
0
    time_t date = 0;
549
0
    memcpy(dbuf, curlx_str(val), curlx_strlen(val));
550
0
    dbuf[curlx_strlen(val)] = 0;
551
0
    if(!Curl_getdate_capped(dbuf, &date)) {
552
0
      if(!date)
553
0
        date++;
554
0
      co->expires = (curl_off_t)date;
555
0
    }
556
0
    else
557
0
      co->expires = 0;
558
0
    if(!*nowp)
559
0
      *nowp = time(NULL);
560
0
    cap_expires(*nowp, co);
561
0
  }
562
0
}
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
0
{
575
  /* This line was read off an HTTP-header */
576
0
  time_t now = 0;
577
0
  size_t linelength = strlen(ptr);
578
0
  CURLcode result = CURLE_OK;
579
0
  struct Curl_str cookie[COOKIE_PIECES];
580
0
  *okay = FALSE;
581
0
  if(linelength > MAX_COOKIE_LINE)
582
    /* discard overly long lines at once */
583
0
    return CURLE_OK;
584
585
  /* memset instead of initializer because gcc 4.8.1 is silly */
586
0
  memset(cookie, 0, sizeof(cookie));
587
0
  do {
588
0
    struct Curl_str name;
589
590
    /* we have a <name>=<value> pair or a stand-alone word here */
591
0
    int rc = curlx_str_cspn(&ptr, &name, ";\r\n=");
592
0
    if(!rc || (rc == STRE_SHORT)) {
593
0
      struct Curl_str val;
594
0
      bool sep = FALSE;
595
0
      curlx_str_trimblanks(&name);
596
597
0
      if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) {
598
0
        infof(data, "invalid octets in name, cookie dropped");
599
0
        return CURLE_OK;
600
0
      }
601
602
0
      if(!curlx_str_single(&ptr, '=')) {
603
0
        sep = TRUE; /* a '=' was used */
604
0
        if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
605
0
          curlx_str_trimblanks(&val);
606
607
0
        if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) {
608
0
          infof(data, "invalid octets in value, cookie dropped");
609
0
          return CURLE_OK;
610
0
        }
611
0
      }
612
0
      else
613
0
        curlx_str_init(&val);
614
615
0
      if(!curlx_strlen(&cookie[COOKIE_NAME])) {
616
0
        if(!parse_first_pair(data, co, cookie, &name, &val, sep))
617
0
          return CURLE_OK;
618
0
      }
619
620
      /* whether there's separator or not */
621
0
      else if(curlx_str_casecompare(&name, "secure")) {
622
0
        if(secure_origin)
623
0
          co->secure = TRUE;
624
0
        else {
625
0
          infof(data, "skipped cookie because not 'secure'");
626
0
          return CURLE_OK;
627
0
        }
628
0
      }
629
0
      else if(curlx_str_casecompare(&name, "httponly"))
630
0
        co->httponly = TRUE;
631
0
      else if(sep) {
632
        /* only if a separating equals sign is present */
633
0
        if(curlx_str_casecompare(&name, "path"))
634
0
          cookie[COOKIE_PATH] = val;
635
0
        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
0
        else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val))
641
0
          parse_maxage(co, &val, &now);
642
0
        else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val))
643
0
          parse_expires(co, &val, &now);
644
0
      }
645
0
    }
646
0
  } while(!curlx_str_single(&ptr, ';'));
647
648
0
  if(curlx_strlen(&cookie[COOKIE_NAME])) {
649
    /* the header was fine, now store the data */
650
0
    result = storecookie(co, &cookie[0], path, domain);
651
0
    if(!result)
652
0
      *okay = TRUE;
653
0
  }
654
0
  return result;
655
0
}
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
10.5k
{
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
10.5k
  const char *ptr, *next;
668
10.5k
  int fields;
669
10.5k
  size_t len;
670
10.5k
  *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
10.5k
  if(!strncmp(lineptr, "#HttpOnly_", 10)) {
679
0
    lineptr += 10;
680
0
    co->httponly = TRUE;
681
0
  }
682
683
10.5k
  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
10.5k
  fields = 0;
692
21.1k
  for(next = lineptr; next; fields++) {
693
10.5k
    ptr = next;
694
10.5k
    len = strcspn(ptr, "\t\r\n");
695
10.5k
    next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);
696
10.5k
    switch(fields) {
697
10.5k
    case 0:
698
10.5k
      if(ptr[0] == '.') { /* skip preceding dots */
699
0
        ptr++;
700
0
        len--;
701
0
      }
702
10.5k
      co->domain = curlx_memdup0(ptr, len);
703
10.5k
      if(!co->domain)
704
8
        return CURLE_OUT_OF_MEMORY;
705
10.5k
      break;
706
10.5k
    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
10.5k
    }
764
10.5k
  }
765
10.5k
  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
10.5k
  if(fields != 7)
775
    /* we did not find the sufficient number of fields */
776
10.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
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
0
{
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
0
  (void)data;
861
0
  (void)co;
862
0
  (void)domain;
863
0
  DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",
864
0
               co->name, co->domain, domain ? domain : "[file]"));
865
0
#endif
866
0
  return FALSE;
867
0
}
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
0
{
876
0
  bool replace_old = FALSE;
877
0
  struct Curl_llist_node *replace_n = NULL;
878
0
  struct Curl_llist_node *n;
879
0
  size_t myhash = cookiehash(co->domain);
880
0
  for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
881
0
    struct Cookie *clist = Curl_node_elem(n);
882
0
    if(!strcmp(clist->name, co->name)) {
883
      /* the names are identical */
884
0
      bool matching_domains = FALSE;
885
886
0
      if(clist->domain && co->domain) {
887
0
        if(cookie_tailmatch(clist->domain, strlen(clist->domain),
888
0
                            co->domain) ||
889
0
           cookie_tailmatch(co->domain, strlen(co->domain), clist->domain))
890
          /* The existing one is a tail of the new or vice versa */
891
0
          matching_domains = TRUE;
892
0
      }
893
0
      else if(!clist->domain && !co->domain)
894
0
        matching_domains = TRUE;
895
896
0
      if(matching_domains && /* the domains were identical */
897
0
         clist->path && co->path && /* both have paths */
898
0
         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
0
    }
924
925
0
    if(!replace_n && !strcmp(clist->name, co->name)) {
926
      /* the names are identical */
927
928
0
      if(clist->domain && co->domain) {
929
0
        if(curl_strequal(clist->domain, co->domain) &&
930
0
           (clist->tailmatch == co->tailmatch))
931
          /* The domains are identical */
932
0
          replace_old = TRUE;
933
0
      }
934
0
      else if(!clist->domain && !co->domain)
935
0
        replace_old = TRUE;
936
937
0
      if(replace_old) {
938
        /* the domains were identical */
939
940
0
        if(clist->path && co->path &&
941
0
           strcmp(clist->path, co->path))
942
0
          replace_old = FALSE;
943
0
        else if(!clist->path != !co->path)
944
0
          replace_old = FALSE;
945
0
      }
946
947
0
      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
0
      if(replace_old)
957
0
        replace_n = n;
958
0
    }
959
0
  }
960
0
  if(replace_n) {
961
0
    struct Cookie *repl = Curl_node_elem(replace_n);
962
963
    /* when replacing, creationtime is kept from old */
964
0
    co->creationtime = repl->creationtime;
965
966
    /* unlink the old */
967
0
    Curl_node_remove(replace_n);
968
969
    /* free the old cookie */
970
0
    freecookie(repl, TRUE);
971
0
  }
972
0
  *replacep = replace_old;
973
0
  return TRUE;
974
0
}
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
10.5k
{
994
10.5k
  struct Cookie comem;
995
10.5k
  struct Cookie *co;
996
10.5k
  size_t myhash;
997
10.5k
  CURLcode result;
998
10.5k
  bool replaces = FALSE;
999
10.5k
  bool okay;
1000
1001
10.5k
  DEBUGASSERT(data);
1002
10.5k
  DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
1003
10.5k
  if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
1004
0
    return CURLE_OK; /* silently ignore */
1005
1006
10.5k
  co = &comem;
1007
10.5k
  memset(co, 0, sizeof(comem));
1008
1009
10.5k
  if(flags & COOKIE_HTTPHEADER)
1010
0
    result = parse_cookie_header(data, co, &okay,
1011
0
                                 lineptr, domain, path, flags & COOKIE_SECURE);
1012
10.5k
  else
1013
10.5k
    result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE);
1014
1015
10.5k
  if(result || !okay)
1016
10.5k
    goto fail;
1017
1018
0
  if(co->prefix_secure && !co->secure)
1019
    /* The __Secure- prefix only requires that the cookie be set secure */
1020
0
    goto fail;
1021
1022
0
  if(!(flags & COOKIE_NOPSL) && is_public_suffix(data, co, domain))
1023
0
    goto fail;
1024
1025
0
  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
0
    if(co->secure && co->path && !strcmp(co->path, "/") && !co->tailmatch)
1031
0
      ;
1032
0
    else
1033
0
      goto fail;
1034
0
  }
1035
1036
0
  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
0
  co->livecookie = ci->running;
1042
0
  co->creationtime = ++ci->lastct;
1043
1044
0
  if(!(flags & COOKIE_NOEXPIRE))
1045
0
    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
0
  if(!replace_existing(data, co, ci, flags & COOKIE_SECURE, &replaces))
1053
0
    goto fail;
1054
1055
  /* clone the stack struct into heap */
1056
0
  co = curlx_memdup(&comem, sizeof(comem));
1057
0
  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
0
  myhash = cookiehash(co->domain);
1065
0
  Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
1066
1067
0
  if(ci->running)
1068
    /* Only show this when NOT reading the cookies from a file */
1069
0
    infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1070
0
          "expire %" FMT_OFF_T,
1071
0
          replaces ? "Replaced" : "Added", co->name, co->value,
1072
0
          co->domain, co->path, co->expires);
1073
1074
0
  if(!replaces)
1075
0
    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
0
  if(co->expires && (co->expires < ci->next_expiration))
1082
0
    ci->next_expiration = co->expires;
1083
1084
0
  if(flags & COOKIE_HTTPHEADER)
1085
0
    data->req.setcookies++;
1086
1087
0
  return result;
1088
10.5k
fail:
1089
10.5k
  freecookie(co, FALSE);
1090
10.5k
  return result;
1091
0
}
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
8.80k
{
1109
8.80k
  int i;
1110
8.80k
  struct CookieInfo *ci = curlx_calloc(1, sizeof(struct CookieInfo));
1111
8.80k
  if(!ci)
1112
12
    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
562k
  for(i = 0; i < COOKIE_HASH_SIZE; i++)
1117
553k
    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
8.79k
  ci->next_expiration = CURL_OFF_T_MAX;
1123
1124
8.79k
  return ci;
1125
8.80k
}
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
10.5k
{
1139
10.5k
  FILE *handle = NULL;
1140
10.5k
  CURLcode result = CURLE_OK;
1141
10.5k
  FILE *fp = NULL;
1142
10.5k
  DEBUGASSERT(ci);
1143
10.5k
  DEBUGASSERT(data);
1144
10.5k
  DEBUGASSERT(file);
1145
1146
10.5k
  ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */
1147
10.5k
  ci->running = FALSE; /* this is not running, this is init */
1148
1149
10.5k
  if(file && *file) {
1150
10.5k
    if(!strcmp(file, "-"))
1151
0
      fp = stdin;
1152
10.5k
    else {
1153
10.5k
      fp = curlx_fopen(file, "rb");
1154
10.5k
      if(!fp)
1155
0
        infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1156
10.5k
      else {
1157
10.5k
        curlx_struct_stat stat;
1158
10.5k
        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
10.5k
        else
1165
10.5k
          handle = fp;
1166
10.5k
      }
1167
10.5k
    }
1168
10.5k
  }
1169
1170
10.5k
  if(fp) {
1171
10.5k
    struct dynbuf buf;
1172
10.5k
    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
10.5k
    curlx_dyn_init(&buf, DYN_APRINTF + 2);
1178
10.5k
    do {
1179
10.5k
      result = Curl_get_line(&buf, fp, &eof);
1180
10.5k
      if(!result) {
1181
10.5k
        const char *lineptr = curlx_dyn_ptr(&buf);
1182
10.5k
        bool headerline = FALSE;
1183
10.5k
        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
10.5k
        result = Curl_cookie_add(data, ci, lineptr, NULL, NULL,
1191
10.5k
                                 (headerline ? COOKIE_HTTPHEADER : 0) |
1192
10.5k
                                 COOKIE_NOEXPIRE | COOKIE_SECURE |
1193
10.5k
                                 (flags & COOKIE_NOPSL));
1194
        /* Ignore individual cookie problems unless we ran out of memory */
1195
10.5k
        if(result != CURLE_OUT_OF_MEMORY)
1196
10.5k
          result = CURLE_OK;
1197
10.5k
      }
1198
10.5k
    } while(!result && !eof);
1199
10.5k
    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
10.5k
    remove_expired(ci);
1206
1207
10.5k
    if(handle)
1208
10.5k
      curlx_fclose(handle);
1209
10.5k
  }
1210
10.5k
  data->state.cookie_engine = TRUE;
1211
10.5k
  ci->running = TRUE; /* now, we are running */
1212
1213
10.5k
  return result;
1214
10.5k
}
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
10.6k
{
1222
10.6k
  CURLcode result = CURLE_OK;
1223
10.6k
  struct curl_slist *list = data->state.cookielist;
1224
10.6k
  if(list) {
1225
10.5k
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1226
10.5k
    if(!data->cookies)
1227
25
      data->cookies = Curl_cookie_init();
1228
10.5k
    if(!data->cookies)
1229
1
      result = CURLE_OUT_OF_MEMORY;
1230
10.5k
    else {
1231
10.5k
      data->state.cookie_engine = TRUE;
1232
21.1k
      while(list) {
1233
10.5k
        result = cookie_load(data, list->data, data->cookies, flags);
1234
10.5k
        if(result)
1235
9
          break;
1236
10.5k
        list = list->next;
1237
10.5k
      }
1238
10.5k
    }
1239
10.5k
    Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1240
10.5k
  }
1241
10.6k
  return result;
1242
10.6k
}
1243
1244
/*
1245
 * cookie_sort
1246
 *
1247
 * Helper function to sort cookies such that the longest path gets before the
1248
 * shorter path. Path, domain and name lengths are considered in that order,
1249
 * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1250
 * be unique per cookie, so we know we will get an ordering at that point.
1251
 */
1252
static int cookie_sort(const void *p1, const void *p2)
1253
0
{
1254
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1255
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1256
0
  size_t l1, l2;
1257
1258
  /* 1 - compare cookie path lengths */
1259
0
  l1 = c1->path ? strlen(c1->path) : 0;
1260
0
  l2 = c2->path ? strlen(c2->path) : 0;
1261
1262
0
  if(l1 != l2)
1263
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1264
1265
  /* 2 - compare cookie domain lengths */
1266
0
  l1 = c1->domain ? strlen(c1->domain) : 0;
1267
0
  l2 = c2->domain ? strlen(c2->domain) : 0;
1268
1269
0
  if(l1 != l2)
1270
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1271
1272
  /* 3 - compare cookie name lengths */
1273
0
  l1 = c1->name ? strlen(c1->name) : 0;
1274
0
  l2 = c2->name ? strlen(c2->name) : 0;
1275
1276
0
  if(l1 != l2)
1277
0
    return (l2 > l1) ? 1 : -1;
1278
1279
  /* 4 - compare cookie creation time */
1280
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1281
0
}
1282
1283
/*
1284
 * cookie_sort_ct
1285
 *
1286
 * Helper function to sort cookies according to creation time.
1287
 */
1288
static int cookie_sort_ct(const void *p1, const void *p2)
1289
0
{
1290
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1291
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1292
1293
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1294
0
}
1295
1296
bool Curl_secure_context(struct Curl_easy *data, const char *host)
1297
0
{
1298
0
  return Curl_xfer_is_secure(data) ||
1299
0
    curl_strequal("localhost", host) ||
1300
0
    !strcmp(host, "127.0.0.1") ||
1301
0
    !strcmp(host, "::1");
1302
0
}
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
0
{
1319
0
  size_t matches = 0;
1320
0
  const bool is_ip = Curl_host_is_ipnum(host);
1321
0
  const size_t myhash = cookiehash(host);
1322
0
  struct Curl_llist_node *n;
1323
0
  const bool secure = Curl_secure_context(data, host);
1324
0
  struct CookieInfo *ci = data->cookies;
1325
0
  const char *path = data->state.up.path;
1326
0
  CURLcode result = CURLE_OK;
1327
0
  *okay = FALSE;
1328
1329
0
  Curl_llist_init(list, NULL);
1330
1331
0
  if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
1332
0
    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
8.79k
{
1423
8.79k
  if(ci) {
1424
8.79k
    unsigned int i;
1425
562k
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1426
553k
      struct Curl_llist_node *n;
1427
553k
      for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
1428
0
        struct Cookie *c = Curl_node_elem(n);
1429
0
        struct Curl_llist_node *e = Curl_node_next(n);
1430
0
        Curl_node_remove(n);
1431
0
        freecookie(c, TRUE);
1432
0
        n = e;
1433
0
      }
1434
553k
    }
1435
8.79k
    ci->numcookies = 0;
1436
8.79k
  }
1437
8.79k
}
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
8.79k
{
1474
8.79k
  if(ci) {
1475
8.79k
    Curl_cookie_clearall(ci);
1476
8.79k
    curlx_free(ci); /* free the base struct as well */
1477
8.79k
  }
1478
8.79k
}
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
0
{
1489
0
  return curl_maprintf(
1490
0
    "%s"               /* httponly preamble */
1491
0
    "%s%s\t"           /* domain */
1492
0
    "%s\t"             /* tailmatch */
1493
0
    "%s\t"             /* path */
1494
0
    "%s\t"             /* secure */
1495
0
    "%" FMT_OFF_T "\t" /* expires */
1496
0
    "%s\t"             /* name */
1497
0
    "%s",              /* value */
1498
0
    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
0
    (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
1504
0
    co->domain ? co->domain : "unknown",
1505
0
    co->tailmatch ? "TRUE" : "FALSE",
1506
0
    co->path ? co->path : "/",
1507
0
    co->secure ? "TRUE" : "FALSE",
1508
0
    co->expires,
1509
0
    co->name,
1510
0
    co->value ? co->value : "");
1511
0
}
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
8.71k
{
1525
8.71k
  FILE *out = NULL;
1526
8.71k
  bool use_stdout = FALSE;
1527
8.71k
  char *tempstore = NULL;
1528
8.71k
  CURLcode result = CURLE_OK;
1529
1530
8.71k
  if(!ci)
1531
    /* no cookie engine alive */
1532
0
    return CURLE_OK;
1533
1534
  /* at first, remove expired cookies */
1535
8.71k
  remove_expired(ci);
1536
1537
8.71k
  if(!strcmp("-", filename)) {
1538
    /* use stdout */
1539
0
    out = stdout;
1540
0
    use_stdout = TRUE;
1541
0
  }
1542
8.71k
  else {
1543
8.71k
    result = Curl_fopen(data, filename, &out, &tempstore);
1544
8.71k
    if(result)
1545
0
      goto error;
1546
8.71k
  }
1547
1548
8.71k
  fputs("# Netscape HTTP Cookie File\n"
1549
8.71k
        "# https://curl.se/docs/http-cookies.html\n"
1550
8.71k
        "# This file was generated by libcurl! Edit at your own risk.\n\n",
1551
8.71k
        out);
1552
1553
8.71k
  if(ci->numcookies) {
1554
0
    unsigned int i;
1555
0
    size_t nvalid = 0;
1556
0
    struct Cookie **array;
1557
0
    struct Curl_llist_node *n;
1558
1559
0
    array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies);
1560
0
    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
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1567
0
      for(n = Curl_llist_head(&ci->cookielist[i]); n; n = Curl_node_next(n)) {
1568
0
        struct Cookie *co = Curl_node_elem(n);
1569
0
        if(!co->domain)
1570
0
          continue;
1571
0
        array[nvalid++] = co;
1572
0
      }
1573
0
    }
1574
1575
0
    qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1576
1577
0
    for(i = 0; i < nvalid; i++) {
1578
0
      char *format_ptr = get_netscape_format(array[i]);
1579
0
      if(!format_ptr) {
1580
0
        curlx_free(array);
1581
0
        result = CURLE_OUT_OF_MEMORY;
1582
0
        goto error;
1583
0
      }
1584
0
      curl_mfprintf(out, "%s\n", format_ptr);
1585
0
      curlx_free(format_ptr);
1586
0
    }
1587
1588
0
    curlx_free(array);
1589
0
  }
1590
1591
8.71k
  if(!use_stdout) {
1592
8.71k
    curlx_fclose(out);
1593
8.71k
    out = NULL;
1594
8.71k
    if(tempstore && curlx_rename(tempstore, filename)) {
1595
0
      result = CURLE_WRITE_ERROR;
1596
0
      goto error;
1597
0
    }
1598
8.71k
  }
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
8.71k
  curlx_free(tempstore);
1606
8.71k
  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
8.71k
}
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
17.6k
{
1669
17.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
17.6k
  if(data->cookies) {
1675
8.79k
    const char *cookiejar = CURL_EASY_STR(data, STRING_COOKIEJAR);
1676
8.79k
    if(cookiejar && data->cookies->running) {
1677
      /* if we have a destination file for all the cookies to get dumped to */
1678
8.71k
      CURLcode result = cookie_output(data, data->cookies, cookiejar);
1679
8.71k
      if(result)
1680
0
        infof(data, "WARNING: failed to save cookies in %s: %s",
1681
8.71k
              cookiejar, curl_easy_strerror(result));
1682
8.71k
    }
1683
1684
8.79k
    if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1685
8.79k
      Curl_cookie_cleanup(data->cookies);
1686
8.79k
      data->cookies = NULL;
1687
8.79k
    }
1688
8.79k
  }
1689
17.6k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1690
17.6k
}
1691
1692
void Curl_cookie_run(struct Curl_easy *data)
1693
10.6k
{
1694
10.6k
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1695
10.6k
  if(data->cookies)
1696
10.5k
    data->cookies->running = TRUE;
1697
10.6k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1698
10.6k
}
1699
1700
#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */