]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/curl/lib/cookie.c
Various Mac OS X tweaks to get this to build. Probably breaking things.
[icculus/iodoom3.git] / neo / curl / lib / cookie.c
1 /***************************************************************************
2  *                                  _   _ ____  _     
3  *  Project                     ___| | | |  _ \| |    
4  *                             / __| | | | |_) | |    
5  *                            | (__| |_| |  _ <| |___ 
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2004, 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  * $Id: cookie.c,v 1.52 2004/03/10 09:41:37 bagder Exp $
22  ***************************************************************************/
23
24 /***
25
26
27 RECEIVING COOKIE INFORMATION
28 ============================
29
30 struct CookieInfo *cookie_init(char *file);
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 int cookies_set(struct CookieInfo *cookie, char *cookie_line);
36
37         The 'cookie_line' parameter is a full "Set-cookie:" line as
38         received from a server.
39
40         The function need to replace previously stored lines that this new
41         line superceeds.
42
43         It may remove lines that are expired.
44
45         It should return an indication of success/error.
46
47
48 SENDING COOKIE INFORMATION
49 ==========================
50
51 struct Cookies *cookie_getlist(struct CookieInfo *cookie,
52                                char *host, char *path, bool secure);
53
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
57         not.
58
59         It shall only return cookies that haven't expired.
60
61     
62 Example set of cookies:
63     
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
75     Set-cookie:
76     Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
77     13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
78 ****/
79
80
81 #include "setup.h"
82
83 #ifndef CURL_DISABLE_HTTP
84
85 #include <stdlib.h>
86 #include <string.h>
87 #include <ctype.h>
88
89 #include "urldata.h"
90 #include "cookie.h"
91 #include "getdate.h"
92 #include "strequal.h"
93 #include "strtok.h"
94 #include "sendf.h"
95
96 /* The last #include file should be: */
97 #ifdef CURLDEBUG
98 #include "memdebug.h"
99 #endif
100
101 static void
102 free_cookiemess(struct Cookie *co)
103 {
104   if(co->domain)
105     free(co->domain);
106   if(co->path)
107     free(co->path);
108   if(co->name)
109     free(co->name);
110   if(co->value)
111     free(co->value);
112
113   free(co);
114 }
115
116 static bool tailmatch(const char *little, const char *bigone)
117 {
118   size_t littlelen = strlen(little);
119   size_t biglen = strlen(bigone);
120
121   if(littlelen > biglen)
122     return FALSE;
123
124   return (bool)strequal(little, bigone+biglen-littlelen);
125 }
126
127 /****************************************************************************
128  *
129  * Curl_cookie_add()
130  *
131  * Add a single cookie line to the cookie keeping object.
132  *
133  ***************************************************************************/
134
135 struct Cookie *
136 Curl_cookie_add(struct SessionHandle *data,
137                 /* The 'data' pointer here may be NULL at times, and thus
138                    must only be used very carefully for things that can deal
139                    with data being NULL. Such as infof() and similar */
140                 
141                 struct CookieInfo *c,
142                 bool httpheader, /* TRUE if HTTP header-style line */
143                 char *lineptr,   /* first character of the line */
144                 char *domain,    /* default domain */
145                 char *path)      /* full path used when this cookie is set,
146                                     used to get default path for the cookie
147                                     unless set */
148 {
149   struct Cookie *clist;
150   char what[MAX_COOKIE_LINE];
151   char name[MAX_NAME];
152   char *ptr;
153   char *semiptr;
154   struct Cookie *co;
155   struct Cookie *lastc=NULL;
156   time_t now = time(NULL);
157   bool replace_old = FALSE;
158   bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
159
160   /* First, alloc and init a new struct for it */
161   co = (struct Cookie *)calloc(sizeof(struct Cookie), 1);
162   if(!co)
163     return NULL; /* bail out if we're this low on memory */
164
165   if(httpheader) {
166     /* This line was read off a HTTP-header */
167     char *sep;
168     semiptr=strchr(lineptr, ';'); /* first, find a semicolon */
169
170     while(*lineptr && isspace((int)*lineptr))
171       lineptr++;
172
173     ptr = lineptr;
174     do {
175       /* we have a <what>=<this> pair or a 'secure' word here */
176       sep = strchr(ptr, '=');
177       if(sep && (!semiptr || (semiptr>sep)) ) {
178         /*
179          * There is a = sign and if there was a semicolon too, which make sure
180          * that the semicolon comes _after_ the equal sign.
181          */
182
183         name[0]=what[0]=0; /* init the buffers */
184         if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;=]=%"
185                        MAX_COOKIE_LINE_TXT "[^;\r\n]",
186                        name, what)) {
187           /* this is a <name>=<what> pair */
188
189           char *whatptr;
190
191           /* Strip off trailing whitespace from the 'what' */
192           size_t len=strlen(what);
193           while(len && isspace((int)what[len-1])) {
194             what[len-1]=0;
195             len--;
196           }
197
198           /* Skip leading whitespace from the 'what' */
199           whatptr=what;
200           while(isspace((int)*whatptr)) {
201             whatptr++;
202           }
203
204           if(strequal("path", name)) {
205             co->path=strdup(whatptr);
206           }
207           else if(strequal("domain", name)) {
208             /* note that this name may or may not have a preceeding dot, but
209                we don't care about that, we treat the names the same anyway */
210
211             const char *domptr=whatptr;
212             int dotcount=1;
213             unsigned int i;
214
215             static const char *seventhree[]= {
216               "com", "edu", "net", "org", "gov", "mil", "int"
217             };
218
219             /* Count the dots, we need to make sure that there are THREE dots
220                in the normal domains, or TWO in the seventhree-domains. */
221
222             if('.' == whatptr[0])
223               /* don't count the initial dot, assume it */
224               domptr++;
225
226             do {
227               domptr = strchr(domptr, '.');
228               if(domptr) {
229                 domptr++;
230                 dotcount++;
231               }
232             } while(domptr);
233
234             for(i=0;
235                 i<sizeof(seventhree)/sizeof(seventhree[0]); i++) {
236               if(tailmatch(seventhree[i], whatptr)) {
237                 dotcount++; /* we allow one dot less for these */
238                 break;
239               }
240             }
241             /* The original Netscape cookie spec defined that this domain name
242                MUST have three dots (or two if one of the seven holy TLDs),
243                but it seems that these kinds of cookies are in use "out there"
244                so we cannot be that strict. I've therefore lowered the check
245                to not allow less than two dots. */
246             
247             if(dotcount < 2) {
248               /* Received and skipped a cookie with a domain using too few
249                  dots. */
250               badcookie=TRUE; /* mark this as a bad cookie */
251               infof(data, "skipped cookie with illegal dotcount domain: %s",
252                     whatptr);
253             }
254             else {
255               /* Now, we make sure that our host is within the given domain,
256                  or the given domain is not valid and thus cannot be set. */
257
258               if(!domain || tailmatch(whatptr, domain)) {
259                 const char *tailptr=whatptr;
260                 if(tailptr[0] == '.')
261                   tailptr++;
262                 co->domain=strdup(tailptr); /* don't prefix w/dots internally */
263                 co->tailmatch=TRUE; /* we always do that if the domain name was
264                                        given */
265               }
266               else {
267                 /* we did not get a tailmatch and then the attempted set domain
268                    is not a domain to which the current host belongs. Mark as
269                    bad. */
270                 badcookie=TRUE;
271                 infof(data, "skipped cookie with bad tailmatch domain: %s",
272                       whatptr);
273               }
274             }
275           }
276           else if(strequal("version", name)) {
277             co->version=strdup(whatptr);
278           }
279           else if(strequal("max-age", name)) {
280             /* Defined in RFC2109:
281
282                Optional.  The Max-Age attribute defines the lifetime of the
283                cookie, in seconds.  The delta-seconds value is a decimal non-
284                negative integer.  After delta-seconds seconds elapse, the
285                client should discard the cookie.  A value of zero means the
286                cookie should be discarded immediately.
287
288              */
289             co->maxage = strdup(whatptr);
290             co->expires =
291               atoi((*co->maxage=='\"')?&co->maxage[1]:&co->maxage[0]) + now;
292           }
293           else if(strequal("expires", name)) {
294             co->expirestr=strdup(whatptr);
295             co->expires = curl_getdate(what, &now);
296           }
297           else if(!co->name) {
298             co->name = strdup(name);
299             co->value = strdup(whatptr);
300           }
301           /*
302             else this is the second (or more) name we don't know
303             about! */
304         }
305         else {
306           /* this is an "illegal" <what>=<this> pair */
307         }
308       }
309       else {
310         if(sscanf(ptr, "%" MAX_COOKIE_LINE_TXT "[^;\r\n]",
311                   what)) {
312           if(strequal("secure", what))
313             co->secure = TRUE;
314           /* else,
315              unsupported keyword without assign! */
316
317         }
318       }
319       if(!semiptr || !*semiptr) {
320         /* we already know there are no more cookies */
321         semiptr = NULL;
322         continue;
323       }
324
325       ptr=semiptr+1;
326       while(ptr && *ptr && isspace((int)*ptr))
327         ptr++;
328       semiptr=strchr(ptr, ';'); /* now, find the next semicolon */
329
330       if(!semiptr && *ptr)
331         /* There are no more semicolons, but there's a final name=value pair
332            coming up */
333         semiptr=strchr(ptr, '\0');
334     } while(semiptr);
335
336     if(badcookie || (NULL == co->name)) {
337       /* we didn't get a cookie name or a bad one,
338          this is an illegal line, bail out */
339       if(co->expirestr)
340         free(co->expirestr);
341       if(co->domain)
342         free(co->domain);
343       if(co->path)
344         free(co->path);
345       if(co->name)
346         free(co->name);
347       if(co->value)
348         free(co->value);
349       free(co);
350       return NULL;
351     }
352
353     if(NULL == co->domain)
354       /* no domain was given in the header line, set the default now */
355       co->domain=domain?strdup(domain):NULL;
356     if((NULL == co->path) && path) {
357       /* no path was given in the header line, set the default now */
358       char *endslash = strrchr(path, '/');
359       if(endslash) {
360         size_t pathlen = endslash-path+1; /* include the ending slash */
361         co->path=malloc(pathlen+1); /* one extra for the zero byte */
362         if(co->path) {
363           memcpy(co->path, path, pathlen);
364           co->path[pathlen]=0; /* zero terminate */
365         }
366       }
367     }
368   }
369   else {
370     /* This line is NOT a HTTP header style line, we do offer support for
371        reading the odd netscape cookies-file format here */
372     char *firstptr;
373     char *tok_buf;
374     int fields;
375
376     if(lineptr[0]=='#') {
377       /* don't even try the comments */
378       free(co);
379       return NULL;
380     }
381     /* strip off the possible end-of-line characters */
382     ptr=strchr(lineptr, '\r');
383     if(ptr)
384       *ptr=0; /* clear it */
385     ptr=strchr(lineptr, '\n');
386     if(ptr)
387       *ptr=0; /* clear it */
388
389     firstptr=strtok_r(lineptr, "\t", &tok_buf); /* first tokenize it on the TAB */
390
391     /* Here's a quick check to eliminate normal HTTP-headers from this */
392     if(!firstptr || strchr(firstptr, ':')) {
393       free(co);
394       return NULL;
395     }
396
397     /* Now loop through the fields and init the struct we already have
398        allocated */
399     for(ptr=firstptr, fields=0; ptr;
400         ptr=strtok_r(NULL, "\t", &tok_buf), fields++) {
401       switch(fields) {
402       case 0:
403         if(ptr[0]=='.') /* skip preceeding dots */
404           ptr++;
405         co->domain = strdup(ptr);
406         break;
407       case 1:
408         /* This field got its explanation on the 23rd of May 2001 by
409            Andrés García:
410
411            flag: A TRUE/FALSE value indicating if all machines within a given
412            domain can access the variable. This value is set automatically by
413            the browser, depending on the value you set for the domain.
414
415            As far as I can see, it is set to true when the cookie says
416            .domain.com and to false when the domain is complete www.domain.com
417         */
418         co->tailmatch=(bool)strequal(ptr, "TRUE"); /* store information */
419         break;
420       case 2:
421         /* It turns out, that sometimes the file format allows the path
422            field to remain not filled in, we try to detect this and work
423            around it! Andrés García made us aware of this... */
424         if (strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
425           /* only if the path doesn't look like a boolean option! */
426           co->path = strdup(ptr);
427           break;
428         }
429         /* this doesn't look like a path, make one up! */
430         co->path = strdup("/");
431         fields++; /* add a field and fall down to secure */
432         /* FALLTHROUGH */
433       case 3:
434         co->secure = (bool)strequal(ptr, "TRUE");
435         break;
436       case 4:
437         co->expires = atoi(ptr);
438         break;
439       case 5:
440         co->name = strdup(ptr);
441         break;
442       case 6:
443         co->value = strdup(ptr);
444         break;
445       }
446     }
447
448     if(6 == fields) {
449       /* we got a cookie with blank contents, fix it */
450       co->value = strdup("");
451     }
452     else if(7 != fields) {
453       /* we did not find the sufficient number of fields to recognize this
454          as a valid line, abort and go home */
455       free_cookiemess(co);
456       return NULL;
457     }
458   }
459
460   if(!c->running &&    /* read from a file */
461      c->newsession &&  /* clean session cookies */
462      !co->expires) {   /* this is a session cookie since it doesn't expire! */
463     free_cookiemess(co);
464     return NULL;
465   }
466
467   co->livecookie = c->running;
468
469   /* now, we have parsed the incoming line, we must now check if this
470      superceeds an already existing cookie, which it may if the previous have
471      the same domain and path as this */
472
473   clist = c->cookies;
474   replace_old = FALSE;
475   while(clist) {
476     if(strequal(clist->name, co->name)) {
477       /* the names are identical */
478
479       if(clist->domain && co->domain) {
480         if(strequal(clist->domain, co->domain))
481           /* The domains are identical */
482           replace_old=TRUE;
483       }
484       else if(!clist->domain && !co->domain)
485         replace_old = TRUE;
486
487       if(replace_old) {
488         /* the domains were identical */
489
490         if(clist->path && co->path) {
491           if(strequal(clist->path, co->path)) {
492             replace_old = TRUE;
493           }
494           else
495             replace_old = FALSE;
496         }
497         else if(!clist->path && !co->path)
498           replace_old = TRUE;
499         else
500           replace_old = FALSE;
501         
502       }
503
504       if(replace_old && !co->livecookie && clist->livecookie) {
505         /* Both cookies matched fine, except that the already present
506            cookie is "live", which means it was set from a header, while
507            the new one isn't "live" and thus only read from a file. We let
508            live cookies stay alive */
509
510         /* Free the newcomer and get out of here! */
511         if(co->domain)
512           free(co->domain);
513         if(co->path)
514           free(co->path);
515         if(co->name)
516           free(co->name);
517         if(co->value)
518           free(co->value);
519
520         free(co);
521         return NULL;
522       }
523
524       if(replace_old) {
525         co->next = clist->next; /* get the next-pointer first */
526
527         /* then free all the old pointers */
528         if(clist->name)
529           free(clist->name);
530         if(clist->value)
531           free(clist->value);
532         if(clist->domain)
533           free(clist->domain);
534         if(clist->path)
535           free(clist->path);
536         if(clist->expirestr)
537           free(clist->expirestr);
538
539         if(clist->version)
540           free(clist->version);
541         if(clist->maxage)
542           free(clist->maxage);
543
544         *clist = *co;  /* then store all the new data */
545
546         free(co);   /* free the newly alloced memory */
547         co = clist; /* point to the previous struct instead */
548
549         /* We have replaced a cookie, now skip the rest of the list but
550            make sure the 'lastc' pointer is properly set */
551         do {
552           lastc = clist;
553           clist = clist->next;
554         } while(clist);
555         break;
556       }
557     }
558     lastc = clist;
559     clist = clist->next;
560   }
561
562   if(c->running)
563     /* Only show this when NOT reading the cookies from a file */
564     infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, expire %d\n",
565           replace_old?"Replaced":"Added", co->name, co->value,
566           co->domain, co->path, co->expires);
567
568   if(!replace_old) {
569     /* then make the last item point on this new one */
570     if(lastc)
571       lastc->next = co;
572     else
573       c->cookies = co;
574   }
575
576   c->numcookies++; /* one more cookie in the jar */
577   return co;
578 }
579
580 /*****************************************************************************
581  *
582  * Curl_cookie_init()
583  *
584  * Inits a cookie struct to read data from a local file. This is always
585  * called before any cookies are set. File may be NULL.
586  *
587  * If 'newsession' is TRUE, discard all "session cookies" on read from file.
588  *
589  ****************************************************************************/
590 struct CookieInfo *Curl_cookie_init(struct SessionHandle *data,
591                                     char *file,
592                                     struct CookieInfo *inc,
593                                     bool newsession)
594 {
595   char line[MAX_COOKIE_LINE];
596   struct CookieInfo *c;
597   FILE *fp;
598   bool fromfile=TRUE;
599   
600   if(NULL == inc) {
601     /* we didn't get a struct, create one */
602     c = (struct CookieInfo *)malloc(sizeof(struct CookieInfo));
603     if(!c)
604       return NULL; /* failed to get memory */
605     memset(c, 0, sizeof(struct CookieInfo));
606     c->filename = strdup(file?file:"none"); /* copy the name just in case */
607   }
608   else {
609     /* we got an already existing one, use that */
610     c = inc;
611   }
612   c->running = FALSE; /* this is not running, this is init */
613
614   if(file && strequal(file, "-")) {
615     fp = stdin;
616     fromfile=FALSE;
617   }
618   else
619     fp = file?fopen(file, "r"):NULL;
620
621   c->newsession = newsession; /* new session? */
622
623   if(fp) {
624     char *lineptr;
625     bool headerline;
626     while(fgets(line, MAX_COOKIE_LINE, fp)) {
627       if(checkprefix("Set-Cookie:", line)) {
628         /* This is a cookie line, get it! */
629         lineptr=&line[11];
630         headerline=TRUE;
631       }
632       else {
633         lineptr=line;
634         headerline=FALSE;
635       }
636       while(*lineptr && isspace((int)*lineptr))
637         lineptr++;
638
639       Curl_cookie_add(data, c, headerline, lineptr, NULL, NULL);
640     }
641     if(fromfile)
642       fclose(fp);
643   }
644
645   c->running = TRUE;          /* now, we're running */
646
647   return c;
648 }
649
650 /*****************************************************************************
651  *
652  * Curl_cookie_getlist()
653  *
654  * For a given host and path, return a linked list of cookies that the
655  * client should send to the server if used now. The secure boolean informs
656  * the cookie if a secure connection is achieved or not.
657  *
658  * It shall only return cookies that haven't expired.
659  *
660  ****************************************************************************/
661
662 struct Cookie *Curl_cookie_getlist(struct CookieInfo *c,
663                                    char *host, char *path, bool secure)
664 {
665    struct Cookie *newco;
666    struct Cookie *co;
667    time_t now = time(NULL);
668    struct Cookie *mainco=NULL;
669
670    if(!c || !c->cookies)
671       return NULL; /* no cookie struct or no cookies in the struct */
672
673    co = c->cookies;
674
675    while(co) {
676      /* only process this cookie if it is not expired or had no expire
677         date AND that if the cookie requires we're secure we must only
678         continue if we are! */
679      if( (co->expires<=0 || (co->expires> now)) &&
680          (co->secure?secure:TRUE) ) {
681        
682        /* now check if the domain is correct */
683        if(!co->domain ||
684           (co->tailmatch && tailmatch(co->domain, host)) ||
685           (!co->tailmatch && strequal(host, co->domain)) ) {
686          /* the right part of the host matches the domain stuff in the
687             cookie data */
688          
689          /* now check the left part of the path with the cookies path
690             requirement */
691          if(!co->path ||
692             checkprefix(co->path, path) ) {
693
694            /* and now, we know this is a match and we should create an
695               entry for the return-linked-list */
696            
697            newco = (struct Cookie *)malloc(sizeof(struct Cookie));
698            if(newco) {
699              /* first, copy the whole source cookie: */
700              memcpy(newco, co, sizeof(struct Cookie));
701
702              /* then modify our next */
703              newco->next = mainco;
704              
705              /* point the main to us */
706              mainco = newco;
707            }
708          }
709        }
710      }
711      co = co->next;
712    }
713
714    return mainco; /* return the new list */
715 }
716
717
718 /*****************************************************************************
719  *
720  * Curl_cookie_freelist()
721  *
722  * Free a list of cookies previously returned by Curl_cookie_getlist();
723  *
724  ****************************************************************************/
725
726 void Curl_cookie_freelist(struct Cookie *co)
727 {
728    struct Cookie *next;
729    if(co) {
730       while(co) {
731          next = co->next;
732          free(co); /* we only free the struct since the "members" are all
733                       just copied! */
734          co = next;
735       }
736    }
737 }
738
739 /*****************************************************************************
740  *
741  * Curl_cookie_cleanup()
742  *
743  * Free a "cookie object" previous created with cookie_init().
744  *
745  ****************************************************************************/
746 void Curl_cookie_cleanup(struct CookieInfo *c)
747 {
748    struct Cookie *co;
749    struct Cookie *next;
750    if(c) {
751       if(c->filename)
752          free(c->filename);
753       co = c->cookies;
754
755       while(co) {
756          if(co->name)
757             free(co->name);
758          if(co->value)
759             free(co->value);
760          if(co->domain)
761             free(co->domain);
762          if(co->path)
763             free(co->path);
764          if(co->expirestr)
765             free(co->expirestr);
766
767          if(co->version)
768             free(co->version);
769          if(co->maxage)
770             free(co->maxage);
771
772          next = co->next;
773          free(co);
774          co = next;
775       }
776       free(c); /* free the base struct as well */
777    }
778 }
779
780 /*
781  * Curl_cookie_output()
782  *
783  * Writes all internally known cookies to the specified file. Specify
784  * "-" as file name to write to stdout.
785  *
786  * The function returns non-zero on write failure.
787  */
788 int Curl_cookie_output(struct CookieInfo *c, char *dumphere)
789 {
790   struct Cookie *co;
791   FILE *out;
792   bool use_stdout=FALSE;
793
794   if((NULL == c) || (0 == c->numcookies))
795     /* If there are no known cookies, we don't write or even create any
796        destination file */
797     return 0;
798
799   if(strequal("-", dumphere)) {
800     /* use stdout */
801     out = stdout;
802     use_stdout=TRUE;
803   }
804   else {
805     out = fopen(dumphere, "w");
806     if(!out)
807       return 1; /* failure */
808   }
809
810   if(c) {
811     fputs("# Netscape HTTP Cookie File\n"
812           "# http://www.netscape.com/newsref/std/cookie_spec.html\n"
813           "# This file was generated by libcurl! Edit at your own risk.\n\n",
814           out);
815     co = c->cookies;
816      
817     while(co) {
818       fprintf(out,
819               "%s%s\t" /* domain */
820               "%s\t" /* tailmatch */
821               "%s\t" /* path */
822               "%s\t" /* secure */
823               "%u\t" /* expires */
824               "%s\t" /* name */
825               "%s\n", /* value */
826
827               /* Make sure all domains are prefixed with a dot if they allow
828                  tailmatching. This is Mozilla-style. */
829               (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
830               co->domain?co->domain:"unknown",
831               co->tailmatch?"TRUE":"FALSE",
832               co->path?co->path:"/",
833               co->secure?"TRUE":"FALSE",
834               (unsigned int)co->expires,
835               co->name,
836               co->value?co->value:"");
837
838       co=co->next;
839     }
840   }
841
842   if(!use_stdout)
843     fclose(out);
844
845   return 0;
846 }
847
848 #endif /* CURL_DISABLE_HTTP */