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