1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * $Id: cookie.c,v 1.2 2007-03-15 19:22:13 andy Exp $
22 ***************************************************************************/
27 RECEIVING COOKIE INFORMATION
28 ============================
30 struct CookieInfo *cookie_init(char *file);
32 Inits a cookie struct to store data in a local file. This is always
33 called before any cookies are set.
35 int cookies_set(struct CookieInfo *cookie, char *cookie_line);
37 The 'cookie_line' parameter is a full "Set-cookie:" line as
38 received from a server.
40 The function need to replace previously stored lines that this new
43 It may remove lines that are expired.
45 It should return an indication of success/error.
48 SENDING COOKIE INFORMATION
49 ==========================
51 struct Cookies *cookie_getlist(struct CookieInfo *cookie,
52 char *host, char *path, bool secure);
54 For a given host and path, return a linked list of cookies that
55 the client should send to the server if used now. The secure
56 boolean informs the cookie if a secure connection is achieved or
59 It shall only return cookies that haven't expired.
62 Example set of cookies:
64 Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure
65 Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
66 domain=.fidelity.com; path=/ftgw; secure
67 Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
68 domain=.fidelity.com; path=/; secure
69 Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
70 domain=.fidelity.com; path=/; secure
71 Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
72 domain=.fidelity.com; path=/; secure
73 Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
74 domain=.fidelity.com; path=/; secure
76 Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
77 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
83 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
88 #define _MPRINTF_REPLACE /* without this on windows OS we get undefined reference to snprintf */
89 #include <curl/mprintf.h>
98 #include "strtoofft.h"
100 /* The last #include file should be: */
102 #include "memdebug.h"
105 #define my_isspace(x) ((x == ' ') || (x == '\t'))
107 static void freecookie(struct Cookie
*co
)
127 static bool tailmatch(const char *little
, const char *bigone
)
129 size_t littlelen
= strlen(little
);
130 size_t biglen
= strlen(bigone
);
132 if(littlelen
> biglen
)
135 return (bool)strequal(little
, bigone
+biglen
-littlelen
);
139 * Load cookies from all given cookie files (CURLOPT_COOKIEFILE).
141 void Curl_cookie_loadfiles(struct SessionHandle
*data
)
143 struct curl_slist
*list
= data
->change
.cookielist
;
145 Curl_share_lock(data
, CURL_LOCK_DATA_COOKIE
, CURL_LOCK_ACCESS_SINGLE
);
147 data
->cookies
= Curl_cookie_init(data
,
150 data
->set
.cookiesession
);
153 Curl_share_unlock(data
, CURL_LOCK_DATA_COOKIE
);
154 curl_slist_free_all(data
->change
.cookielist
); /* clean up list */
155 data
->change
.cookielist
= NULL
; /* don't do this again! */
159 /****************************************************************************
163 * Add a single cookie line to the cookie keeping object.
165 ***************************************************************************/
168 Curl_cookie_add(struct SessionHandle
*data
,
169 /* The 'data' pointer here may be NULL at times, and thus
170 must only be used very carefully for things that can deal
171 with data being NULL. Such as infof() and similar */
173 struct CookieInfo
*c
,
174 bool httpheader
, /* TRUE if HTTP header-style line */
175 char *lineptr
, /* first character of the line */
176 char *domain
, /* default domain */
177 char *path
) /* full path used when this cookie is set,
178 used to get default path for the cookie
181 struct Cookie
*clist
;
187 struct Cookie
*lastc
=NULL
;
188 time_t now
= time(NULL
);
189 bool replace_old
= FALSE
;
190 bool badcookie
= FALSE
; /* cookies are good by default. mmmmm yummy */
192 /* First, alloc and init a new struct for it */
193 co
= (struct Cookie
*)calloc(sizeof(struct Cookie
), 1);
195 return NULL
; /* bail out if we're this low on memory */
198 /* This line was read off a HTTP-header */
201 what
= malloc(MAX_COOKIE_LINE
);
207 semiptr
=strchr(lineptr
, ';'); /* first, find a semicolon */
209 while(*lineptr
&& my_isspace(*lineptr
))
214 /* we have a <what>=<this> pair or a 'secure' word here */
215 sep
= strchr(ptr
, '=');
216 if(sep
&& (!semiptr
|| (semiptr
>sep
)) ) {
218 * There is a = sign and if there was a semicolon too, which make sure
219 * that the semicolon comes _after_ the equal sign.
222 name
[0]=what
[0]=0; /* init the buffers */
223 if(1 <= sscanf(ptr
, "%" MAX_NAME_TXT
"[^;=]=%"
224 MAX_COOKIE_LINE_TXT
"[^;\r\n]",
226 /* this is a <name>=<what> pair */
230 /* Strip off trailing whitespace from the 'what' */
231 size_t len
=strlen(what
);
232 while(len
&& my_isspace(what
[len
-1])) {
237 /* Skip leading whitespace from the 'what' */
239 while(my_isspace(*whatptr
)) {
243 if(strequal("path", name
)) {
244 co
->path
=strdup(whatptr
);
246 badcookie
= TRUE
; /* out of memory bad */
250 else if(strequal("domain", name
)) {
251 /* note that this name may or may not have a preceeding dot, but
252 we don't care about that, we treat the names the same anyway */
254 const char *domptr
=whatptr
;
257 /* Count the dots, we need to make sure that there are enough
260 if('.' == whatptr
[0])
261 /* don't count the initial dot, assume it */
265 domptr
= strchr(domptr
, '.');
272 /* The original Netscape cookie spec defined that this domain name
273 MUST have three dots (or two if one of the seven holy TLDs),
274 but it seems that these kinds of cookies are in use "out there"
275 so we cannot be that strict. I've therefore lowered the check
276 to not allow less than two dots. */
279 /* Received and skipped a cookie with a domain using too few
281 badcookie
=TRUE
; /* mark this as a bad cookie */
282 infof(data
, "skipped cookie with illegal dotcount domain: %s\n",
286 /* Now, we make sure that our host is within the given domain,
287 or the given domain is not valid and thus cannot be set. */
289 if('.' == whatptr
[0])
290 whatptr
++; /* ignore preceeding dot */
292 if(!domain
|| tailmatch(whatptr
, domain
)) {
293 const char *tailptr
=whatptr
;
294 if(tailptr
[0] == '.')
296 co
->domain
=strdup(tailptr
); /* don't prefix w/dots
302 co
->tailmatch
=TRUE
; /* we always do that if the domain name was
306 /* we did not get a tailmatch and then the attempted set domain
307 is not a domain to which the current host belongs. Mark as
310 infof(data
, "skipped cookie with bad tailmatch domain: %s\n",
315 else if(strequal("version", name
)) {
316 co
->version
=strdup(whatptr
);
322 else if(strequal("max-age", name
)) {
323 /* Defined in RFC2109:
325 Optional. The Max-Age attribute defines the lifetime of the
326 cookie, in seconds. The delta-seconds value is a decimal non-
327 negative integer. After delta-seconds seconds elapse, the
328 client should discard the cookie. A value of zero means the
329 cookie should be discarded immediately.
332 co
->maxage
= strdup(whatptr
);
338 atoi((*co
->maxage
=='\"')?&co
->maxage
[1]:&co
->maxage
[0]) + (long)now
;
340 else if(strequal("expires", name
)) {
341 co
->expirestr
=strdup(whatptr
);
346 co
->expires
= curl_getdate(what
, &now
);
349 co
->name
= strdup(name
);
350 co
->value
= strdup(whatptr
);
351 if(!co
->name
|| !co
->value
) {
357 else this is the second (or more) name we don't know
361 /* this is an "illegal" <what>=<this> pair */
365 if(sscanf(ptr
, "%" MAX_COOKIE_LINE_TXT
"[^;\r\n]",
367 if(strequal("secure", what
))
370 unsupported keyword without assign! */
374 if(!semiptr
|| !*semiptr
) {
375 /* we already know there are no more cookies */
381 while(ptr
&& *ptr
&& my_isspace(*ptr
))
383 semiptr
=strchr(ptr
, ';'); /* now, find the next semicolon */
386 /* There are no more semicolons, but there's a final name=value pair
388 semiptr
=strchr(ptr
, '\0');
391 if(!badcookie
&& !co
->domain
) {
393 /* no domain was given in the header line, set the default */
394 co
->domain
=strdup(domain
);
400 if(!badcookie
&& !co
->path
&& path
) {
401 /* no path was given in the header line, set the default */
402 char *endslash
= strrchr(path
, '/');
404 size_t pathlen
= endslash
-path
+1; /* include the ending slash */
405 co
->path
=malloc(pathlen
+1); /* one extra for the zero byte */
407 memcpy(co
->path
, path
, pathlen
);
408 co
->path
[pathlen
]=0; /* zero terminate */
417 if(badcookie
|| !co
->name
) {
418 /* we didn't get a cookie name or a bad one,
419 this is an illegal line, bail out */
426 /* This line is NOT a HTTP header style line, we do offer support for
427 reading the odd netscape cookies-file format here */
432 if(lineptr
[0]=='#') {
433 /* don't even try the comments */
437 /* strip off the possible end-of-line characters */
438 ptr
=strchr(lineptr
, '\r');
440 *ptr
=0; /* clear it */
441 ptr
=strchr(lineptr
, '\n');
443 *ptr
=0; /* clear it */
445 firstptr
=strtok_r(lineptr
, "\t", &tok_buf
); /* tokenize it on the TAB */
447 /* Here's a quick check to eliminate normal HTTP-headers from this */
448 if(!firstptr
|| strchr(firstptr
, ':')) {
453 /* Now loop through the fields and init the struct we already have
455 for(ptr
=firstptr
, fields
=0; ptr
&& !badcookie
;
456 ptr
=strtok_r(NULL
, "\t", &tok_buf
), fields
++) {
459 if(ptr
[0]=='.') /* skip preceeding dots */
461 co
->domain
= strdup(ptr
);
466 /* This field got its explanation on the 23rd of May 2001 by
469 flag: A TRUE/FALSE value indicating if all machines within a given
470 domain can access the variable. This value is set automatically by
471 the browser, depending on the value you set for the domain.
473 As far as I can see, it is set to true when the cookie says
474 .domain.com and to false when the domain is complete www.domain.com
476 co
->tailmatch
=(bool)strequal(ptr
, "TRUE"); /* store information */
479 /* It turns out, that sometimes the file format allows the path
480 field to remain not filled in, we try to detect this and work
481 around it! Andrés GarcÃa made us aware of this... */
482 if (strcmp("TRUE", ptr
) && strcmp("FALSE", ptr
)) {
483 /* only if the path doesn't look like a boolean option! */
484 co
->path
= strdup(ptr
);
489 /* this doesn't look like a path, make one up! */
490 co
->path
= strdup("/");
493 fields
++; /* add a field and fall down to secure */
496 co
->secure
= (bool)strequal(ptr
, "TRUE");
499 co
->expires
= curlx_strtoofft(ptr
, NULL
, 10);
502 co
->name
= strdup(ptr
);
507 co
->value
= strdup(ptr
);
514 /* we got a cookie with blank contents, fix it */
515 co
->value
= strdup("");
522 if(!badcookie
&& (7 != fields
))
523 /* we did not find the sufficient number of fields */
533 if(!c
->running
&& /* read from a file */
534 c
->newsession
&& /* clean session cookies */
535 !co
->expires
) { /* this is a session cookie since it doesn't expire! */
540 co
->livecookie
= c
->running
;
542 /* now, we have parsed the incoming line, we must now check if this
543 superceeds an already existing cookie, which it may if the previous have
544 the same domain and path as this */
549 if(strequal(clist
->name
, co
->name
)) {
550 /* the names are identical */
552 if(clist
->domain
&& co
->domain
) {
553 if(strequal(clist
->domain
, co
->domain
))
554 /* The domains are identical */
557 else if(!clist
->domain
&& !co
->domain
)
561 /* the domains were identical */
563 if(clist
->path
&& co
->path
) {
564 if(strequal(clist
->path
, co
->path
)) {
570 else if(!clist
->path
&& !co
->path
)
577 if(replace_old
&& !co
->livecookie
&& clist
->livecookie
) {
578 /* Both cookies matched fine, except that the already present
579 cookie is "live", which means it was set from a header, while
580 the new one isn't "live" and thus only read from a file. We let
581 live cookies stay alive */
583 /* Free the newcomer and get out of here! */
589 co
->next
= clist
->next
; /* get the next-pointer first */
591 /* then free all the old pointers */
601 free(clist
->expirestr
);
604 free(clist
->version
);
608 *clist
= *co
; /* then store all the new data */
610 free(co
); /* free the newly alloced memory */
611 co
= clist
; /* point to the previous struct instead */
613 /* We have replaced a cookie, now skip the rest of the list but
614 make sure the 'lastc' pointer is properly set */
627 /* Only show this when NOT reading the cookies from a file */
628 infof(data
, "%s cookie %s=\"%s\" for domain %s, path %s, expire %d\n",
629 replace_old
?"Replaced":"Added", co
->name
, co
->value
,
630 co
->domain
, co
->path
, co
->expires
);
633 /* then make the last item point on this new one */
640 c
->numcookies
++; /* one more cookie in the jar */
644 /*****************************************************************************
648 * Inits a cookie struct to read data from a local file. This is always
649 * called before any cookies are set. File may be NULL.
651 * If 'newsession' is TRUE, discard all "session cookies" on read from file.
653 ****************************************************************************/
654 struct CookieInfo
*Curl_cookie_init(struct SessionHandle
*data
,
656 struct CookieInfo
*inc
,
659 struct CookieInfo
*c
;
664 /* we didn't get a struct, create one */
665 c
= (struct CookieInfo
*)calloc(1, sizeof(struct CookieInfo
));
667 return NULL
; /* failed to get memory */
668 c
->filename
= strdup(file
?file
:"none"); /* copy the name just in case */
671 /* we got an already existing one, use that */
674 c
->running
= FALSE
; /* this is not running, this is init */
676 if(file
&& strequal(file
, "-")) {
680 else if(file
&& !*file
) {
681 /* points to a "" string */
685 fp
= file
?fopen(file
, "r"):NULL
;
687 c
->newsession
= newsession
; /* new session? */
693 char *line
= (char *)malloc(MAX_COOKIE_LINE
);
695 while(fgets(line
, MAX_COOKIE_LINE
, fp
)) {
696 if(checkprefix("Set-Cookie:", line
)) {
697 /* This is a cookie line, get it! */
705 while(*lineptr
&& my_isspace(*lineptr
))
708 Curl_cookie_add(data
, c
, headerline
, lineptr
, NULL
, NULL
);
710 free(line
); /* free the line buffer */
716 c
->running
= TRUE
; /* now, we're running */
721 /*****************************************************************************
723 * Curl_cookie_getlist()
725 * For a given host and path, return a linked list of cookies that the
726 * client should send to the server if used now. The secure boolean informs
727 * the cookie if a secure connection is achieved or not.
729 * It shall only return cookies that haven't expired.
731 ****************************************************************************/
733 struct Cookie
*Curl_cookie_getlist(struct CookieInfo
*c
,
734 char *host
, char *path
, bool secure
)
736 struct Cookie
*newco
;
738 time_t now
= time(NULL
);
739 struct Cookie
*mainco
=NULL
;
741 if(!c
|| !c
->cookies
)
742 return NULL
; /* no cookie struct or no cookies in the struct */
747 /* only process this cookie if it is not expired or had no expire
748 date AND that if the cookie requires we're secure we must only
749 continue if we are! */
750 if( (co
->expires
<=0 || (co
->expires
> now
)) &&
751 (co
->secure
?secure
:TRUE
) ) {
753 /* now check if the domain is correct */
755 (co
->tailmatch
&& tailmatch(co
->domain
, host
)) ||
756 (!co
->tailmatch
&& strequal(host
, co
->domain
)) ) {
757 /* the right part of the host matches the domain stuff in the
760 /* now check the left part of the path with the cookies path
763 /* not using checkprefix() because matching should be
765 !strncmp(co
->path
, path
, strlen(co
->path
)) ) {
767 /* and now, we know this is a match and we should create an
768 entry for the return-linked-list */
770 newco
= (struct Cookie
*)malloc(sizeof(struct Cookie
));
772 /* first, copy the whole source cookie: */
773 memcpy(newco
, co
, sizeof(struct Cookie
));
775 /* then modify our next */
776 newco
->next
= mainco
;
778 /* point the main to us */
782 /* failure, clear up the allocated chain and return NULL */
797 return mainco
; /* return the new list */
800 /*****************************************************************************
802 * Curl_cookie_clearall()
804 * Clear all existing cookies and reset the counter.
806 ****************************************************************************/
807 void Curl_cookie_clearall(struct CookieInfo
*cookies
)
810 Curl_cookie_freelist(cookies
->cookies
);
811 cookies
->cookies
= NULL
;
812 cookies
->numcookies
= 0;
816 /*****************************************************************************
818 * Curl_cookie_freelist()
820 * Free a list of cookies previously returned by Curl_cookie_getlist();
822 ****************************************************************************/
824 void Curl_cookie_freelist(struct Cookie
*co
)
830 free(co
); /* we only free the struct since the "members" are all
838 /*****************************************************************************
840 * Curl_cookie_clearsess()
842 * Free all session cookies in the cookies list.
844 ****************************************************************************/
845 void Curl_cookie_clearsess(struct CookieInfo
*cookies
)
847 struct Cookie
*first
, *curr
, *next
, *prev
= NULL
;
849 if(!cookies
->cookies
)
852 first
= curr
= prev
= cookies
->cookies
;
854 for(; curr
; curr
= next
) {
866 cookies
->numcookies
--;
872 cookies
->cookies
= first
;
876 /*****************************************************************************
878 * Curl_cookie_cleanup()
880 * Free a "cookie object" previous created with cookie_init().
882 ****************************************************************************/
883 void Curl_cookie_cleanup(struct CookieInfo
*c
)
897 free(c
); /* free the base struct as well */
901 /* get_netscape_format()
903 * Formats a string for Netscape output file, w/o a newline at the end.
905 * Function returns a char * to a formatted line. Has to be free()d
907 static char *get_netscape_format(const struct Cookie
*co
)
910 "%s%s\t" /* domain */
911 "%s\t" /* tailmatch */
914 "%" FORMAT_OFF_T
"\t" /* expires */
917 /* Make sure all domains are prefixed with a dot if they allow
918 tailmatching. This is Mozilla-style. */
919 (co
->tailmatch
&& co
->domain
&& co
->domain
[0] != '.')? ".":"",
920 co
->domain
?co
->domain
:"unknown",
921 co
->tailmatch
?"TRUE":"FALSE",
922 co
->path
?co
->path
:"/",
923 co
->secure
?"TRUE":"FALSE",
926 co
->value
?co
->value
:"");
930 * Curl_cookie_output()
932 * Writes all internally known cookies to the specified file. Specify
933 * "-" as file name to write to stdout.
935 * The function returns non-zero on write failure.
937 int Curl_cookie_output(struct CookieInfo
*c
, char *dumphere
)
941 bool use_stdout
=FALSE
;
943 if((NULL
== c
) || (0 == c
->numcookies
))
944 /* If there are no known cookies, we don't write or even create any
948 if(strequal("-", dumphere
)) {
954 out
= fopen(dumphere
, "w");
956 return 1; /* failure */
962 fputs("# Netscape HTTP Cookie File\n"
963 "# http://curlm.haxx.se/rfc/cookie_spec.html\n"
964 "# This file was generated by libcurl! Edit at your own risk.\n\n",
969 format_ptr
= get_netscape_format(co
);
970 if (format_ptr
== NULL
) {
971 fprintf(out
, "#\n# Fatal libcurl error\n");
974 fprintf(out
, "%s\n", format_ptr
);
986 struct curl_slist
*Curl_cookie_list(struct SessionHandle
*data
)
988 struct curl_slist
*list
= NULL
;
989 struct curl_slist
*beg
;
993 if ((data
->cookies
== NULL
) ||
994 (data
->cookies
->numcookies
== 0))
997 c
= data
->cookies
->cookies
;
1001 /* fill the list with _all_ the cookies we know */
1002 line
= get_netscape_format(c
);
1004 /* get_netscape_format returns null only if we run out of memory */
1006 curl_slist_free_all(beg
); /* free some memory */
1009 list
= curl_slist_append(list
, line
);
1017 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */