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