Coverage Report

Created: 2026-01-09 07:14

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