Coverage Report

Created: 2026-04-29 07:01

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