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