Coverage Report

Created: 2026-10-22 06:48

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
2.22k
{
66
2.22k
  curlx_free(co->domain);
67
2.22k
  curlx_free(co->path);
68
2.22k
  curlx_free(co->name);
69
2.22k
  curlx_free(co->value);
70
2.22k
  if(maintoo)
71
0
    curlx_free(co);
72
2.22k
}
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
4.45k
{
276
4.45k
  struct Cookie *co;
277
4.45k
  curl_off_t now = (curl_off_t)time(NULL);
278
4.45k
  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
4.45k
  if(now < ci->next_expiration &&
289
4.45k
     ci->next_expiration != CURL_OFF_T_MAX)
290
0
    return;
291
4.45k
  else
292
4.45k
    ci->next_expiration = CURL_OFF_T_MAX;
293
294
284k
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
295
280k
    struct Curl_llist_node *n;
296
280k
    struct Curl_llist_node *e = NULL;
297
298
280k
    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
280k
  }
316
4.45k
}
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
2.22k
{
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
2.22k
  const char *ptr, *next;
668
2.22k
  int fields;
669
2.22k
  size_t len;
670
2.22k
  *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
2.22k
  if(!strncmp(lineptr, "#HttpOnly_", 10)) {
679
0
    lineptr += 10;
680
0
    co->httponly = TRUE;
681
0
  }
682
683
2.22k
  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
2.22k
  fields = 0;
692
4.44k
  for(next = lineptr; next; fields++) {
693
2.22k
    ptr = next;
694
2.22k
    len = strcspn(ptr, "\t\r\n");
695
2.22k
    next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);
696
2.22k
    switch(fields) {
697
2.22k
    case 0:
698
2.22k
      if(ptr[0] == '.') { /* skip preceding dots */
699
0
        ptr++;
700
0
        len--;
701
0
      }
702
2.22k
      co->domain = curlx_memdup0(ptr, len);
703
2.22k
      if(!co->domain)
704
4
        return CURLE_OUT_OF_MEMORY;
705
2.21k
      break;
706
2.21k
    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
2.22k
    }
764
2.22k
  }
765
2.21k
  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
2.21k
  if(fields != 7)
775
    /* we did not find the sufficient number of fields */
776
2.21k
    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
2.22k
{
994
2.22k
  struct Cookie comem;
995
2.22k
  struct Cookie *co;
996
2.22k
  size_t myhash;
997
2.22k
  CURLcode result;
998
2.22k
  bool replaces = FALSE;
999
2.22k
  bool okay;
1000
1001
2.22k
  DEBUGASSERT(data);
1002
2.22k
  DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
1003
2.22k
  if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
1004
0
    return CURLE_OK; /* silently ignore */
1005
1006
2.22k
  co = &comem;
1007
2.22k
  memset(co, 0, sizeof(comem));
1008
1009
2.22k
  if(flags & COOKIE_HTTPHEADER)
1010
0
    result = parse_cookie_header(data, co, &okay,
1011
0
                                 lineptr, domain, path, flags & COOKIE_SECURE);
1012
2.22k
  else
1013
2.22k
    result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE);
1014
1015
2.22k
  if(result || !okay)
1016
2.22k
    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
2.22k
fail:
1089
2.22k
  freecookie(co, FALSE);
1090
2.22k
  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
2.41k
{
1109
2.41k
  int i;
1110
2.41k
  struct CookieInfo *ci = curlx_calloc(1, sizeof(struct CookieInfo));
1111
2.41k
  if(!ci)
1112
11
    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
153k
  for(i = 0; i < COOKIE_HASH_SIZE; i++)
1117
151k
    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
2.40k
  ci->next_expiration = CURL_OFF_T_MAX;
1123
1124
2.40k
  return ci;
1125
2.41k
}
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
2.22k
{
1139
2.22k
  FILE *handle = NULL;
1140
2.22k
  CURLcode result = CURLE_OK;
1141
2.22k
  FILE *fp = NULL;
1142
2.22k
  DEBUGASSERT(ci);
1143
2.22k
  DEBUGASSERT(data);
1144
2.22k
  DEBUGASSERT(file);
1145
1146
2.22k
  ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */
1147
2.22k
  ci->running = FALSE; /* this is not running, this is init */
1148
1149
2.22k
  if(file && *file) {
1150
2.22k
    if(!strcmp(file, "-"))
1151
0
      fp = stdin;
1152
2.22k
    else {
1153
2.22k
      fp = curlx_fopen(file, "rb");
1154
2.22k
      if(!fp)
1155
0
        infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1156
2.22k
      else {
1157
2.22k
        curlx_struct_stat stat;
1158
2.22k
        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
2.22k
        else
1165
2.22k
          handle = fp;
1166
2.22k
      }
1167
2.22k
    }
1168
2.22k
  }
1169
1170
2.22k
  if(fp) {
1171
2.22k
    struct dynbuf buf;
1172
2.22k
    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
2.22k
    curlx_dyn_init(&buf, DYN_APRINTF + 2);
1178
2.22k
    do {
1179
2.22k
      result = Curl_get_line(&buf, fp, &eof);
1180
2.22k
      if(!result) {
1181
2.22k
        const char *lineptr = curlx_dyn_ptr(&buf);
1182
2.22k
        bool headerline = FALSE;
1183
2.22k
        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
2.22k
        result = Curl_cookie_add(data, ci, lineptr, NULL, NULL,
1191
2.22k
                                 (headerline ? COOKIE_HTTPHEADER : 0) |
1192
2.22k
                                 COOKIE_NOEXPIRE | COOKIE_SECURE |
1193
2.22k
                                 (flags & COOKIE_NOPSL));
1194
        /* Ignore individual cookie problems unless we ran out of memory */
1195
2.22k
        if(result != CURLE_OUT_OF_MEMORY)
1196
2.21k
          result = CURLE_OK;
1197
2.22k
      }
1198
2.22k
    } while(!result && !eof);
1199
2.22k
    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
2.22k
    remove_expired(ci);
1206
1207
2.22k
    if(handle)
1208
2.22k
      curlx_fclose(handle);
1209
2.22k
  }
1210
2.22k
  data->state.cookie_engine = TRUE;
1211
2.22k
  ci->running = TRUE; /* now, we are running */
1212
1213
2.22k
  return result;
1214
2.22k
}
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
2.25k
{
1222
2.25k
  CURLcode result = CURLE_OK;
1223
2.25k
  struct curl_slist *list = data->state.cookielist;
1224
2.25k
  if(list) {
1225
2.22k
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1226
2.22k
    if(!data->cookies)
1227
21
      data->cookies = Curl_cookie_init();
1228
2.22k
    if(!data->cookies)
1229
1
      result = CURLE_OUT_OF_MEMORY;
1230
2.22k
    else {
1231
2.22k
      data->state.cookie_engine = TRUE;
1232
4.44k
      while(list) {
1233
2.22k
        result = cookie_load(data, list->data, data->cookies, flags);
1234
2.22k
        if(result)
1235
7
          break;
1236
2.21k
        list = list->next;
1237
2.21k
      }
1238
2.22k
    }
1239
2.22k
    Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1240
2.22k
  }
1241
2.25k
  return result;
1242
2.25k
}
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
2.40k
{
1423
2.40k
  if(ci) {
1424
2.40k
    unsigned int i;
1425
153k
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1426
151k
      struct Curl_llist_node *n;
1427
151k
      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
151k
    }
1435
2.40k
    ci->numcookies = 0;
1436
2.40k
  }
1437
2.40k
}
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
2.40k
{
1474
2.40k
  if(ci) {
1475
2.40k
    Curl_cookie_clearall(ci);
1476
2.40k
    curlx_free(ci); /* free the base struct as well */
1477
2.40k
  }
1478
2.40k
}
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
2.22k
{
1525
2.22k
  FILE *out = NULL;
1526
2.22k
  bool use_stdout = FALSE;
1527
2.22k
  char *tempstore = NULL;
1528
2.22k
  CURLcode result = CURLE_OK;
1529
1530
2.22k
  if(!ci)
1531
    /* no cookie engine alive */
1532
0
    return CURLE_OK;
1533
1534
  /* at first, remove expired cookies */
1535
2.22k
  remove_expired(ci);
1536
1537
2.22k
  if(!strcmp("-", filename)) {
1538
    /* use stdout */
1539
0
    out = stdout;
1540
0
    use_stdout = TRUE;
1541
0
  }
1542
2.22k
  else {
1543
2.22k
    result = Curl_fopen(data, filename, &out, &tempstore);
1544
2.22k
    if(result)
1545
0
      goto error;
1546
2.22k
  }
1547
1548
2.22k
  fputs("# Netscape HTTP Cookie File\n"
1549
2.22k
        "# https://curl.se/docs/http-cookies.html\n"
1550
2.22k
        "# This file was generated by libcurl! Edit at your own risk.\n\n",
1551
2.22k
        out);
1552
1553
2.22k
  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
2.22k
  if(!use_stdout) {
1592
2.22k
    curlx_fclose(out);
1593
2.22k
    out = NULL;
1594
2.22k
    if(tempstore && curlx_rename(tempstore, filename)) {
1595
0
      result = CURLE_WRITE_ERROR;
1596
0
      goto error;
1597
0
    }
1598
2.22k
  }
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
2.22k
  curlx_free(tempstore);
1606
2.22k
  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
2.22k
}
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
4.84k
{
1669
4.84k
  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
4.84k
  if(data->cookies) {
1675
2.40k
    const char *cookiejar = CURL_EASY_STR(data, STRING_COOKIEJAR);
1676
2.40k
    if(cookiejar && data->cookies->running) {
1677
      /* if we have a destination file for all the cookies to get dumped to */
1678
2.22k
      CURLcode result = cookie_output(data, data->cookies, cookiejar);
1679
2.22k
      if(result)
1680
0
        infof(data, "WARNING: failed to save cookies in %s: %s",
1681
2.22k
              cookiejar, curl_easy_strerror(result));
1682
2.22k
    }
1683
1684
2.40k
    if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1685
2.40k
      Curl_cookie_cleanup(data->cookies);
1686
2.40k
      data->cookies = NULL;
1687
2.40k
    }
1688
2.40k
  }
1689
4.84k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1690
4.84k
}
1691
1692
void Curl_cookie_run(struct Curl_easy *data)
1693
2.25k
{
1694
2.25k
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1695
2.25k
  if(data->cookies)
1696
2.23k
    data->cookies->running = TRUE;
1697
2.25k
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1698
2.25k
}
1699
1700
#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */