Coverage Report

Created: 2026-10-15 06:25

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