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