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