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