Coverage Report

Created: 2026-06-15 07:03

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
  Yet, 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 parse_cookie_header(
427
  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 origin */
437
0
{
438
  /* This line was read off an HTTP-header */
439
0
  time_t now = 0;
440
0
  size_t linelength = strlen(ptr);
441
0
  CURLcode result = CURLE_OK;
442
0
  struct Curl_str cookie[COOKIE_PIECES];
443
0
  *okay = FALSE;
444
0
  if(linelength > MAX_COOKIE_LINE)
445
    /* discard overly long lines at once */
446
0
    return CURLE_OK;
447
448
  /* memset instead of initializer because gcc 4.8.1 is silly */
449
0
  memset(cookie, 0, sizeof(cookie));
450
0
  do {
451
0
    struct Curl_str name;
452
0
    struct Curl_str val;
453
454
    /* we have a <name>=<value> pair or a stand-alone word here */
455
0
    if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {
456
0
      bool sep = FALSE;
457
0
      curlx_str_trimblanks(&name);
458
459
0
      if(!curlx_str_single(&ptr, '=')) {
460
0
        sep = TRUE; /* a '=' was used */
461
0
        if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
462
0
          curlx_str_trimblanks(&val);
463
464
        /* Reject cookies with a TAB inside the value */
465
0
        if(curlx_strlen(&val) &&
466
0
           memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {
467
0
          infof(data, "cookie contains TAB, dropping");
468
0
          return CURLE_OK;
469
0
        }
470
0
      }
471
0
      else
472
0
        curlx_str_init(&val);
473
474
0
      if(!curlx_strlen(&cookie[COOKIE_NAME])) {
475
        /* The first name/value pair is the actual cookie name */
476
0
        if(!sep ||
477
           /* Bad name/value pair. */
478
0
           invalid_octets(curlx_str(&name), curlx_strlen(&name)) ||
479
0
           invalid_octets(curlx_str(&val), curlx_strlen(&val)) ||
480
0
           !curlx_strlen(&name)) {
481
0
          infof(data, "invalid octets in name/value, cookie dropped");
482
0
          return CURLE_OK;
483
0
        }
484
485
        /*
486
         * Check for too long individual name or contents, or too long
487
         * combination of name + contents. Chrome and Firefox support 4095 or
488
         * 4096 bytes combo
489
         */
490
0
        if(curlx_strlen(&name) >= (MAX_NAME - 1) ||
491
0
           curlx_strlen(&val) >= (MAX_NAME - 1) ||
492
0
           ((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) {
493
0
          infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
494
0
                curlx_strlen(&name), curlx_strlen(&val));
495
0
          return CURLE_OK;
496
0
        }
497
498
        /* Check if we have a reserved prefix set. */
499
0
        if(!strncmp("__Secure-", curlx_str(&name), 9))
500
0
          co->prefix_secure = TRUE;
501
0
        else if(!strncmp("__Host-", curlx_str(&name), 7))
502
0
          co->prefix_host = TRUE;
503
504
0
        cookie[COOKIE_NAME] = name;
505
0
        cookie[COOKIE_VALUE] = val;
506
0
      }
507
0
      else if(!sep) {
508
        /*
509
         * this is a "<name>" with no content
510
         */
511
512
        /*
513
         * secure cookies are only allowed to be set when the connection is
514
         * using a secure protocol, or when the cookie is being set by
515
         * reading from file
516
         */
517
0
        if(curlx_str_casecompare(&name, "secure")) {
518
0
          if(secure || !ci->running)
519
0
            co->secure = TRUE;
520
0
          else {
521
0
            infof(data, "skipped cookie because not 'secure'");
522
0
            return CURLE_OK;
523
0
          }
524
0
        }
525
0
        else if(curlx_str_casecompare(&name, "httponly"))
526
0
          co->httponly = TRUE;
527
0
      }
528
0
      else if(curlx_str_casecompare(&name, "path")) {
529
0
        cookie[COOKIE_PATH] = val;
530
0
      }
531
0
      else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) {
532
0
        bool is_ip;
533
0
        const char *v = curlx_str(&val);
534
        /*
535
         * Now, we make sure that our host is within the given domain, or
536
         * the given domain is not valid and thus cannot be set.
537
         */
538
539
0
        if('.' == *v)
540
0
          curlx_str_nudge(&val, 1);
541
542
0
#ifndef USE_LIBPSL
543
        /*
544
         * Without PSL we do not know when the incoming cookie is set on a
545
         * TLD or otherwise "protected" suffix. To reduce risk, we require a
546
         * dot OR the exact hostname being "localhost".
547
         */
548
0
        if(bad_domain(curlx_str(&val), curlx_strlen(&val)))
549
0
          domain = ":";
550
0
#endif
551
552
0
        is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val));
553
554
0
        if(!domain ||
555
0
           (is_ip &&
556
0
            !strncmp(curlx_str(&val), domain, curlx_strlen(&val)) &&
557
0
            (curlx_strlen(&val) == strlen(domain))) ||
558
0
           (!is_ip && cookie_tailmatch(curlx_str(&val),
559
0
                                          curlx_strlen(&val), domain))) {
560
0
          cookie[COOKIE_DOMAIN] = val;
561
0
          if(!is_ip)
562
0
            co->tailmatch = TRUE; /* we always do that if the domain name was
563
                                     given */
564
0
        }
565
0
        else {
566
          /*
567
           * We did not get a tailmatch and then the attempted set domain is
568
           * not a domain to which the current host belongs. Mark as bad.
569
           */
570
0
          infof(data, "skipped cookie with bad tailmatch domain: %s",
571
0
                curlx_str(&val));
572
0
          return CURLE_OK;
573
0
        }
574
0
      }
575
0
      else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) {
576
        /*
577
         * Defined in RFC2109:
578
         *
579
         * Optional. The Max-Age attribute defines the lifetime of the
580
         * cookie, in seconds. The delta-seconds value is a decimal non-
581
         * negative integer. After delta-seconds seconds elapse, the
582
         * client should discard the cookie. A value of zero means the
583
         * cookie should be discarded immediately.
584
         */
585
0
        int rc;
586
0
        const char *maxage = curlx_str(&val);
587
0
        if(*maxage == '\"')
588
0
          maxage++;
589
0
        rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
590
0
        if(!now)
591
0
          now = time(NULL);
592
0
        switch(rc) {
593
0
        case STRE_OVERFLOW:
594
          /* overflow, used max value */
595
0
          co->expires = CURL_OFF_T_MAX;
596
0
          break;
597
0
        default:
598
          /* negative or otherwise bad, expire */
599
0
          co->expires = 1;
600
0
          break;
601
0
        case STRE_OK:
602
0
          if(!co->expires)
603
0
            co->expires = 1; /* expire now */
604
0
          else if(CURL_OFF_T_MAX - now < co->expires)
605
            /* would overflow */
606
0
            co->expires = CURL_OFF_T_MAX;
607
0
          else
608
0
            co->expires += now;
609
0
          break;
610
0
        }
611
0
        cap_expires(now, co);
612
0
      }
613
0
      else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val) &&
614
0
              !co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) {
615
        /*
616
         * Let max-age have priority.
617
         *
618
         * If the date cannot get parsed for whatever reason, the cookie
619
         * will be treated as a session cookie
620
         */
621
0
        char dbuf[MAX_DATE_LENGTH + 1];
622
0
        time_t date = 0;
623
0
        memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));
624
0
        dbuf[curlx_strlen(&val)] = 0;
625
0
        if(!Curl_getdate_capped(dbuf, &date)) {
626
0
          if(!date)
627
0
            date++;
628
0
          co->expires = (curl_off_t)date;
629
0
        }
630
0
        else
631
0
          co->expires = 0;
632
0
        if(!now)
633
0
          now = time(NULL);
634
0
        cap_expires(now, co);
635
0
      }
636
0
    }
637
0
  } while(!curlx_str_single(&ptr, ';'));
638
639
0
  if(curlx_strlen(&cookie[COOKIE_NAME])) {
640
    /* the header was fine, now store the data */
641
0
    result = storecookie(co, &cookie[0], path, domain);
642
0
    if(!result)
643
0
      *okay = TRUE;
644
0
  }
645
0
  return result;
646
0
}
647
648
static CURLcode parse_netscape(struct Cookie *co,
649
                               struct CookieInfo *ci,
650
                               bool *okay,
651
                               const char *lineptr,
652
                               bool secure) /* TRUE if connection is over
653
                                               secure origin */
654
0
{
655
  /*
656
   * This line is NOT an HTTP header style line, we do offer support for
657
   * reading the odd netscape cookies-file format here
658
   */
659
0
  const char *ptr, *next;
660
0
  int fields;
661
0
  size_t len;
662
0
  *okay = FALSE;
663
664
  /*
665
   * In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS
666
   * attacks. Cookies marked httpOnly are not accessible to JavaScript. In
667
   * Firefox's cookie files, they are prefixed #HttpOnly_ and the rest
668
   * remains as usual, so we skip 10 characters of the line.
669
   */
670
0
  if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
671
0
    lineptr += 10;
672
0
    co->httponly = TRUE;
673
0
  }
674
675
0
  if(lineptr[0] == '#')
676
    /* do not even try the comments */
677
0
    return CURLE_OK;
678
679
  /*
680
   * Now loop through the fields and init the struct we already have
681
   * allocated
682
   */
683
0
  fields = 0;
684
0
  for(next = lineptr; next; fields++) {
685
0
    ptr = next;
686
0
    len = strcspn(ptr, "\t\r\n");
687
0
    next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);
688
0
    switch(fields) {
689
0
    case 0:
690
0
      if(ptr[0] == '.') { /* skip preceding dots */
691
0
        ptr++;
692
0
        len--;
693
0
      }
694
0
      co->domain = curlx_memdup0(ptr, len);
695
0
      if(!co->domain)
696
0
        return CURLE_OUT_OF_MEMORY;
697
0
      break;
698
0
    case 1:
699
      /*
700
       * flag: A TRUE/FALSE value indicating if all machines within a given
701
       * domain can access the variable. Set TRUE when the cookie says
702
       * .example.com and to false when the domain is complete www.example.com
703
       */
704
0
      co->tailmatch = !!curl_strnequal(ptr, "TRUE", len);
705
0
      break;
706
0
    case 2:
707
      /* The file format allows the path field to remain not filled in */
708
0
      if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {
709
        /* only if the path does not look like a boolean option! */
710
0
        co->path = sanitize_cookie_path(ptr, len);
711
0
        if(!co->path)
712
0
          return CURLE_OUT_OF_MEMORY;
713
0
        break;
714
0
      }
715
0
      else {
716
        /* this does not look like a path, make one up! */
717
0
        co->path = curlx_strdup("/");
718
0
        if(!co->path)
719
0
          return CURLE_OUT_OF_MEMORY;
720
0
      }
721
0
      fields++; /* add a field and fall down to secure */
722
0
      FALLTHROUGH();
723
0
    case 3:
724
0
      co->secure = FALSE;
725
0
      if(curl_strnequal(ptr, "TRUE", len)) {
726
0
        if(secure || ci->running)
727
0
          co->secure = TRUE;
728
0
        else
729
0
          return CURLE_OK;
730
0
      }
731
0
      break;
732
0
    case 4:
733
0
      if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX))
734
0
        return CURLE_OK;
735
0
      break;
736
0
    case 5:
737
0
      co->name = curlx_memdup0(ptr, len);
738
0
      if(!co->name)
739
0
        return CURLE_OUT_OF_MEMORY;
740
0
      else {
741
        /* For Netscape file format cookies we check prefix on the name */
742
0
        if(curl_strnequal("__Secure-", co->name, 9))
743
0
          co->prefix_secure = TRUE;
744
0
        else if(curl_strnequal("__Host-", co->name, 7))
745
0
          co->prefix_host = TRUE;
746
0
      }
747
0
      break;
748
0
    case 6:
749
0
      co->value = curlx_memdup0(ptr, len);
750
0
      if(!co->value)
751
0
        return CURLE_OUT_OF_MEMORY;
752
0
      break;
753
0
    }
754
0
  }
755
0
  if(fields == 6) {
756
    /* we got a cookie with blank contents, fix it */
757
0
    co->value = curlx_strdup("");
758
0
    if(!co->value)
759
0
      return CURLE_OUT_OF_MEMORY;
760
0
    else
761
0
      fields++;
762
0
  }
763
764
0
  if(fields != 7)
765
    /* we did not find the sufficient number of fields */
766
0
    return CURLE_OK;
767
768
0
  *okay = TRUE;
769
0
  return CURLE_OK;
770
0
}
771
772
static bool is_public_suffix(struct Curl_easy *data,
773
                             struct Cookie *co,
774
                             const char *domain)
775
0
{
776
#ifdef USE_LIBPSL
777
  /*
778
   * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
779
   * must also check that the data handle is not NULL since the psl code will
780
   * dereference it.
781
   */
782
  DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s",
783
               co->name, co->domain, domain));
784
  if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
785
    bool acceptable = FALSE;
786
    char lcase[256];
787
    char lcookie[256];
788
    size_t dlen = strlen(domain);
789
    size_t clen = strlen(co->domain);
790
    if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {
791
      const psl_ctx_t *psl = Curl_psl_use(data);
792
      if(psl) {
793
        /* the PSL check requires lowercase domain name and pattern */
794
        Curl_strntolower(lcase, domain, dlen + 1);
795
        Curl_strntolower(lcookie, co->domain, clen + 1);
796
        acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);
797
        Curl_psl_release(data);
798
      }
799
      else
800
        infof(data, "libpsl problem, rejecting cookie for safety");
801
    }
802
803
    if(!acceptable) {
804
      infof(data, "cookie '%s' dropped, domain '%s' must not "
805
            "set cookies for '%s'", co->name, domain, co->domain);
806
      return TRUE;
807
    }
808
  }
809
#else
810
0
  (void)data;
811
0
  (void)co;
812
0
  (void)domain;
813
0
  DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",
814
0
               co->name, co->domain, domain));
815
0
#endif
816
0
  return FALSE;
817
0
}
818
819
/* returns TRUE when replaced */
820
static bool replace_existing(struct Curl_easy *data,
821
                             struct Cookie *co,
822
                             struct CookieInfo *ci,
823
                             bool secure,
824
                             bool *replacep)
825
0
{
826
0
  bool replace_old = FALSE;
827
0
  struct Curl_llist_node *replace_n = NULL;
828
0
  struct Curl_llist_node *n;
829
0
  size_t myhash = cookiehash(co->domain);
830
0
  for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
831
0
    struct Cookie *clist = Curl_node_elem(n);
832
0
    if(!strcmp(clist->name, co->name)) {
833
      /* the names are identical */
834
0
      bool matching_domains = FALSE;
835
836
0
      if(clist->domain && co->domain) {
837
0
        if(curl_strequal(clist->domain, co->domain))
838
          /* The domains are identical */
839
0
          matching_domains = TRUE;
840
0
      }
841
0
      else if(!clist->domain && !co->domain)
842
0
        matching_domains = TRUE;
843
844
0
      if(matching_domains && /* the domains were identical */
845
0
         clist->path && co->path && /* both have paths */
846
0
         clist->secure && !co->secure && !secure) {
847
0
        size_t cllen;
848
0
        const char *sep = NULL;
849
850
        /*
851
         * A non-secure cookie may not overlay an existing secure cookie.
852
         * For an existing cookie "a" with path "/login", refuse a new
853
         * cookie "a" with for example path "/login/en", while the path
854
         * "/loginhelper" is ok.
855
         */
856
857
0
        DEBUGASSERT(clist->path[0]);
858
0
        if(clist->path[0])
859
0
          sep = strchr(clist->path + 1, '/');
860
0
        if(sep)
861
0
          cllen = sep - clist->path;
862
0
        else
863
0
          cllen = strlen(clist->path);
864
865
0
        if(curl_strnequal(clist->path, co->path, cllen)) {
866
0
          infof(data, "cookie '%s' for domain '%s' dropped, would "
867
0
                "overlay an existing cookie", co->name, co->domain);
868
0
          return FALSE;
869
0
        }
870
0
      }
871
0
    }
872
873
0
    if(!replace_n && !strcmp(clist->name, co->name)) {
874
      /* the names are identical */
875
876
0
      if(clist->domain && co->domain) {
877
0
        if(curl_strequal(clist->domain, co->domain) &&
878
0
           (clist->tailmatch == co->tailmatch))
879
          /* The domains are identical */
880
0
          replace_old = TRUE;
881
0
      }
882
0
      else if(!clist->domain && !co->domain)
883
0
        replace_old = TRUE;
884
885
0
      if(replace_old) {
886
        /* the domains were identical */
887
888
0
        if(clist->path && co->path &&
889
0
           !curl_strequal(clist->path, co->path))
890
0
          replace_old = FALSE;
891
0
        else if(!clist->path != !co->path)
892
0
          replace_old = FALSE;
893
0
      }
894
895
0
      if(replace_old && !co->livecookie && clist->livecookie) {
896
        /*
897
         * Both cookies matched fine, except that the already present cookie
898
         * is "live", which means it was set from a header, while the new one
899
         * was read from a file and thus is not "live". "live" cookies are
900
         * preferred so the new cookie is freed.
901
         */
902
0
        return FALSE;
903
0
      }
904
0
      if(replace_old)
905
0
        replace_n = n;
906
0
    }
907
0
  }
908
0
  if(replace_n) {
909
0
    struct Cookie *repl = Curl_node_elem(replace_n);
910
911
    /* when replacing, creationtime is kept from old */
912
0
    co->creationtime = repl->creationtime;
913
914
    /* unlink the old */
915
0
    Curl_node_remove(replace_n);
916
917
    /* free the old cookie */
918
0
    freecookie(repl, TRUE);
919
0
  }
920
0
  *replacep = replace_old;
921
0
  return TRUE;
922
0
}
923
924
/*
925
 * Curl_cookie_add
926
 *
927
 * Add a single cookie line to the cookie keeping object. Be aware that
928
 * sometimes we get an IP-only hostname, and that might also be a numerical
929
 * IPv6 address.
930
 *
931
 */
932
CURLcode Curl_cookie_add(
933
  struct Curl_easy *data,
934
  struct CookieInfo *ci,
935
  bool httpheader,     /* TRUE if HTTP header-style line */
936
  bool noexpire,       /* if TRUE, skip remove_expired() */
937
  const char *lineptr, /* first character of the line */
938
  const char *domain,  /* default domain */
939
  const char *path,    /* full path used when this cookie is set, used
940
                          to get default path for the cookie unless set */
941
  bool secure)         /* TRUE if connection is over secure origin */
942
0
{
943
0
  struct Cookie comem;
944
0
  struct Cookie *co;
945
0
  size_t myhash;
946
0
  CURLcode result;
947
0
  bool replaces = FALSE;
948
0
  bool okay;
949
950
0
  DEBUGASSERT(data);
951
0
  DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
952
0
  if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
953
0
    return CURLE_OK; /* silently ignore */
954
955
0
  co = &comem;
956
0
  memset(co, 0, sizeof(comem));
957
958
0
  if(httpheader)
959
0
    result = parse_cookie_header(data, co, ci, &okay,
960
0
                                 lineptr, domain, path, secure);
961
0
  else
962
0
    result = parse_netscape(co, ci, &okay, lineptr, secure);
963
964
0
  if(result || !okay)
965
0
    goto fail;
966
967
0
  if(co->prefix_secure && !co->secure)
968
    /* The __Secure- prefix only requires that the cookie be set secure */
969
0
    goto fail;
970
971
0
  if(co->prefix_host) {
972
    /*
973
     * The __Host- prefix requires the cookie to be secure, have a "/" path
974
     * and not have a domain set.
975
     */
976
0
    if(co->secure && co->path && !strcmp(co->path, "/") && !co->tailmatch)
977
0
      ;
978
0
    else
979
0
      goto fail;
980
0
  }
981
982
0
  if(!ci->running &&    /* read from a file */
983
0
     ci->newsession &&  /* clean session cookies */
984
0
     !co->expires)      /* this is a session cookie */
985
0
    goto fail;
986
987
0
  co->livecookie = ci->running;
988
0
  co->creationtime = ++ci->lastct;
989
990
  /*
991
   * Now we have parsed the incoming line, we must now check if this supersedes
992
   * an already existing cookie, which it may if the previous have the same
993
   * domain and path as this.
994
   */
995
996
  /* remove expired cookies */
997
0
  if(!noexpire)
998
0
    remove_expired(ci);
999
1000
0
  if(is_public_suffix(data, co, domain))
1001
0
    goto fail;
1002
1003
0
  if(!replace_existing(data, co, ci, secure, &replaces))
1004
0
    goto fail;
1005
1006
  /* clone the stack struct into heap */
1007
0
  co = curlx_memdup(&comem, sizeof(comem));
1008
0
  if(!co) {
1009
0
    co = &comem;
1010
0
    result = CURLE_OUT_OF_MEMORY;
1011
0
    goto fail; /* bail out if we are this low on memory */
1012
0
  }
1013
1014
  /* add this cookie to the list */
1015
0
  myhash = cookiehash(co->domain);
1016
0
  Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
1017
1018
0
  if(ci->running)
1019
    /* Only show this when NOT reading the cookies from a file */
1020
0
    infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
1021
0
          "expire %" FMT_OFF_T,
1022
0
          replaces ? "Replaced" : "Added", co->name, co->value,
1023
0
          co->domain, co->path, co->expires);
1024
1025
0
  if(!replaces)
1026
0
    ci->numcookies++; /* one more cookie in the jar */
1027
1028
  /*
1029
   * Now that we have added a new cookie to the jar, update the expiration
1030
   * tracker in case it is the next one to expire.
1031
   */
1032
0
  if(co->expires && (co->expires < ci->next_expiration))
1033
0
    ci->next_expiration = co->expires;
1034
1035
0
  if(httpheader)
1036
0
    data->req.setcookies++;
1037
1038
0
  return result;
1039
0
fail:
1040
0
  freecookie(co, FALSE);
1041
0
  return result;
1042
0
}
1043
1044
/*
1045
 * Curl_cookie_init()
1046
 *
1047
 * Inits a cookie struct to read data from a local file. This is always
1048
 * called before any cookies are set. File may be NULL in which case only the
1049
 * struct is initialized. Is file is "-" then STDIN is read.
1050
 *
1051
 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1052
 *
1053
 * Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
1054
 * will be ignored.
1055
 *
1056
 * Returns NULL on out of memory.
1057
 */
1058
struct CookieInfo *Curl_cookie_init(void)
1059
0
{
1060
0
  int i;
1061
0
  struct CookieInfo *ci = curlx_calloc(1, sizeof(struct CookieInfo));
1062
0
  if(!ci)
1063
0
    return NULL;
1064
1065
  /* This does not use the destructor callback since we want to add
1066
     and remove to lists while keeping the cookie struct intact */
1067
0
  for(i = 0; i < COOKIE_HASH_SIZE; i++)
1068
0
    Curl_llist_init(&ci->cookielist[i], NULL);
1069
  /*
1070
   * Initialize the next_expiration time to signal that we do not have enough
1071
   * information yet.
1072
   */
1073
0
  ci->next_expiration = CURL_OFF_T_MAX;
1074
1075
0
  return ci;
1076
0
}
1077
1078
/*
1079
 * cookie_load()
1080
 *
1081
 * Reads cookies from a local file. This is always called before any cookies
1082
 * are set. If file is "-" then STDIN is read.
1083
 *
1084
 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
1085
 *
1086
 */
1087
static CURLcode cookie_load(struct Curl_easy *data, const char *file,
1088
                            struct CookieInfo *ci, bool newsession)
1089
0
{
1090
0
  FILE *handle = NULL;
1091
0
  CURLcode result = CURLE_OK;
1092
0
  FILE *fp = NULL;
1093
0
  DEBUGASSERT(ci);
1094
0
  DEBUGASSERT(data);
1095
0
  DEBUGASSERT(file);
1096
1097
0
  ci->newsession = newsession; /* new session? */
1098
0
  ci->running = FALSE; /* this is not running, this is init */
1099
1100
0
  if(file && *file) {
1101
0
    if(!strcmp(file, "-"))
1102
0
      fp = stdin;
1103
0
    else {
1104
0
      fp = curlx_fopen(file, "rb");
1105
0
      if(!fp)
1106
0
        infof(data, "WARNING: failed to open cookie file \"%s\"", file);
1107
0
      else {
1108
0
        curlx_struct_stat stat;
1109
0
        if((curlx_fstat(fileno(fp), &stat) != -1) && S_ISDIR(stat.st_mode)) {
1110
0
          curlx_fclose(fp);
1111
0
          fp = NULL;
1112
0
          infof(data, "WARNING: cookie filename points to a directory: \"%s\"",
1113
0
                file);
1114
0
        }
1115
0
        else
1116
0
          handle = fp;
1117
0
      }
1118
0
    }
1119
0
  }
1120
1121
0
  if(fp) {
1122
0
    struct dynbuf buf;
1123
0
    bool eof = FALSE;
1124
0
    curlx_dyn_init(&buf, MAX_COOKIE_LINE);
1125
0
    do {
1126
0
      result = Curl_get_line(&buf, fp, &eof);
1127
0
      if(!result) {
1128
0
        const char *lineptr = curlx_dyn_ptr(&buf);
1129
0
        bool headerline = FALSE;
1130
0
        if(checkprefix("Set-Cookie:", lineptr)) {
1131
          /* This is a cookie line, get it! */
1132
0
          lineptr += 11;
1133
0
          headerline = TRUE;
1134
0
          curlx_str_passblanks(&lineptr);
1135
0
        }
1136
1137
0
        result = Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL,
1138
0
                                 NULL, TRUE);
1139
        /* File reading cookie failures are not propagated back to the
1140
           caller because there is no way to do that */
1141
0
      }
1142
0
    } while(!result && !eof);
1143
0
    curlx_dyn_free(&buf); /* free the line buffer */
1144
1145
    /*
1146
     * Remove expired cookies from the hash. We must make sure to run this
1147
     * after reading the file, and not on every cookie.
1148
     */
1149
0
    remove_expired(ci);
1150
1151
0
    if(handle)
1152
0
      curlx_fclose(handle);
1153
0
  }
1154
0
  data->state.cookie_engine = TRUE;
1155
0
  ci->running = TRUE;          /* now, we are running */
1156
1157
0
  return result;
1158
0
}
1159
1160
/*
1161
 * Load cookies from all given cookie files (CURLOPT_COOKIEFILE).
1162
 */
1163
CURLcode Curl_cookie_loadfiles(struct Curl_easy *data)
1164
0
{
1165
0
  CURLcode result = CURLE_OK;
1166
0
  struct curl_slist *list = data->state.cookielist;
1167
0
  if(list) {
1168
0
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1169
0
    if(!data->cookies)
1170
0
      data->cookies = Curl_cookie_init();
1171
0
    if(!data->cookies)
1172
0
      result = CURLE_OUT_OF_MEMORY;
1173
0
    else {
1174
0
      data->state.cookie_engine = TRUE;
1175
0
      while(list) {
1176
0
        result = cookie_load(data, list->data, data->cookies,
1177
0
                             (bool)data->set.cookiesession);
1178
0
        if(result)
1179
0
          break;
1180
0
        list = list->next;
1181
0
      }
1182
0
    }
1183
0
    Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1184
0
  }
1185
0
  return result;
1186
0
}
1187
1188
/*
1189
 * cookie_sort
1190
 *
1191
 * Helper function to sort cookies such that the longest path gets before the
1192
 * shorter path. Path, domain and name lengths are considered in that order,
1193
 * with the creationtime as the tiebreaker. The creationtime is guaranteed to
1194
 * be unique per cookie, so we know we will get an ordering at that point.
1195
 */
1196
static int cookie_sort(const void *p1, const void *p2)
1197
0
{
1198
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1199
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1200
0
  size_t l1, l2;
1201
1202
  /* 1 - compare cookie path lengths */
1203
0
  l1 = c1->path ? strlen(c1->path) : 0;
1204
0
  l2 = c2->path ? strlen(c2->path) : 0;
1205
1206
0
  if(l1 != l2)
1207
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1208
1209
  /* 2 - compare cookie domain lengths */
1210
0
  l1 = c1->domain ? strlen(c1->domain) : 0;
1211
0
  l2 = c2->domain ? strlen(c2->domain) : 0;
1212
1213
0
  if(l1 != l2)
1214
0
    return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */
1215
1216
  /* 3 - compare cookie name lengths */
1217
0
  l1 = c1->name ? strlen(c1->name) : 0;
1218
0
  l2 = c2->name ? strlen(c2->name) : 0;
1219
1220
0
  if(l1 != l2)
1221
0
    return (l2 > l1) ? 1 : -1;
1222
1223
  /* 4 - compare cookie creation time */
1224
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1225
0
}
1226
1227
/*
1228
 * cookie_sort_ct
1229
 *
1230
 * Helper function to sort cookies according to creation time.
1231
 */
1232
static int cookie_sort_ct(const void *p1, const void *p2)
1233
0
{
1234
0
  const struct Cookie *c1 = *(const struct Cookie * const *)p1;
1235
0
  const struct Cookie *c2 = *(const struct Cookie * const *)p2;
1236
1237
0
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
1238
0
}
1239
1240
bool Curl_secure_context(struct connectdata *conn, const char *host)
1241
0
{
1242
0
  return conn->scheme->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS) ||
1243
0
    curl_strequal("localhost", host) ||
1244
0
    !strcmp(host, "127.0.0.1") ||
1245
0
    !strcmp(host, "::1");
1246
0
}
1247
1248
/*
1249
 * Curl_cookie_getlist
1250
 *
1251
 * For a given host and path, return a linked list of cookies that the client
1252
 * should send to the server if used now. The secure boolean informs the cookie
1253
 * if a secure connection is achieved or not.
1254
 *
1255
 * It shall only return cookies that have not expired.
1256
 *
1257
 * 'okay' is TRUE when there is a list returned.
1258
 */
1259
CURLcode Curl_cookie_getlist(struct Curl_easy *data,
1260
                             struct connectdata *conn,
1261
                             bool *okay,
1262
                             const char *host,
1263
                             struct Curl_llist *list)
1264
0
{
1265
0
  size_t matches = 0;
1266
0
  const bool is_ip = Curl_host_is_ipnum(host);
1267
0
  const size_t myhash = cookiehash(host);
1268
0
  struct Curl_llist_node *n;
1269
0
  const bool secure = Curl_secure_context(conn, host);
1270
0
  struct CookieInfo *ci = data->cookies;
1271
0
  const char *path = data->state.up.path;
1272
0
  CURLcode result = CURLE_OK;
1273
0
  *okay = FALSE;
1274
1275
0
  Curl_llist_init(list, NULL);
1276
1277
0
  if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
1278
0
    return CURLE_OK; /* no cookie struct or no cookies in the struct */
1279
1280
  /* at first, remove expired cookies */
1281
0
  remove_expired(ci);
1282
1283
0
  for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
1284
0
    struct Cookie *co = Curl_node_elem(n);
1285
1286
    /* if the cookie requires we are secure we must only continue if we are! */
1287
0
    if(co->secure ? secure : TRUE) {
1288
1289
      /* now check if the domain is correct */
1290
0
      if(!co->domain ||
1291
0
         (co->tailmatch && !is_ip &&
1292
0
          cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
1293
0
         ((!co->tailmatch || is_ip) && curl_strequal(host, co->domain))) {
1294
        /*
1295
         * the right part of the host matches the domain stuff in the
1296
         * cookie data
1297
         */
1298
1299
        /*
1300
         * now check the left part of the path with the cookies path
1301
         * requirement
1302
         */
1303
0
        if(!co->path || pathmatch(co->path, path)) {
1304
1305
          /*
1306
           * This is a match and we add it to the return-linked-list
1307
           */
1308
0
          Curl_llist_append(list, co, &co->getnode);
1309
0
          matches++;
1310
0
          if(matches >= MAX_COOKIE_SEND_AMOUNT) {
1311
0
            infof(data, "Included max number of cookies (%zu) in request!",
1312
0
                  matches);
1313
0
            break;
1314
0
          }
1315
0
        }
1316
0
      }
1317
0
    }
1318
0
  }
1319
1320
0
  if(matches) {
1321
    /*
1322
     * Now we need to make sure that if there is a name appearing more than
1323
     * once, the longest specified path version comes first. To make this the
1324
     * swiftest way, we sort them all based on path length.
1325
     */
1326
0
    struct Cookie **array;
1327
0
    size_t i;
1328
1329
    /* alloc an array and store all cookie pointers */
1330
0
    array = curlx_malloc(sizeof(struct Cookie *) * matches);
1331
0
    if(!array) {
1332
0
      result = CURLE_OUT_OF_MEMORY;
1333
0
      goto fail;
1334
0
    }
1335
1336
0
    n = Curl_llist_head(list);
1337
1338
0
    for(i = 0; n; n = Curl_node_next(n))
1339
0
      array[i++] = Curl_node_elem(n);
1340
1341
    /* now sort the cookie pointers in path length order */
1342
0
    qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1343
1344
    /* remake the linked list order according to the new order */
1345
0
    Curl_llist_destroy(list, NULL);
1346
1347
0
    for(i = 0; i < matches; i++)
1348
0
      Curl_llist_append(list, array[i], &array[i]->getnode);
1349
1350
0
    curlx_free(array); /* remove the temporary data again */
1351
0
  }
1352
1353
0
  *okay = TRUE;
1354
0
  return CURLE_OK; /* success */
1355
1356
0
fail:
1357
  /* failure, clear up the allocated chain and return NULL */
1358
0
  Curl_llist_destroy(list, NULL);
1359
0
  return result; /* error */
1360
0
}
1361
1362
/*
1363
 * Curl_cookie_clearall
1364
 *
1365
 * Clear all existing cookies and reset the counter.
1366
 */
1367
void Curl_cookie_clearall(struct CookieInfo *ci)
1368
0
{
1369
0
  if(ci) {
1370
0
    unsigned int i;
1371
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1372
0
      struct Curl_llist_node *n;
1373
0
      for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
1374
0
        struct Cookie *c = Curl_node_elem(n);
1375
0
        struct Curl_llist_node *e = Curl_node_next(n);
1376
0
        Curl_node_remove(n);
1377
0
        freecookie(c, TRUE);
1378
0
        n = e;
1379
0
      }
1380
0
    }
1381
0
    ci->numcookies = 0;
1382
0
  }
1383
0
}
1384
1385
/*
1386
 * Curl_cookie_clearsess
1387
 *
1388
 * Free all session cookies in the cookies list.
1389
 */
1390
void Curl_cookie_clearsess(struct CookieInfo *ci)
1391
0
{
1392
0
  unsigned int i;
1393
1394
0
  if(!ci)
1395
0
    return;
1396
1397
0
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1398
0
    struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);
1399
0
    struct Curl_llist_node *e = NULL;
1400
1401
0
    for(; n; n = e) {
1402
0
      struct Cookie *curr = Curl_node_elem(n);
1403
0
      e = Curl_node_next(n); /* in case the node is removed, get it early */
1404
0
      if(!curr->expires) {
1405
0
        Curl_node_remove(n);
1406
0
        freecookie(curr, TRUE);
1407
0
        ci->numcookies--;
1408
0
      }
1409
0
    }
1410
0
  }
1411
0
}
1412
1413
/*
1414
 * Curl_cookie_cleanup()
1415
 *
1416
 * Free a "cookie object" previous created with Curl_cookie_init().
1417
 */
1418
void Curl_cookie_cleanup(struct CookieInfo *ci)
1419
0
{
1420
0
  if(ci) {
1421
0
    Curl_cookie_clearall(ci);
1422
0
    curlx_free(ci); /* free the base struct as well */
1423
0
  }
1424
0
}
1425
1426
/*
1427
 * get_netscape_format()
1428
 *
1429
 * Formats a string for Netscape output file, w/o a newline at the end.
1430
 * Function returns a char * to a formatted line. The caller is responsible
1431
 * for freeing the returned pointer.
1432
 */
1433
static char *get_netscape_format(const struct Cookie *co)
1434
0
{
1435
0
  return curl_maprintf(
1436
0
    "%s"               /* httponly preamble */
1437
0
    "%s%s\t"           /* domain */
1438
0
    "%s\t"             /* tailmatch */
1439
0
    "%s\t"             /* path */
1440
0
    "%s\t"             /* secure */
1441
0
    "%" FMT_OFF_T "\t" /* expires */
1442
0
    "%s\t"             /* name */
1443
0
    "%s",              /* value */
1444
0
    co->httponly ? "#HttpOnly_" : "",
1445
    /*
1446
     * Make sure all domains are prefixed with a dot if they allow
1447
     * tailmatching. This is Mozilla-style.
1448
     */
1449
0
    (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
1450
0
    co->domain ? co->domain : "unknown",
1451
0
    co->tailmatch ? "TRUE" : "FALSE",
1452
0
    co->path ? co->path : "/",
1453
0
    co->secure ? "TRUE" : "FALSE",
1454
0
    co->expires,
1455
0
    co->name,
1456
0
    co->value ? co->value : "");
1457
0
}
1458
1459
/*
1460
 * cookie_output()
1461
 *
1462
 * Writes all internally known cookies to the specified file. Specify
1463
 * "-" as filename to write to stdout.
1464
 *
1465
 * The function returns non-zero on write failure.
1466
 */
1467
static CURLcode cookie_output(struct Curl_easy *data,
1468
                              struct CookieInfo *ci,
1469
                              const char *filename)
1470
0
{
1471
0
  FILE *out = NULL;
1472
0
  bool use_stdout = FALSE;
1473
0
  char *tempstore = NULL;
1474
0
  CURLcode result = CURLE_OK;
1475
1476
0
  if(!ci)
1477
    /* no cookie engine alive */
1478
0
    return CURLE_OK;
1479
1480
  /* at first, remove expired cookies */
1481
0
  remove_expired(ci);
1482
1483
0
  if(!strcmp("-", filename)) {
1484
    /* use stdout */
1485
0
    out = stdout;
1486
0
    use_stdout = TRUE;
1487
0
  }
1488
0
  else {
1489
0
    result = Curl_fopen(data, filename, &out, &tempstore);
1490
0
    if(result)
1491
0
      goto error;
1492
0
  }
1493
1494
0
  fputs("# Netscape HTTP Cookie File\n"
1495
0
        "# https://curl.se/docs/http-cookies.html\n"
1496
0
        "# This file was generated by libcurl! Edit at your own risk.\n\n",
1497
0
        out);
1498
1499
0
  if(ci->numcookies) {
1500
0
    unsigned int i;
1501
0
    size_t nvalid = 0;
1502
0
    struct Cookie **array;
1503
0
    struct Curl_llist_node *n;
1504
1505
0
    array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies);
1506
0
    if(!array) {
1507
0
      result = CURLE_OUT_OF_MEMORY;
1508
0
      goto error;
1509
0
    }
1510
1511
    /* only sort the cookies with a domain property */
1512
0
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1513
0
      for(n = Curl_llist_head(&ci->cookielist[i]); n; n = Curl_node_next(n)) {
1514
0
        struct Cookie *co = Curl_node_elem(n);
1515
0
        if(!co->domain)
1516
0
          continue;
1517
0
        array[nvalid++] = co;
1518
0
      }
1519
0
    }
1520
1521
0
    qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
1522
1523
0
    for(i = 0; i < nvalid; i++) {
1524
0
      char *format_ptr = get_netscape_format(array[i]);
1525
0
      if(!format_ptr) {
1526
0
        curlx_free(array);
1527
0
        result = CURLE_OUT_OF_MEMORY;
1528
0
        goto error;
1529
0
      }
1530
0
      curl_mfprintf(out, "%s\n", format_ptr);
1531
0
      curlx_free(format_ptr);
1532
0
    }
1533
1534
0
    curlx_free(array);
1535
0
  }
1536
1537
0
  if(!use_stdout) {
1538
0
    curlx_fclose(out);
1539
0
    out = NULL;
1540
0
    if(tempstore && curlx_rename(tempstore, filename)) {
1541
0
      result = CURLE_WRITE_ERROR;
1542
0
      goto error;
1543
0
    }
1544
0
  }
1545
1546
  /*
1547
   * If we reach here we have successfully written a cookie file so there is
1548
   * no need to inspect the error, any error case should have jumped into the
1549
   * error block below.
1550
   */
1551
0
  curlx_free(tempstore);
1552
0
  return CURLE_OK;
1553
1554
0
error:
1555
0
  if(out && !use_stdout)
1556
0
    curlx_fclose(out);
1557
0
  if(tempstore) {
1558
0
    unlink(tempstore);
1559
0
    curlx_free(tempstore);
1560
0
  }
1561
0
  return result;
1562
0
}
1563
1564
static struct curl_slist *cookie_list(struct Curl_easy *data)
1565
0
{
1566
0
  struct curl_slist *list = NULL;
1567
0
  struct curl_slist *beg;
1568
0
  unsigned int i;
1569
0
  struct Curl_llist_node *n;
1570
1571
0
  if(!data->cookies || (data->cookies->numcookies == 0))
1572
0
    return NULL;
1573
1574
  /* at first, remove expired cookies */
1575
0
  remove_expired(data->cookies);
1576
1577
0
  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
1578
0
    for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;
1579
0
        n = Curl_node_next(n)) {
1580
0
      struct Cookie *c = Curl_node_elem(n);
1581
0
      char *line;
1582
0
      if(!c->domain)
1583
0
        continue;
1584
0
      line = get_netscape_format(c);
1585
0
      if(!line) {
1586
0
        curl_slist_free_all(list);
1587
0
        return NULL;
1588
0
      }
1589
0
      beg = Curl_slist_append_nodup(list, line);
1590
0
      if(!beg) {
1591
0
        curlx_free(line);
1592
0
        curl_slist_free_all(list);
1593
0
        return NULL;
1594
0
      }
1595
0
      list = beg;
1596
0
    }
1597
0
  }
1598
1599
0
  return list;
1600
0
}
1601
1602
struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
1603
0
{
1604
0
  struct curl_slist *list;
1605
0
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1606
0
  list = cookie_list(data);
1607
0
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1608
0
  return list;
1609
0
}
1610
1611
void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
1612
0
{
1613
0
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1614
  /* only save the cookie file if a transfer was started (cookies->running is
1615
     set), as otherwise the cookies were not completely initialized and there
1616
     might be cookie files that were not loaded so saving the file is the
1617
     wrong thing. */
1618
0
  if(data->cookies) {
1619
0
    if(data->set.str[STRING_COOKIEJAR] && data->cookies->running) {
1620
      /* if we have a destination file for all the cookies to get dumped to */
1621
0
      CURLcode result = cookie_output(data, data->cookies,
1622
0
                                      data->set.str[STRING_COOKIEJAR]);
1623
0
      if(result)
1624
0
        infof(data, "WARNING: failed to save cookies in %s: %s",
1625
0
              data->set.str[STRING_COOKIEJAR], curl_easy_strerror(result));
1626
0
    }
1627
1628
0
    if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1629
0
      Curl_cookie_cleanup(data->cookies);
1630
0
      data->cookies = NULL;
1631
0
    }
1632
0
  }
1633
0
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1634
0
}
1635
1636
void Curl_cookie_run(struct Curl_easy *data)
1637
0
{
1638
0
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1639
0
  if(data->cookies)
1640
0
    data->cookies->running = TRUE;
1641
0
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1642
0
}
1643
1644
#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */