1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.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 ***************************************************************************/
22
23 /***
24
25
26 RECEIVING COOKIE INFORMATION
27 ============================
28
29 struct CookieInfo *Curl_cookie_init(struct SessionHandle *data,
30 const char *file, struct CookieInfo *inc, bool newsession);
31
32 Inits a cookie struct to store data in a local file. This is always
33 called before any cookies are set.
34
35 struct Cookie *Curl_cookie_add(struct SessionHandle *data,
36 struct CookieInfo *c, bool httpheader, char *lineptr,
37 const char *domain, const char *path);
38
39 The 'lineptr' parameter is a full "Set-cookie:" line as
40 received from a server.
41
42 The function need to replace previously stored lines that this new
43 line superceeds.
44
45 It may remove lines that are expired.
46
47 It should return an indication of success/error.
48
49
50 SENDING COOKIE INFORMATION
51 ==========================
52
53 struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie,
54 char *host, char *path, bool secure);
55
56 For a given host and path, return a linked list of cookies that
57 the client should send to the server if used now. The secure
58 boolean informs the cookie if a secure connection is achieved or
59 not.
60
61 It shall only return cookies that haven't expired.
62
63
64 Example set of cookies:
65
66 Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure
67 Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
68 domain=.fidelity.com; path=/ftgw; secure
69 Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
70 domain=.fidelity.com; path=/; secure
71 Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
72 domain=.fidelity.com; path=/; secure
73 Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
74 domain=.fidelity.com; path=/; secure
75 Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
76 domain=.fidelity.com; path=/; secure
77 Set-cookie:
78 Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
79 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
80 ****/
81
82 #include "curl_setup.h"
83
84 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
85
86 #include "curl_printf.h"
87 #include "urldata.h"
88 #include "cookie.h"
89 #include "strequal.h"
90 #include "strtok.h"
91 #include "sendf.h"
92 #include "slist.h"
93 #include "share.h"
94 #include "strtoofft.h"
95 #include "rawstr.h"
96 #include "curl_memrchr.h"
97 #include "inet_pton.h"
98
99 /* The last #include files should be: */
100 #include "curl_memory.h"
101 #include "memdebug.h"
102
freecookie(struct Cookie * co)103 static void freecookie(struct Cookie *co)
104 {
105 free(co->expirestr);
106 free(co->domain);
107 free(co->path);
108 free(co->spath);
109 free(co->name);
110 free(co->value);
111 free(co->maxage);
112 free(co->version);
113 free(co);
114 }
115
tailmatch(const char * cooke_domain,const char * hostname)116 static bool tailmatch(const char *cooke_domain, const char *hostname)
117 {
118 size_t cookie_domain_len = strlen(cooke_domain);
119 size_t hostname_len = strlen(hostname);
120
121 if(hostname_len < cookie_domain_len)
122 return FALSE;
123
124 if(!Curl_raw_equal(cooke_domain, hostname+hostname_len-cookie_domain_len))
125 return FALSE;
126
127 /* A lead char of cookie_domain is not '.'.
128 RFC6265 4.1.2.3. The Domain Attribute says:
129 For example, if the value of the Domain attribute is
130 "example.com", the user agent will include the cookie in the Cookie
131 header when making HTTP requests to example.com, www.example.com, and
132 www.corp.example.com.
133 */
134 if(hostname_len == cookie_domain_len)
135 return TRUE;
136 if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
137 return TRUE;
138 return FALSE;
139 }
140
141 /*
142 * matching cookie path and url path
143 * RFC6265 5.1.4 Paths and Path-Match
144 */
pathmatch(const char * cookie_path,const char * request_uri)145 static bool pathmatch(const char* cookie_path, const char* request_uri)
146 {
147 size_t cookie_path_len;
148 size_t uri_path_len;
149 char* uri_path = NULL;
150 char* pos;
151 bool ret = FALSE;
152
153 /* cookie_path must not have last '/' separator. ex: /sample */
154 cookie_path_len = strlen(cookie_path);
155 if(1 == cookie_path_len) {
156 /* cookie_path must be '/' */
157 return TRUE;
158 }
159
160 uri_path = strdup(request_uri);
161 if(!uri_path)
162 return FALSE;
163 pos = strchr(uri_path, '?');
164 if(pos)
165 *pos = 0x0;
166
167 /* #-fragments are already cut off! */
168 if(0 == strlen(uri_path) || uri_path[0] != '/') {
169 free(uri_path);
170 uri_path = strdup("/");
171 if(!uri_path)
172 return FALSE;
173 }
174
175 /* here, RFC6265 5.1.4 says
176 4. Output the characters of the uri-path from the first character up
177 to, but not including, the right-most %x2F ("/").
178 but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site
179 without redirect.
180 Ignore this algorithm because /hoge is uri path for this case
181 (uri path is not /).
182 */
183
184 uri_path_len = strlen(uri_path);
185
186 if(uri_path_len < cookie_path_len) {
187 ret = FALSE;
188 goto pathmatched;
189 }
190
191 /* not using checkprefix() because matching should be case-sensitive */
192 if(strncmp(cookie_path, uri_path, cookie_path_len)) {
193 ret = FALSE;
194 goto pathmatched;
195 }
196
197 /* The cookie-path and the uri-path are identical. */
198 if(cookie_path_len == uri_path_len) {
199 ret = TRUE;
200 goto pathmatched;
201 }
202
203 /* here, cookie_path_len < url_path_len */
204 if(uri_path[cookie_path_len] == '/') {
205 ret = TRUE;
206 goto pathmatched;
207 }
208
209 ret = FALSE;
210
211 pathmatched:
212 free(uri_path);
213 return ret;
214 }
215
216 /*
217 * cookie path sanitize
218 */
sanitize_cookie_path(const char * cookie_path)219 static char *sanitize_cookie_path(const char *cookie_path)
220 {
221 size_t len;
222 char *new_path = strdup(cookie_path);
223 if(!new_path)
224 return NULL;
225
226 /* some stupid site sends path attribute with '"'. */
227 len = strlen(new_path);
228 if(new_path[0] == '\"') {
229 memmove((void *)new_path, (const void *)(new_path + 1), len);
230 len--;
231 }
232 if(len && (new_path[len - 1] == '\"')) {
233 new_path[len - 1] = 0x0;
234 len--;
235 }
236
237 /* RFC6265 5.2.4 The Path Attribute */
238 if(new_path[0] != '/') {
239 /* Let cookie-path be the default-path. */
240 free(new_path);
241 new_path = strdup("/");
242 return new_path;
243 }
244
245 /* convert /hoge/ to /hoge */
246 if(len && new_path[len - 1] == '/') {
247 new_path[len - 1] = 0x0;
248 }
249
250 return new_path;
251 }
252
253 /*
254 * Load cookies from all given cookie files (CURLOPT_COOKIEFILE).
255 *
256 * NOTE: OOM or cookie parsing failures are ignored.
257 */
Curl_cookie_loadfiles(struct SessionHandle * data)258 void Curl_cookie_loadfiles(struct SessionHandle *data)
259 {
260 struct curl_slist *list = data->change.cookielist;
261 if(list) {
262 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
263 while(list) {
264 struct CookieInfo *newcookies = Curl_cookie_init(data,
265 list->data,
266 data->cookies,
267 data->set.cookiesession);
268 if(!newcookies)
269 /* Failure may be due to OOM or a bad cookie; both are ignored
270 * but only the first should be
271 */
272 infof(data, "ignoring failed cookie_init for %s\n", list->data);
273 else
274 data->cookies = newcookies;
275 list = list->next;
276 }
277 curl_slist_free_all(data->change.cookielist); /* clean up list */
278 data->change.cookielist = NULL; /* don't do this again! */
279 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
280 }
281 }
282
283 /*
284 * strstore() makes a strdup() on the 'newstr' and if '*str' is non-NULL
285 * that will be freed before the allocated string is stored there.
286 *
287 * It is meant to easily replace strdup()
288 */
strstore(char ** str,const char * newstr)289 static void strstore(char **str, const char *newstr)
290 {
291 free(*str);
292 *str = strdup(newstr);
293 }
294
295 /*
296 * remove_expired() removes expired cookies.
297 */
remove_expired(struct CookieInfo * cookies)298 static void remove_expired(struct CookieInfo *cookies)
299 {
300 struct Cookie *co, *nx, *pv;
301 curl_off_t now = (curl_off_t)time(NULL);
302
303 co = cookies->cookies;
304 pv = NULL;
305 while(co) {
306 nx = co->next;
307 if((co->expirestr || co->maxage) && co->expires < now) {
308 if(co == cookies->cookies) {
309 cookies->cookies = co->next;
310 }
311 else {
312 pv->next = co->next;
313 }
314 cookies->numcookies--;
315 freecookie(co);
316 }
317 else {
318 pv = co;
319 }
320 co = nx;
321 }
322 }
323
324 /*
325 * Return true if the given string is an IP(v4|v6) address.
326 */
isip(const char * domain)327 static bool isip(const char *domain)
328 {
329 struct in_addr addr;
330 #ifdef ENABLE_IPV6
331 struct in6_addr addr6;
332 #endif
333
334 if(Curl_inet_pton(AF_INET, domain, &addr)
335 #ifdef ENABLE_IPV6
336 || Curl_inet_pton(AF_INET6, domain, &addr6)
337 #endif
338 ) {
339 /* domain name given as IP address */
340 return TRUE;
341 }
342
343 return FALSE;
344 }
345
346 /****************************************************************************
347 *
348 * Curl_cookie_add()
349 *
350 * Add a single cookie line to the cookie keeping object.
351 *
352 * Be aware that sometimes we get an IP-only host name, and that might also be
353 * a numerical IPv6 address.
354 *
355 * Returns NULL on out of memory or invalid cookie. This is suboptimal,
356 * as they should be treated separately.
357 ***************************************************************************/
358
359 struct Cookie *
Curl_cookie_add(struct SessionHandle * data,struct CookieInfo * c,bool httpheader,char * lineptr,const char * domain,const char * path)360 Curl_cookie_add(struct SessionHandle *data,
361 /* The 'data' pointer here may be NULL at times, and thus
362 must only be used very carefully for things that can deal
363 with data being NULL. Such as infof() and similar */
364
365 struct CookieInfo *c,
366 bool httpheader, /* TRUE if HTTP header-style line */
367 char *lineptr, /* first character of the line */
368 const char *domain, /* default domain */
369 const char *path) /* full path used when this cookie is set,
370 used to get default path for the cookie
371 unless set */
372 {
373 struct Cookie *clist;
374 char name[MAX_NAME];
375 struct Cookie *co;
376 struct Cookie *lastc=NULL;
377 time_t now = time(NULL);
378 bool replace_old = FALSE;
379 bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
380
381 #ifdef CURL_DISABLE_VERBOSE_STRINGS
382 (void)data;
383 #endif
384
385 /* First, alloc and init a new struct for it */
386 co = calloc(1, sizeof(struct Cookie));
387 if(!co)
388 return NULL; /* bail out if we're this low on memory */
389
390 if(httpheader) {
391 /* This line was read off a HTTP-header */
392 const char *ptr;
393 const char *semiptr;
394 char *what;
395
396 what = malloc(MAX_COOKIE_LINE);
397 if(!what) {
398 free(co);
399 return NULL;
400 }
401
402 semiptr=strchr(lineptr, ';'); /* first, find a semicolon */
403
404 while(*lineptr && ISBLANK(*lineptr))
405 lineptr++;
406
407 ptr = lineptr;
408 do {
409 /* we have a <what>=<this> pair or a stand-alone word here */
410 name[0]=what[0]=0; /* init the buffers */
411 if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n =] =%"
412 MAX_COOKIE_LINE_TXT "[^;\r\n]",
413 name, what)) {
414 /* Use strstore() below to properly deal with received cookie
415 headers that have the same string property set more than once,
416 and then we use the last one. */
417 const char *whatptr;
418 bool done = FALSE;
419 bool sep;
420 size_t len=strlen(what);
421 const char *endofn = &ptr[ strlen(name) ];
422
423 /* skip trailing spaces in name */
424 while(*endofn && ISBLANK(*endofn))
425 endofn++;
426
427 /* name ends with a '=' ? */
428 sep = (*endofn == '=')?TRUE:FALSE;
429
430 /* Strip off trailing whitespace from the 'what' */
431 while(len && ISBLANK(what[len-1])) {
432 what[len-1]=0;
433 len--;
434 }
435
436 /* Skip leading whitespace from the 'what' */
437 whatptr=what;
438 while(*whatptr && ISBLANK(*whatptr))
439 whatptr++;
440
441 if(!len) {
442 /* this was a "<name>=" with no content, and we must allow
443 'secure' and 'httponly' specified this weirdly */
444 done = TRUE;
445 if(Curl_raw_equal("secure", name))
446 co->secure = TRUE;
447 else if(Curl_raw_equal("httponly", name))
448 co->httponly = TRUE;
449 else if(sep)
450 /* there was a '=' so we're not done parsing this field */
451 done = FALSE;
452 }
453 if(done)
454 ;
455 else if(Curl_raw_equal("path", name)) {
456 strstore(&co->path, whatptr);
457 if(!co->path) {
458 badcookie = TRUE; /* out of memory bad */
459 break;
460 }
461 co->spath = sanitize_cookie_path(co->path);
462 if(!co->spath) {
463 badcookie = TRUE; /* out of memory bad */
464 break;
465 }
466 }
467 else if(Curl_raw_equal("domain", name)) {
468 bool is_ip;
469 const char *dotp;
470
471 /* Now, we make sure that our host is within the given domain,
472 or the given domain is not valid and thus cannot be set. */
473
474 if('.' == whatptr[0])
475 whatptr++; /* ignore preceding dot */
476
477 is_ip = isip(domain ? domain : whatptr);
478
479 /* check for more dots */
480 dotp = strchr(whatptr, '.');
481 if(!dotp)
482 domain=":";
483
484 if(!domain
485 || (is_ip && !strcmp(whatptr, domain))
486 || (!is_ip && tailmatch(whatptr, domain))) {
487 strstore(&co->domain, whatptr);
488 if(!co->domain) {
489 badcookie = TRUE;
490 break;
491 }
492 if(!is_ip)
493 co->tailmatch=TRUE; /* we always do that if the domain name was
494 given */
495 }
496 else {
497 /* we did not get a tailmatch and then the attempted set domain
498 is not a domain to which the current host belongs. Mark as
499 bad. */
500 badcookie=TRUE;
501 infof(data, "skipped cookie with bad tailmatch domain: %s\n",
502 whatptr);
503 }
504 }
505 else if(Curl_raw_equal("version", name)) {
506 strstore(&co->version, whatptr);
507 if(!co->version) {
508 badcookie = TRUE;
509 break;
510 }
511 }
512 else if(Curl_raw_equal("max-age", name)) {
513 /* Defined in RFC2109:
514
515 Optional. The Max-Age attribute defines the lifetime of the
516 cookie, in seconds. The delta-seconds value is a decimal non-
517 negative integer. After delta-seconds seconds elapse, the
518 client should discard the cookie. A value of zero means the
519 cookie should be discarded immediately.
520
521 */
522 strstore(&co->maxage, whatptr);
523 if(!co->maxage) {
524 badcookie = TRUE;
525 break;
526 }
527 }
528 else if(Curl_raw_equal("expires", name)) {
529 strstore(&co->expirestr, whatptr);
530 if(!co->expirestr) {
531 badcookie = TRUE;
532 break;
533 }
534 }
535 else if(!co->name) {
536 co->name = strdup(name);
537 co->value = strdup(whatptr);
538 if(!co->name || !co->value) {
539 badcookie = TRUE;
540 break;
541 }
542 }
543 /*
544 else this is the second (or more) name we don't know
545 about! */
546 }
547 else {
548 /* this is an "illegal" <what>=<this> pair */
549 }
550
551 if(!semiptr || !*semiptr) {
552 /* we already know there are no more cookies */
553 semiptr = NULL;
554 continue;
555 }
556
557 ptr=semiptr+1;
558 while(*ptr && ISBLANK(*ptr))
559 ptr++;
560 semiptr=strchr(ptr, ';'); /* now, find the next semicolon */
561
562 if(!semiptr && *ptr)
563 /* There are no more semicolons, but there's a final name=value pair
564 coming up */
565 semiptr=strchr(ptr, '\0');
566 } while(semiptr);
567
568 if(co->maxage) {
569 co->expires =
570 curlx_strtoofft((*co->maxage=='\"')?
571 &co->maxage[1]:&co->maxage[0], NULL, 10);
572 if(CURL_OFF_T_MAX - now < co->expires)
573 /* avoid overflow */
574 co->expires = CURL_OFF_T_MAX;
575 else
576 co->expires += now;
577 }
578 else if(co->expirestr) {
579 /* Note that if the date couldn't get parsed for whatever reason,
580 the cookie will be treated as a session cookie */
581 co->expires = curl_getdate(co->expirestr, NULL);
582
583 /* Session cookies have expires set to 0 so if we get that back
584 from the date parser let's add a second to make it a
585 non-session cookie */
586 if(co->expires == 0)
587 co->expires = 1;
588 else if(co->expires < 0)
589 co->expires = 0;
590 }
591
592 if(!badcookie && !co->domain) {
593 if(domain) {
594 /* no domain was given in the header line, set the default */
595 co->domain=strdup(domain);
596 if(!co->domain)
597 badcookie = TRUE;
598 }
599 }
600
601 if(!badcookie && !co->path && path) {
602 /* No path was given in the header line, set the default.
603 Note that the passed-in path to this function MAY have a '?' and
604 following part that MUST not be stored as part of the path. */
605 char *queryp = strchr(path, '?');
606
607 /* queryp is where the interesting part of the path ends, so now we
608 want to the find the last */
609 char *endslash;
610 if(!queryp)
611 endslash = strrchr(path, '/');
612 else
613 endslash = memrchr(path, '/', (size_t)(queryp - path));
614 if(endslash) {
615 size_t pathlen = (size_t)(endslash-path+1); /* include ending slash */
616 co->path=malloc(pathlen+1); /* one extra for the zero byte */
617 if(co->path) {
618 memcpy(co->path, path, pathlen);
619 co->path[pathlen]=0; /* zero terminate */
620 co->spath = sanitize_cookie_path(co->path);
621 if(!co->spath)
622 badcookie = TRUE; /* out of memory bad */
623 }
624 else
625 badcookie = TRUE;
626 }
627 }
628
629 free(what);
630
631 if(badcookie || !co->name) {
632 /* we didn't get a cookie name or a bad one,
633 this is an illegal line, bail out */
634 freecookie(co);
635 return NULL;
636 }
637
638 }
639 else {
640 /* This line is NOT a HTTP header style line, we do offer support for
641 reading the odd netscape cookies-file format here */
642 char *ptr;
643 char *firstptr;
644 char *tok_buf=NULL;
645 int fields;
646
647 /* IE introduced HTTP-only cookies to prevent XSS attacks. Cookies
648 marked with httpOnly after the domain name are not accessible
649 from javascripts, but since curl does not operate at javascript
650 level, we include them anyway. In Firefox's cookie files, these
651 lines are preceded with #HttpOnly_ and then everything is
652 as usual, so we skip 10 characters of the line..
653 */
654 if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
655 lineptr += 10;
656 co->httponly = TRUE;
657 }
658
659 if(lineptr[0]=='#') {
660 /* don't even try the comments */
661 free(co);
662 return NULL;
663 }
664 /* strip off the possible end-of-line characters */
665 ptr=strchr(lineptr, '\r');
666 if(ptr)
667 *ptr=0; /* clear it */
668 ptr=strchr(lineptr, '\n');
669 if(ptr)
670 *ptr=0; /* clear it */
671
672 firstptr=strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */
673
674 /* Now loop through the fields and init the struct we already have
675 allocated */
676 for(ptr=firstptr, fields=0; ptr && !badcookie;
677 ptr=strtok_r(NULL, "\t", &tok_buf), fields++) {
678 switch(fields) {
679 case 0:
680 if(ptr[0]=='.') /* skip preceding dots */
681 ptr++;
682 co->domain = strdup(ptr);
683 if(!co->domain)
684 badcookie = TRUE;
685 break;
686 case 1:
687 /* This field got its explanation on the 23rd of May 2001 by
688 Andr�s Garc�a:
689
690 flag: A TRUE/FALSE value indicating if all machines within a given
691 domain can access the variable. This value is set automatically by
692 the browser, depending on the value you set for the domain.
693
694 As far as I can see, it is set to true when the cookie says
695 .domain.com and to false when the domain is complete www.domain.com
696 */
697 co->tailmatch = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE;
698 break;
699 case 2:
700 /* It turns out, that sometimes the file format allows the path
701 field to remain not filled in, we try to detect this and work
702 around it! Andr�s Garc�a made us aware of this... */
703 if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
704 /* only if the path doesn't look like a boolean option! */
705 co->path = strdup(ptr);
706 if(!co->path)
707 badcookie = TRUE;
708 else {
709 co->spath = sanitize_cookie_path(co->path);
710 if(!co->spath) {
711 badcookie = TRUE; /* out of memory bad */
712 }
713 }
714 break;
715 }
716 /* this doesn't look like a path, make one up! */
717 co->path = strdup("/");
718 if(!co->path)
719 badcookie = TRUE;
720 co->spath = strdup("/");
721 if(!co->spath)
722 badcookie = TRUE;
723 fields++; /* add a field and fall down to secure */
724 /* FALLTHROUGH */
725 case 3:
726 co->secure = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE;
727 break;
728 case 4:
729 co->expires = curlx_strtoofft(ptr, NULL, 10);
730 break;
731 case 5:
732 co->name = strdup(ptr);
733 if(!co->name)
734 badcookie = TRUE;
735 break;
736 case 6:
737 co->value = strdup(ptr);
738 if(!co->value)
739 badcookie = TRUE;
740 break;
741 }
742 }
743 if(6 == fields) {
744 /* we got a cookie with blank contents, fix it */
745 co->value = strdup("");
746 if(!co->value)
747 badcookie = TRUE;
748 else
749 fields++;
750 }
751
752 if(!badcookie && (7 != fields))
753 /* we did not find the sufficient number of fields */
754 badcookie = TRUE;
755
756 if(badcookie) {
757 freecookie(co);
758 return NULL;
759 }
760
761 }
762
763 if(!c->running && /* read from a file */
764 c->newsession && /* clean session cookies */
765 !co->expires) { /* this is a session cookie since it doesn't expire! */
766 freecookie(co);
767 return NULL;
768 }
769
770 co->livecookie = c->running;
771
772 /* now, we have parsed the incoming line, we must now check if this
773 superceeds an already existing cookie, which it may if the previous have
774 the same domain and path as this */
775
776 /* at first, remove expired cookies */
777 remove_expired(c);
778
779 clist = c->cookies;
780 replace_old = FALSE;
781 while(clist) {
782 if(Curl_raw_equal(clist->name, co->name)) {
783 /* the names are identical */
784
785 if(clist->domain && co->domain) {
786 if(Curl_raw_equal(clist->domain, co->domain))
787 /* The domains are identical */
788 replace_old=TRUE;
789 }
790 else if(!clist->domain && !co->domain)
791 replace_old = TRUE;
792
793 if(replace_old) {
794 /* the domains were identical */
795
796 if(clist->spath && co->spath) {
797 if(Curl_raw_equal(clist->spath, co->spath)) {
798 replace_old = TRUE;
799 }
800 else
801 replace_old = FALSE;
802 }
803 else if(!clist->spath && !co->spath)
804 replace_old = TRUE;
805 else
806 replace_old = FALSE;
807
808 }
809
810 if(replace_old && !co->livecookie && clist->livecookie) {
811 /* Both cookies matched fine, except that the already present
812 cookie is "live", which means it was set from a header, while
813 the new one isn't "live" and thus only read from a file. We let
814 live cookies stay alive */
815
816 /* Free the newcomer and get out of here! */
817 freecookie(co);
818 return NULL;
819 }
820
821 if(replace_old) {
822 co->next = clist->next; /* get the next-pointer first */
823
824 /* then free all the old pointers */
825 free(clist->name);
826 free(clist->value);
827 free(clist->domain);
828 free(clist->path);
829 free(clist->spath);
830 free(clist->expirestr);
831 free(clist->version);
832 free(clist->maxage);
833
834 *clist = *co; /* then store all the new data */
835
836 free(co); /* free the newly alloced memory */
837 co = clist; /* point to the previous struct instead */
838
839 /* We have replaced a cookie, now skip the rest of the list but
840 make sure the 'lastc' pointer is properly set */
841 do {
842 lastc = clist;
843 clist = clist->next;
844 } while(clist);
845 break;
846 }
847 }
848 lastc = clist;
849 clist = clist->next;
850 }
851
852 if(c->running)
853 /* Only show this when NOT reading the cookies from a file */
854 infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
855 "expire %" CURL_FORMAT_CURL_OFF_T "\n",
856 replace_old?"Replaced":"Added", co->name, co->value,
857 co->domain, co->path, co->expires);
858
859 if(!replace_old) {
860 /* then make the last item point on this new one */
861 if(lastc)
862 lastc->next = co;
863 else
864 c->cookies = co;
865 c->numcookies++; /* one more cookie in the jar */
866 }
867
868 return co;
869 }
870
871 /*****************************************************************************
872 *
873 * Curl_cookie_init()
874 *
875 * Inits a cookie struct to read data from a local file. This is always
876 * called before any cookies are set. File may be NULL.
877 *
878 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
879 *
880 * Returns NULL on out of memory. Invalid cookies are ignored.
881 ****************************************************************************/
Curl_cookie_init(struct SessionHandle * data,const char * file,struct CookieInfo * inc,bool newsession)882 struct CookieInfo *Curl_cookie_init(struct SessionHandle *data,
883 const char *file,
884 struct CookieInfo *inc,
885 bool newsession)
886 {
887 struct CookieInfo *c;
888 FILE *fp = NULL;
889 bool fromfile=TRUE;
890 char *line = NULL;
891
892 if(NULL == inc) {
893 /* we didn't get a struct, create one */
894 c = calloc(1, sizeof(struct CookieInfo));
895 if(!c)
896 return NULL; /* failed to get memory */
897 c->filename = strdup(file?file:"none"); /* copy the name just in case */
898 if(!c->filename)
899 goto fail; /* failed to get memory */
900 }
901 else {
902 /* we got an already existing one, use that */
903 c = inc;
904 }
905 c->running = FALSE; /* this is not running, this is init */
906
907 if(file && strequal(file, "-")) {
908 fp = stdin;
909 fromfile=FALSE;
910 }
911 else if(file && !*file) {
912 /* points to a "" string */
913 fp = NULL;
914 }
915 else
916 fp = file?fopen(file, FOPEN_READTEXT):NULL;
917
918 c->newsession = newsession; /* new session? */
919
920 if(fp) {
921 char *lineptr;
922 bool headerline;
923
924 line = malloc(MAX_COOKIE_LINE);
925 if(!line)
926 goto fail;
927 while(fgets(line, MAX_COOKIE_LINE, fp)) {
928 if(checkprefix("Set-Cookie:", line)) {
929 /* This is a cookie line, get it! */
930 lineptr=&line[11];
931 headerline=TRUE;
932 }
933 else {
934 lineptr=line;
935 headerline=FALSE;
936 }
937 while(*lineptr && ISBLANK(*lineptr))
938 lineptr++;
939
940 Curl_cookie_add(data, c, headerline, lineptr, NULL, NULL);
941 }
942 free(line); /* free the line buffer */
943
944 if(fromfile)
945 fclose(fp);
946 }
947
948 c->running = TRUE; /* now, we're running */
949
950 return c;
951
952 fail:
953 free(line);
954 if(!inc)
955 /* Only clean up if we allocated it here, as the original could still be in
956 * use by a share handle */
957 Curl_cookie_cleanup(c);
958 if(fromfile && fp)
959 fclose(fp);
960 return NULL; /* out of memory */
961 }
962
963 /* sort this so that the longest path gets before the shorter path */
cookie_sort(const void * p1,const void * p2)964 static int cookie_sort(const void *p1, const void *p2)
965 {
966 struct Cookie *c1 = *(struct Cookie **)p1;
967 struct Cookie *c2 = *(struct Cookie **)p2;
968 size_t l1, l2;
969
970 /* 1 - compare cookie path lengths */
971 l1 = c1->path ? strlen(c1->path) : 0;
972 l2 = c2->path ? strlen(c2->path) : 0;
973
974 if(l1 != l2)
975 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
976
977 /* 2 - compare cookie domain lengths */
978 l1 = c1->domain ? strlen(c1->domain) : 0;
979 l2 = c2->domain ? strlen(c2->domain) : 0;
980
981 if(l1 != l2)
982 return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */
983
984 /* 3 - compare cookie names */
985 if(c1->name && c2->name)
986 return strcmp(c1->name, c2->name);
987
988 /* sorry, can't be more deterministic */
989 return 0;
990 }
991
992 /*****************************************************************************
993 *
994 * Curl_cookie_getlist()
995 *
996 * For a given host and path, return a linked list of cookies that the
997 * client should send to the server if used now. The secure boolean informs
998 * the cookie if a secure connection is achieved or not.
999 *
1000 * It shall only return cookies that haven't expired.
1001 *
1002 ****************************************************************************/
1003
Curl_cookie_getlist(struct CookieInfo * c,const char * host,const char * path,bool secure)1004 struct Cookie *Curl_cookie_getlist(struct CookieInfo *c,
1005 const char *host, const char *path,
1006 bool secure)
1007 {
1008 struct Cookie *newco;
1009 struct Cookie *co;
1010 time_t now = time(NULL);
1011 struct Cookie *mainco=NULL;
1012 size_t matches = 0;
1013 bool is_ip;
1014
1015 if(!c || !c->cookies)
1016 return NULL; /* no cookie struct or no cookies in the struct */
1017
1018 /* at first, remove expired cookies */
1019 remove_expired(c);
1020
1021 /* check if host is an IP(v4|v6) address */
1022 is_ip = isip(host);
1023
1024 co = c->cookies;
1025
1026 while(co) {
1027 /* only process this cookie if it is not expired or had no expire
1028 date AND that if the cookie requires we're secure we must only
1029 continue if we are! */
1030 if((!co->expires || (co->expires > now)) &&
1031 (co->secure?secure:TRUE)) {
1032
1033 /* now check if the domain is correct */
1034 if(!co->domain ||
1035 (co->tailmatch && !is_ip && tailmatch(co->domain, host)) ||
1036 ((!co->tailmatch || is_ip) && Curl_raw_equal(host, co->domain)) ) {
1037 /* the right part of the host matches the domain stuff in the
1038 cookie data */
1039
1040 /* now check the left part of the path with the cookies path
1041 requirement */
1042 if(!co->spath || pathmatch(co->spath, path) ) {
1043
1044 /* and now, we know this is a match and we should create an
1045 entry for the return-linked-list */
1046
1047 newco = malloc(sizeof(struct Cookie));
1048 if(newco) {
1049 /* first, copy the whole source cookie: */
1050 memcpy(newco, co, sizeof(struct Cookie));
1051
1052 /* then modify our next */
1053 newco->next = mainco;
1054
1055 /* point the main to us */
1056 mainco = newco;
1057
1058 matches++;
1059 }
1060 else {
1061 fail:
1062 /* failure, clear up the allocated chain and return NULL */
1063 while(mainco) {
1064 co = mainco->next;
1065 free(mainco);
1066 mainco = co;
1067 }
1068
1069 return NULL;
1070 }
1071 }
1072 }
1073 }
1074 co = co->next;
1075 }
1076
1077 if(matches) {
1078 /* Now we need to make sure that if there is a name appearing more than
1079 once, the longest specified path version comes first. To make this
1080 the swiftest way, we just sort them all based on path length. */
1081 struct Cookie **array;
1082 size_t i;
1083
1084 /* alloc an array and store all cookie pointers */
1085 array = malloc(sizeof(struct Cookie *) * matches);
1086 if(!array)
1087 goto fail;
1088
1089 co = mainco;
1090
1091 for(i=0; co; co = co->next)
1092 array[i++] = co;
1093
1094 /* now sort the cookie pointers in path length order */
1095 qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
1096
1097 /* remake the linked list order according to the new order */
1098
1099 mainco = array[0]; /* start here */
1100 for(i=0; i<matches-1; i++)
1101 array[i]->next = array[i+1];
1102 array[matches-1]->next = NULL; /* terminate the list */
1103
1104 free(array); /* remove the temporary data again */
1105 }
1106
1107 return mainco; /* return the new list */
1108 }
1109
1110 /*****************************************************************************
1111 *
1112 * Curl_cookie_clearall()
1113 *
1114 * Clear all existing cookies and reset the counter.
1115 *
1116 ****************************************************************************/
Curl_cookie_clearall(struct CookieInfo * cookies)1117 void Curl_cookie_clearall(struct CookieInfo *cookies)
1118 {
1119 if(cookies) {
1120 Curl_cookie_freelist(cookies->cookies, TRUE);
1121 cookies->cookies = NULL;
1122 cookies->numcookies = 0;
1123 }
1124 }
1125
1126 /*****************************************************************************
1127 *
1128 * Curl_cookie_freelist()
1129 *
1130 * Free a list of cookies previously returned by Curl_cookie_getlist();
1131 *
1132 * The 'cookiestoo' argument tells this function whether to just free the
1133 * list or actually also free all cookies within the list as well.
1134 *
1135 ****************************************************************************/
1136
Curl_cookie_freelist(struct Cookie * co,bool cookiestoo)1137 void Curl_cookie_freelist(struct Cookie *co, bool cookiestoo)
1138 {
1139 struct Cookie *next;
1140 while(co) {
1141 next = co->next;
1142 if(cookiestoo)
1143 freecookie(co);
1144 else
1145 free(co); /* we only free the struct since the "members" are all just
1146 pointed out in the main cookie list! */
1147 co = next;
1148 }
1149 }
1150
1151
1152 /*****************************************************************************
1153 *
1154 * Curl_cookie_clearsess()
1155 *
1156 * Free all session cookies in the cookies list.
1157 *
1158 ****************************************************************************/
Curl_cookie_clearsess(struct CookieInfo * cookies)1159 void Curl_cookie_clearsess(struct CookieInfo *cookies)
1160 {
1161 struct Cookie *first, *curr, *next, *prev = NULL;
1162
1163 if(!cookies || !cookies->cookies)
1164 return;
1165
1166 first = curr = prev = cookies->cookies;
1167
1168 for(; curr; curr = next) {
1169 next = curr->next;
1170 if(!curr->expires) {
1171 if(first == curr)
1172 first = next;
1173
1174 if(prev == curr)
1175 prev = next;
1176 else
1177 prev->next = next;
1178
1179 freecookie(curr);
1180 cookies->numcookies--;
1181 }
1182 else
1183 prev = curr;
1184 }
1185
1186 cookies->cookies = first;
1187 }
1188
1189
1190 /*****************************************************************************
1191 *
1192 * Curl_cookie_cleanup()
1193 *
1194 * Free a "cookie object" previous created with Curl_cookie_init().
1195 *
1196 ****************************************************************************/
Curl_cookie_cleanup(struct CookieInfo * c)1197 void Curl_cookie_cleanup(struct CookieInfo *c)
1198 {
1199 if(c) {
1200 free(c->filename);
1201 Curl_cookie_freelist(c->cookies, TRUE);
1202 free(c); /* free the base struct as well */
1203 }
1204 }
1205
1206 /* get_netscape_format()
1207 *
1208 * Formats a string for Netscape output file, w/o a newline at the end.
1209 *
1210 * Function returns a char * to a formatted line. Has to be free()d
1211 */
get_netscape_format(const struct Cookie * co)1212 static char *get_netscape_format(const struct Cookie *co)
1213 {
1214 return aprintf(
1215 "%s" /* httponly preamble */
1216 "%s%s\t" /* domain */
1217 "%s\t" /* tailmatch */
1218 "%s\t" /* path */
1219 "%s\t" /* secure */
1220 "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */
1221 "%s\t" /* name */
1222 "%s", /* value */
1223 co->httponly?"#HttpOnly_":"",
1224 /* Make sure all domains are prefixed with a dot if they allow
1225 tailmatching. This is Mozilla-style. */
1226 (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
1227 co->domain?co->domain:"unknown",
1228 co->tailmatch?"TRUE":"FALSE",
1229 co->path?co->path:"/",
1230 co->secure?"TRUE":"FALSE",
1231 co->expires,
1232 co->name,
1233 co->value?co->value:"");
1234 }
1235
1236 /*
1237 * cookie_output()
1238 *
1239 * Writes all internally known cookies to the specified file. Specify
1240 * "-" as file name to write to stdout.
1241 *
1242 * The function returns non-zero on write failure.
1243 */
cookie_output(struct CookieInfo * c,const char * dumphere)1244 static int cookie_output(struct CookieInfo *c, const char *dumphere)
1245 {
1246 struct Cookie *co;
1247 FILE *out;
1248 bool use_stdout=FALSE;
1249
1250 if((NULL == c) || (0 == c->numcookies))
1251 /* If there are no known cookies, we don't write or even create any
1252 destination file */
1253 return 0;
1254
1255 /* at first, remove expired cookies */
1256 remove_expired(c);
1257
1258 if(strequal("-", dumphere)) {
1259 /* use stdout */
1260 out = stdout;
1261 use_stdout=TRUE;
1262 }
1263 else {
1264 out = fopen(dumphere, FOPEN_WRITETEXT);
1265 if(!out)
1266 return 1; /* failure */
1267 }
1268
1269 if(c) {
1270 char *format_ptr;
1271
1272 fputs("# Netscape HTTP Cookie File\n"
1273 "# http://curl.haxx.se/docs/http-cookies.html\n"
1274 "# This file was generated by libcurl! Edit at your own risk.\n\n",
1275 out);
1276 co = c->cookies;
1277
1278 while(co) {
1279 if(!co->domain)
1280 continue;
1281 format_ptr = get_netscape_format(co);
1282 if(format_ptr == NULL) {
1283 fprintf(out, "#\n# Fatal libcurl error\n");
1284 if(!use_stdout)
1285 fclose(out);
1286 return 1;
1287 }
1288 fprintf(out, "%s\n", format_ptr);
1289 free(format_ptr);
1290 co=co->next;
1291 }
1292 }
1293
1294 if(!use_stdout)
1295 fclose(out);
1296
1297 return 0;
1298 }
1299
Curl_cookie_list(struct SessionHandle * data)1300 struct curl_slist *Curl_cookie_list(struct SessionHandle *data)
1301 {
1302 struct curl_slist *list = NULL;
1303 struct curl_slist *beg;
1304 struct Cookie *c;
1305 char *line;
1306
1307 if((data->cookies == NULL) ||
1308 (data->cookies->numcookies == 0))
1309 return NULL;
1310
1311 c = data->cookies->cookies;
1312
1313 while(c) {
1314 if(!c->domain)
1315 continue;
1316 line = get_netscape_format(c);
1317 if(!line) {
1318 curl_slist_free_all(list);
1319 return NULL;
1320 }
1321 beg = Curl_slist_append_nodup(list, line);
1322 if(!beg) {
1323 free(line);
1324 curl_slist_free_all(list);
1325 return NULL;
1326 }
1327 list = beg;
1328 c = c->next;
1329 }
1330
1331 return list;
1332 }
1333
Curl_flush_cookies(struct SessionHandle * data,int cleanup)1334 void Curl_flush_cookies(struct SessionHandle *data, int cleanup)
1335 {
1336 if(data->set.str[STRING_COOKIEJAR]) {
1337 if(data->change.cookielist) {
1338 /* If there is a list of cookie files to read, do it first so that
1339 we have all the told files read before we write the new jar.
1340 Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */
1341 Curl_cookie_loadfiles(data);
1342 }
1343
1344 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1345
1346 /* if we have a destination file for all the cookies to get dumped to */
1347 if(cookie_output(data->cookies, data->set.str[STRING_COOKIEJAR]))
1348 infof(data, "WARNING: failed to save cookies in %s\n",
1349 data->set.str[STRING_COOKIEJAR]);
1350 }
1351 else {
1352 if(cleanup && data->change.cookielist) {
1353 /* since nothing is written, we can just free the list of cookie file
1354 names */
1355 curl_slist_free_all(data->change.cookielist); /* clean up list */
1356 data->change.cookielist = NULL;
1357 }
1358 Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
1359 }
1360
1361 if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
1362 Curl_cookie_cleanup(data->cookies);
1363 }
1364 Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
1365 }
1366
1367 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
1368