]> sjero.net Git - wget/blob - src/cookies.c
[svn] Renamed wget.h XDIGIT-related macros to (hopefully) clearer names.
[wget] / src / cookies.c
1 /* Support for cookies.
2    Copyright (C) 2001, 2002 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 /* Written by Hrvoje Niksic.  Parts are loosely inspired by cookie
31    code submitted by Tomasz Wegrzanowski.
32
33    TODO: Implement limits on cookie-related sizes, such as max. cookie
34    size, max. number of cookies, etc.  Add more "cookie jar" methods,
35    such as methods to over stored cookies, to clear temporary cookies,
36    to perform intelligent auto-saving, etc.  Ultimately support
37    `Set-Cookie2' and `Cookie2' headers.  */
38
39 #include <config.h>
40
41 #include <stdio.h>
42 #ifdef HAVE_STRING_H
43 # include <string.h>
44 #else
45 # include <strings.h>
46 #endif
47 #include <stdlib.h>
48 #include <assert.h>
49 #include <errno.h>
50
51 #include "wget.h"
52 #include "utils.h"
53 #include "hash.h"
54 #include "cookies.h"
55
56 /* This should *really* be in a .h file!  */
57 time_t http_atotm PARAMS ((const char *));
58 \f
59 /* Declarations of `struct cookie' and the most basic functions. */
60
61 struct cookie_jar {
62   /* Hash table that maps domain names to cookie chains.  A "cookie
63      chain" is a linked list of cookies that belong to the same
64      domain.  */
65   struct hash_table *chains_by_domain;
66
67   int cookie_count;             /* number of cookies in the jar. */
68 };
69
70 /* Value set by entry point functions, so that the low-level
71    routines don't need to call time() all the time.  */
72 time_t cookies_now;
73
74 struct cookie_jar *
75 cookie_jar_new (void)
76 {
77   struct cookie_jar *jar = xmalloc (sizeof (struct cookie_jar));
78   jar->chains_by_domain = make_nocase_string_hash_table (0);
79   jar->cookie_count = 0;
80   return jar;
81 }
82
83 struct cookie {
84   char *domain;                 /* domain of the cookie */
85   int port;                     /* port number */
86   char *path;                   /* path prefix of the cookie */
87   int secure;                   /* whether cookie should be
88                                    transmitted over non-https
89                                    connections. */
90   int permanent;                /* whether the cookie should outlive
91                                    the session */
92   time_t expiry_time;           /* time when the cookie expires */
93   int discard_requested;        /* whether cookie was created to
94                                    request discarding another
95                                    cookie */
96
97   char *attr;                   /* cookie attribute name */
98   char *value;                  /* cookie attribute value */
99
100   struct cookie_jar *jar;       /* pointer back to the cookie jar, for
101                                    convenience. */
102   struct cookie *next;          /* used for chaining of cookies in the
103                                    same domain. */
104 };
105
106 #define PORT_ANY (-1)
107 #define COOKIE_EXPIRED_P(c) ((c)->expiry_time != 0 && (c)->expiry_time < cookies_now)
108
109 /* Allocate and return a new, empty cookie structure. */
110
111 static struct cookie *
112 cookie_new (void)
113 {
114   struct cookie *cookie = xmalloc (sizeof (struct cookie));
115   memset (cookie, '\0', sizeof (struct cookie));
116
117   /* Both cookie->permanent and cookie->expiry_time are now 0.  By
118      default, we assume that the cookie is non-permanent and valid
119      until the end of the session.  */
120
121   cookie->port = PORT_ANY;
122   return cookie;
123 }
124
125 /* Deallocate COOKIE and its components. */
126
127 static void
128 delete_cookie (struct cookie *cookie)
129 {
130   FREE_MAYBE (cookie->domain);
131   FREE_MAYBE (cookie->path);
132   FREE_MAYBE (cookie->attr);
133   FREE_MAYBE (cookie->value);
134   xfree (cookie);
135 }
136 \f
137 /* Functions for storing cookies.
138
139    All cookies can be reached beginning with jar->chains_by_domain.
140    The key in that table is the domain name, and the value is a linked
141    list of all cookies from that domain.  Every new cookie is placed
142    on the head of the list.  */
143
144 /* Find and return a cookie in JAR whose domain, path, and attribute
145    name correspond to COOKIE.  If found, PREVPTR will point to the
146    location of the cookie previous in chain, or NULL if the found
147    cookie is the head of a chain.
148
149    If no matching cookie is found, return NULL. */
150
151 static struct cookie *
152 find_matching_cookie (struct cookie_jar *jar, struct cookie *cookie,
153                       struct cookie **prevptr)
154 {
155   struct cookie *chain, *prev;
156
157   chain = hash_table_get (jar->chains_by_domain, cookie->domain);
158   if (!chain)
159     goto nomatch;
160
161   prev = NULL;
162   for (; chain; prev = chain, chain = chain->next)
163     if (0 == strcmp (cookie->path, chain->path)
164         && 0 == strcmp (cookie->attr, chain->attr)
165         && cookie->port == chain->port)
166       {
167         *prevptr = prev;
168         return chain;
169       }
170
171  nomatch:
172   *prevptr = NULL;
173   return NULL;
174 }
175
176 /* Store COOKIE to the jar.
177
178    This is done by placing COOKIE at the head of its chain.  However,
179    if COOKIE matches a cookie already in memory, as determined by
180    find_matching_cookie, the old cookie is unlinked and destroyed.
181
182    The key of each chain's hash table entry is allocated only the
183    first time; next hash_table_put's reuse the same key.  */
184
185 static void
186 store_cookie (struct cookie_jar *jar, struct cookie *cookie)
187 {
188   struct cookie *chain_head;
189   char *chain_key;
190
191   if (hash_table_get_pair (jar->chains_by_domain, cookie->domain,
192                            &chain_key, &chain_head))
193     {
194       /* A chain of cookies in this domain already exists.  Check for
195          duplicates -- if an extant cookie exactly matches our domain,
196          port, path, and name, replace it.  */
197       struct cookie *prev;
198       struct cookie *victim = find_matching_cookie (jar, cookie, &prev);
199
200       if (victim)
201         {
202           /* Remove VICTIM from the chain.  COOKIE will be placed at
203              the head. */
204           if (prev)
205             {
206               prev->next = victim->next;
207               cookie->next = chain_head;
208             }
209           else
210             {
211               /* prev is NULL; apparently VICTIM was at the head of
212                  the chain.  This place will be taken by COOKIE, so
213                  all we need to do is:  */
214               cookie->next = victim->next;
215             }
216           delete_cookie (victim);
217           --jar->cookie_count;
218           DEBUGP (("Deleted old cookie (to be replaced.)\n"));
219         }
220       else
221         cookie->next = chain_head;
222     }
223   else
224     {
225       /* We are now creating the chain.  Allocate the string that will
226          be used as a key.  It is unsafe to use cookie->domain for
227          that, because it might get deallocated by the above code at
228          some point later.  */
229       cookie->next = NULL;
230       chain_key = xstrdup (cookie->domain);
231     }
232
233   hash_table_put (jar->chains_by_domain, chain_key, cookie);
234   ++jar->cookie_count;
235
236   DEBUGP (("\nStored cookie %s %d%s %s %s %d %s %s %s\n",
237            cookie->domain, cookie->port,
238            cookie->port == PORT_ANY ? " (ANY)" : "",
239            cookie->path,
240            cookie->permanent ? "permanent" : "nonpermanent",
241            cookie->secure,
242            cookie->expiry_time
243            ? asctime (localtime (&cookie->expiry_time)) : "<undefined>",
244            cookie->attr, cookie->value));
245 }
246
247 /* Discard a cookie matching COOKIE's domain, port, path, and
248    attribute name.  This gets called when we encounter a cookie whose
249    expiry date is in the past, or whose max-age is set to 0.  The
250    former corresponds to netscape cookie spec, while the latter is
251    specified by rfc2109.  */
252
253 static void
254 discard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie)
255 {
256   struct cookie *prev, *victim;
257
258   if (!hash_table_count (jar->chains_by_domain))
259     /* No elements == nothing to discard. */
260     return;
261
262   victim = find_matching_cookie (jar, cookie, &prev);
263   if (victim)
264     {
265       if (prev)
266         /* Simply unchain the victim. */
267         prev->next = victim->next;
268       else
269         {
270           /* VICTIM was head of its chain.  We need to place a new
271              cookie at the head.  */
272           char *chain_key = NULL;
273           int res;
274
275           res = hash_table_get_pair (jar->chains_by_domain, victim->domain,
276                                      &chain_key, NULL);
277           assert (res != 0);
278           if (!victim->next)
279             {
280               /* VICTIM was the only cookie in the chain.  Destroy the
281                  chain and deallocate the chain key.  */
282               hash_table_remove (jar->chains_by_domain, victim->domain);
283               xfree (chain_key);
284             }
285           else
286             hash_table_put (jar->chains_by_domain, chain_key, victim->next);
287         }
288       delete_cookie (victim);
289       DEBUGP (("Discarded old cookie.\n"));
290     }
291 }
292 \f
293 /* Functions for parsing the `Set-Cookie' header, and creating new
294    cookies from the wire.  */
295
296
297 #define NAME_IS(string_literal)                                 \
298   BOUNDED_EQUAL_NO_CASE (name_b, name_e, string_literal)
299
300 #define VALUE_EXISTS (value_b && value_e)
301
302 #define VALUE_NON_EMPTY (VALUE_EXISTS && (value_b != value_e))
303
304 /* Update the appropriate cookie field.  [name_b, name_e) are expected
305    to delimit the attribute name, while [value_b, value_e) (optional)
306    should delimit the attribute value.
307
308    When called the first time, it will set the cookie's attribute name
309    and value.  After that, it will check the attribute name for
310    special fields such as `domain', `path', etc.  Where appropriate,
311    it will parse the values of the fields it recognizes and fill the
312    corresponding fields in COOKIE.
313
314    Returns 1 on success.  Returns zero in case a syntax error is
315    found; such a cookie should be discarded.  */
316
317 static int
318 update_cookie_field (struct cookie *cookie,
319                      const char *name_b, const char *name_e,
320                      const char *value_b, const char *value_e)
321 {
322   assert (name_b != NULL && name_e != NULL);
323
324   if (!cookie->attr)
325     {
326       if (!VALUE_EXISTS)
327         return 0;
328       cookie->attr = strdupdelim (name_b, name_e);
329       cookie->value = strdupdelim (value_b, value_e);
330       return 1;
331     }
332
333   if (NAME_IS ("domain"))
334     {
335       if (!VALUE_NON_EMPTY)
336         return 0;
337       FREE_MAYBE (cookie->domain);
338       cookie->domain = strdupdelim (value_b, value_e);
339       return 1;
340     }
341   else if (NAME_IS ("path"))
342     {
343       if (!VALUE_NON_EMPTY)
344         return 0;
345       FREE_MAYBE (cookie->path);
346       cookie->path = strdupdelim (value_b, value_e);
347       return 1;
348     }
349   else if (NAME_IS ("expires"))
350     {
351       char *value_copy;
352       time_t expires;
353
354       if (!VALUE_NON_EMPTY)
355         return 0;
356       BOUNDED_TO_ALLOCA (value_b, value_e, value_copy);
357
358       expires = http_atotm (value_copy);
359       if (expires != -1)
360         {
361           cookie->permanent = 1;
362           cookie->expiry_time = (time_t)expires;
363         }
364       else
365         /* Error in expiration spec.  Assume default (cookie valid for
366            this session.)  */
367         ;
368
369       /* According to netscape's specification, expiry time in the
370          past means that discarding of a matching cookie is
371          requested.  */
372       if (cookie->expiry_time < cookies_now)
373         cookie->discard_requested = 1;
374
375       return 1;
376     }
377   else if (NAME_IS ("max-age"))
378     {
379       double maxage = -1;
380       char *value_copy;
381
382       if (!VALUE_NON_EMPTY)
383         return 0;
384       BOUNDED_TO_ALLOCA (value_b, value_e, value_copy);
385
386       sscanf (value_copy, "%lf", &maxage);
387       if (maxage == -1)
388         /* something went wrong. */
389         return 0;
390       cookie->permanent = 1;
391       cookie->expiry_time = cookies_now + maxage;
392
393       /* According to rfc2109, a cookie with max-age of 0 means that
394          discarding of a matching cookie is requested.  */
395       if (maxage == 0)
396         cookie->discard_requested = 1;
397
398       return 1;
399     }
400   else if (NAME_IS ("secure"))
401     {
402       /* ignore value completely */
403       cookie->secure = 1;
404       return 1;
405     }
406   else
407     /* Unrecognized attribute; ignore it. */
408     return 1;
409 }
410
411 #undef NAME_IS
412
413 /* Returns non-zero for characters that are legal in the name of an
414    attribute.  This used to allow only alphanumerics, '-', and '_',
415    but we need to be more lenient because a number of sites wants to
416    use weirder attribute names.  rfc2965 "informally specifies"
417    attribute name (token) as "a sequence of non-special, non-white
418    space characters".  So we allow everything except the stuff we know
419    could harm us.  */
420
421 #define ATTR_NAME_CHAR(c) ((c) > 32 && (c) < 127        \
422                            && (c) != '"' && (c) != '='  \
423                            && (c) != ';' && (c) != ',')
424
425 /* Parse the contents of the `Set-Cookie' header.  The header looks
426    like this:
427
428    name1=value1; name2=value2; ...
429
430    Trailing semicolon is optional; spaces are allowed between all
431    tokens.  Additionally, values may be quoted.
432
433    A new cookie is returned upon success, NULL otherwise.  The
434    specified CALLBACK function (normally `update_cookie_field' is used
435    to update the fields of the newly created cookie structure.  */
436
437 static struct cookie *
438 parse_set_cookies (const char *sc,
439                    int (*callback) (struct cookie *,
440                                     const char *, const char *,
441                                     const char *, const char *),
442                    int silent)
443 {
444   struct cookie *cookie = cookie_new ();
445
446   /* #### Hand-written DFAs are no fun to debug.  We'de be better off
447      to rewrite this as an inline parser.  */
448
449   enum { S_START, S_NAME, S_NAME_POST,
450          S_VALUE_PRE, S_VALUE, S_QUOTED_VALUE, S_VALUE_TRAILSPACE,
451          S_ATTR_ACTION, S_DONE, S_ERROR
452   } state = S_START;
453
454   const char *p = sc;
455   char c;
456
457   const char *name_b  = NULL, *name_e  = NULL;
458   const char *value_b = NULL, *value_e = NULL;
459
460   c = *p;
461
462   while (state != S_DONE && state != S_ERROR)
463     {
464       switch (state)
465         {
466         case S_START:
467           if (!c)
468             state = S_DONE;
469           else if (ISSPACE (c))
470             /* Strip all whitespace preceding the name. */
471             c = *++p;
472           else if (ATTR_NAME_CHAR (c))
473             {
474               name_b = p;
475               state = S_NAME;
476             }
477           else
478             /* empty attr name not allowed */
479             state = S_ERROR;
480           break;
481         case S_NAME:
482           if (!c || c == ';' || c == '=' || ISSPACE (c))
483             {
484               name_e = p;
485               state = S_NAME_POST;
486             }
487           else if (ATTR_NAME_CHAR (c))
488             c = *++p;
489           else
490             state = S_ERROR;
491           break;
492         case S_NAME_POST:
493           if (!c || c == ';')
494             {
495               value_b = value_e = NULL;
496               if (c == ';')
497                 c = *++p;
498               state = S_ATTR_ACTION;
499             }
500           else if (c == '=')
501             {
502               c = *++p;
503               state = S_VALUE_PRE;
504             }
505           else if (ISSPACE (c))
506             /* Ignore space and keep the state. */
507             c = *++p;
508           else
509             state = S_ERROR;
510           break;
511         case S_VALUE_PRE:
512           if (!c || c == ';')
513             {
514               value_b = value_e = p;
515               if (c == ';')
516                 c = *++p;
517               state = S_ATTR_ACTION;
518             }
519           else if (c == '"')
520             {
521               c = *++p;
522               value_b = p;
523               state = S_QUOTED_VALUE;
524             }
525           else if (ISSPACE (c))
526             c = *++p;
527           else
528             {
529               value_b = p;
530               value_e = NULL;
531               state = S_VALUE;
532             }
533           break;
534         case S_VALUE:
535           if (!c || c == ';' || ISSPACE (c))
536             {
537               value_e = p;
538               state = S_VALUE_TRAILSPACE;
539             }
540           else
541             {
542               value_e = NULL;   /* no trailing space */
543               c = *++p;
544             }
545           break;
546         case S_QUOTED_VALUE:
547           if (c == '"')
548             {
549               value_e = p;
550               c = *++p;
551               state = S_VALUE_TRAILSPACE;
552             }
553           else if (!c)
554             state = S_ERROR;
555           else
556             c = *++p;
557           break;
558         case S_VALUE_TRAILSPACE:
559           if (c == ';')
560             {
561               c = *++p;
562               state = S_ATTR_ACTION;
563             }
564           else if (!c)
565             state = S_ATTR_ACTION;
566           else if (ISSPACE (c))
567             c = *++p;
568           else
569             state = S_VALUE;
570           break;
571         case S_ATTR_ACTION:
572           {
573             int legal = callback (cookie, name_b, name_e, value_b, value_e);
574             if (!legal)
575               {
576                 if (!silent)
577                   {
578                     char *name;
579                     BOUNDED_TO_ALLOCA (name_b, name_e, name);
580                     logprintf (LOG_NOTQUIET,
581                                _("Error in Set-Cookie, field `%s'"), name);
582                   }
583                 state = S_ERROR;
584                 break;
585               }
586             state = S_START;
587           }
588           break;
589         case S_DONE:
590         case S_ERROR:
591           /* handled by loop condition */
592           break;
593         }
594     }
595   if (state == S_DONE)
596     return cookie;
597
598   delete_cookie (cookie);
599   if (state != S_ERROR)
600     abort ();
601
602   if (!silent)
603     logprintf (LOG_NOTQUIET,
604                _("Syntax error in Set-Cookie: %s at position %d.\n"),
605                sc, p - sc);
606   return NULL;
607 }
608 \f
609 /* Sanity checks.  These are important, otherwise it is possible for
610    mailcious attackers to destroy important cookie information and/or
611    violate your privacy.  */
612
613
614 #define REQUIRE_DIGITS(p) do {                  \
615   if (!ISDIGIT (*p))                            \
616     return 0;                                   \
617   for (++p; ISDIGIT (*p); p++)                  \
618     ;                                           \
619 } while (0)
620
621 #define REQUIRE_DOT(p) do {                     \
622   if (*p++ != '.')                              \
623     return 0;                                   \
624 } while (0)
625
626 /* Check whether ADDR matches <digits>.<digits>.<digits>.<digits>.
627
628   We don't want to call network functions like inet_addr() because all
629   we need is a check, preferrably one that is small, fast, and
630   well-defined.  */
631
632 static int
633 numeric_address_p (const char *addr)
634 {
635   const char *p = addr;
636
637   REQUIRE_DIGITS (p);           /* A */
638   REQUIRE_DOT (p);              /* . */
639   REQUIRE_DIGITS (p);           /* B */
640   REQUIRE_DOT (p);              /* . */
641   REQUIRE_DIGITS (p);           /* C */
642   REQUIRE_DOT (p);              /* . */
643   REQUIRE_DIGITS (p);           /* D */
644
645   if (*p != '\0')
646     return 0;
647   return 1;
648 }
649
650 /* Check whether COOKIE_DOMAIN is an appropriate domain for HOST.
651    Originally I tried to make the check compliant with rfc2109, but
652    the sites deviated too often, so I had to fall back to "tail
653    matching", as defined by the original Netscape's cookie spec.  */
654
655 static int
656 check_domain_match (const char *cookie_domain, const char *host)
657 {
658   DEBUGP (("cdm: 1"));
659
660   /* Numeric address requires exact match.  It also requires HOST to
661      be an IP address.  */
662   if (numeric_address_p (cookie_domain))
663     return 0 == strcmp (cookie_domain, host);
664
665   DEBUGP ((" 2"));
666
667   /* For the sake of efficiency, check for exact match first. */
668   if (!strcasecmp (cookie_domain, host))
669     return 1;
670
671   DEBUGP ((" 3"));
672
673   /* HOST must match the tail of cookie_domain. */
674   if (!match_tail (host, cookie_domain, 1))
675     return 0;
676
677   /* We know that COOKIE_DOMAIN is a subset of HOST; however, we must
678      make sure that somebody is not trying to set the cookie for a
679      subdomain shared by many entities.  For example, "company.co.uk"
680      must not be allowed to set a cookie for ".co.uk".  On the other
681      hand, "sso.redhat.de" should be able to set a cookie for
682      ".redhat.de".
683
684      The only marginally sane way to handle this I can think of is to
685      reject on the basis of the length of the second-level domain name
686      (but when the top-level domain is unknown), with the assumption
687      that those of three or less characters could be reserved.  For
688      example:
689
690           .co.org -> works because the TLD is known
691            .co.uk -> doesn't work because "co" is only two chars long
692           .com.au -> doesn't work because "com" is only 3 chars long
693           .cnn.uk -> doesn't work because "cnn" is also only 3 chars long (ugh)
694           .cnn.de -> doesn't work for the same reason (ugh!!)
695          .abcd.de -> works because "abcd" is 4 chars long
696       .img.cnn.de -> works because it's not trying to set the 2nd level domain
697        .cnn.co.uk -> works for the same reason
698
699     That should prevent misuse, while allowing reasonable usage.  If
700     someone knows of a better way to handle this, please let me
701     know.  */
702   {
703     const char *p = cookie_domain;
704     int dccount = 1;            /* number of domain components */
705     int ldcl  = 0;              /* last domain component length */
706     int nldcl = 0;              /* next to last domain component length */
707     int out;
708     if (*p == '.')
709       /* Ignore leading period in this calculation. */
710       ++p;
711     DEBUGP ((" 4"));
712     for (out = 0; !out; p++)
713       switch (*p)
714         {
715         case '\0':
716           out = 1;
717           break;
718         case '.':
719           if (ldcl == 0)
720             /* Empty domain component found -- the domain is invalid. */
721             return 0;
722           if (*(p + 1) == '\0')
723             {
724               /* Tolerate trailing '.' by not treating the domain as
725                  one ending with an empty domain component.  */
726               out = 1;
727               break;
728             }
729           nldcl = ldcl;
730           ldcl  = 0;
731           ++dccount;
732           break;
733         default:
734           ++ldcl;
735         }
736
737     DEBUGP ((" 5"));
738
739     if (dccount < 2)
740       return 0;
741
742     DEBUGP ((" 6"));
743
744     if (dccount == 2)
745       {
746         int i;
747         int known_toplevel = 0;
748         static char *known_toplevel_domains[] = {
749           ".com", ".edu", ".net", ".org", ".gov", ".mil", ".int"
750         };
751         for (i = 0; i < countof (known_toplevel_domains); i++)
752           if (match_tail (cookie_domain, known_toplevel_domains[i], 1))
753             {
754               known_toplevel = 1;
755               break;
756             }
757         if (!known_toplevel && nldcl <= 3)
758           return 0;
759       }
760   }
761
762   DEBUGP ((" 7"));
763
764   /* Don't allow domain "bar.com" to match host "foobar.com".  */
765   if (*cookie_domain != '.')
766     {
767       int dlen = strlen (cookie_domain);
768       int hlen = strlen (host);
769       /* cookie host:    hostname.foobar.com */
770       /* desired domain:             bar.com */
771       /* '.' must be here in host-> ^        */
772       if (hlen > dlen && host[hlen - dlen - 1] != '.')
773         return 0;
774     }
775
776   DEBUGP ((" 8"));
777
778   return 1;
779 }
780
781 static int path_matches PARAMS ((const char *, const char *));
782
783 /* Check whether PATH begins with COOKIE_PATH. */
784
785 static int
786 check_path_match (const char *cookie_path, const char *path)
787 {
788   return path_matches (path, cookie_path);
789 }
790 \f
791 /* Process the HTTP `Set-Cookie' header.  This results in storing the
792    cookie or discarding a matching one, or ignoring it completely, all
793    depending on the contents.  */
794
795 void
796 cookie_jar_process_set_cookie (struct cookie_jar *jar,
797                                const char *host, int port,
798                                const char *path, const char *set_cookie)
799 {
800   struct cookie *cookie;
801   cookies_now = time (NULL);
802
803   cookie = parse_set_cookies (set_cookie, update_cookie_field, 0);
804   if (!cookie)
805     goto out;
806
807   /* Sanitize parts of cookie. */
808
809   if (!cookie->domain)
810     {
811     copy_domain:
812       cookie->domain = xstrdup (host);
813       cookie->port = port;
814     }
815   else
816     {
817       if (!check_domain_match (cookie->domain, host))
818         {
819           logprintf (LOG_NOTQUIET,
820                      "Cookie coming from %s attempted to set domain to %s\n",
821                      host, cookie->domain);
822           goto copy_domain;
823         }
824     }
825   if (!cookie->path)
826     cookie->path = xstrdup (path);
827   else
828     {
829       if (!check_path_match (cookie->path, path))
830         {
831           DEBUGP (("Attempt to fake the path: %s, %s\n",
832                    cookie->path, path));
833           goto out;
834         }
835     }
836
837   if (cookie->discard_requested)
838     {
839       discard_matching_cookie (jar, cookie);
840       goto out;
841     }
842
843   store_cookie (jar, cookie);
844   return;
845
846  out:
847   if (cookie)
848     delete_cookie (cookie);
849 }
850 \f
851 /* Support for sending out cookies in HTTP requests, based on
852    previously stored cookies.  Entry point is
853    `build_cookies_request'.  */
854
855 /* Store CHAIN to STORE if there is room in STORE.  If not, inrecement
856    COUNT anyway, so that when the function is done, we end up with the
857    exact count of how much place we actually need.  */
858
859 #define STORE_CHAIN(st_chain, st_store, st_size, st_count) do { \
860   if (st_count < st_size)                                       \
861     store[st_count] = st_chain;                                 \
862   ++st_count;                                                   \
863 } while (0)
864
865 /* Store cookie chains that match HOST.  Since more than one chain can
866    match, the matches are written to STORE.  No more than SIZE matches
867    are written; if more matches are present, return the number of
868    chains that would have been written.  */
869
870 static int
871 find_matching_chains (struct cookie_jar *jar, const char *host,
872                       struct cookie *store[], int size)
873 {
874   struct cookie *chain;
875   int dot_count;
876   char *hash_key;
877   int count = 0;
878
879   if (!hash_table_count (jar->chains_by_domain))
880     return 0;
881
882   STRDUP_ALLOCA (hash_key, host);
883
884   /* Look for an exact match. */
885   chain = hash_table_get (jar->chains_by_domain, hash_key);
886   if (chain)
887     STORE_CHAIN (chain, store, size, count);
888
889   dot_count = count_char (host, '.');
890
891   /* Match less and less specific domains.  For instance, given
892      fly.srk.fer.hr, we match .srk.fer.hr, then .fer.hr.  */
893   while (dot_count-- > 1)
894     {
895       /* Note: we operate directly on hash_key (in form host:port)
896          because we don't want to allocate new hash keys in a
897          loop.  */
898       char *p = strchr (hash_key, '.');
899       assert (p != NULL);
900       chain = hash_table_get (jar->chains_by_domain, p);
901       if (chain)
902         STORE_CHAIN (chain, store, size, count);
903       hash_key = p + 1;
904     }
905   return count;
906 }
907
908 /* If FULL_PATH begins with PREFIX, return the length of PREFIX, zero
909    otherwise.  */
910
911 static int
912 path_matches (const char *full_path, const char *prefix)
913 {
914   int len;
915
916   if (*prefix != '/')
917     /* Wget's HTTP paths do not begin with '/' (the URL code treats it
918        as a separator), but the '/' is assumed when matching against
919        the cookie stuff.  */
920     return 0;
921
922   ++prefix;
923   len = strlen (prefix);
924
925   if (0 != strncmp (full_path, prefix, len))
926     /* FULL_PATH doesn't begin with PREFIX. */
927     return 0;
928
929   /* Length of PREFIX determines the quality of the match. */
930   return len + 1;
931 }
932
933 /* Return non-zero iff COOKIE matches the given PATH, PORT, and
934    security flag.  HOST is not a flag because it is assumed that the
935    cookie comes from the correct chain.
936
937    If PATH_GOODNESS is non-NULL, store the "path goodness" there.  The
938    said goodness is a measure of how well COOKIE matches PATH.  It is
939    used for ordering cookies.  */
940
941 static int
942 matching_cookie (const struct cookie *cookie, const char *path, int port,
943                  int connection_secure_p, int *path_goodness)
944 {
945   int pg;
946
947   if (COOKIE_EXPIRED_P (cookie))
948     /* Ignore stale cookies.  Don't bother unchaining the cookie at
949        this point -- Wget is a relatively short-lived application, and
950        stale cookies will not be saved by `save_cookies'.  On the
951        other hand, this function should be as efficient as
952        possible.  */
953     return 0;
954
955   if (cookie->secure && !connection_secure_p)
956     /* Don't transmit secure cookies over an insecure connection.  */
957     return 0;
958   if (cookie->port != PORT_ANY && cookie->port != port)
959     return 0;
960   pg = path_matches (path, cookie->path);
961   if (!pg)
962     return 0;
963
964   if (path_goodness)
965     /* If the caller requested path_goodness, we return it.  This is
966        an optimization, so that the caller doesn't need to call
967        path_matches() again.  */
968     *path_goodness = pg;
969   return 1;
970 }
971
972 struct weighed_cookie {
973   struct cookie *cookie;
974   int domain_goodness;
975   int path_goodness;
976 };
977
978 /* Comparator used for uniquifying the list. */
979
980 static int
981 equality_comparator (const void *p1, const void *p2)
982 {
983   struct weighed_cookie *wc1 = (struct weighed_cookie *)p1;
984   struct weighed_cookie *wc2 = (struct weighed_cookie *)p2;
985
986   int namecmp  = strcmp (wc1->cookie->attr, wc2->cookie->attr);
987   int valuecmp = strcmp (wc1->cookie->value, wc2->cookie->value);
988
989   /* We only really care whether both name and value are equal.  We
990      return them in this order only for consistency...  */
991   return namecmp ? namecmp : valuecmp;
992 }
993
994 /* Eliminate duplicate cookies.  "Duplicate cookies" are any two
995    cookies whose name and value are the same.  Whenever a duplicate
996    pair is found, one of the cookies is removed.  */
997
998 static int
999 eliminate_dups (struct weighed_cookie *outgoing, int count)
1000 {
1001   int i;
1002
1003   /* We deploy a simple uniquify algorithm: first sort the array
1004      according to our sort criterion, then uniquify it by comparing
1005      each cookie with its neighbor.  */
1006
1007   qsort (outgoing, count, sizeof (struct weighed_cookie), equality_comparator);
1008
1009   for (i = 0; i < count - 1; i++)
1010     {
1011       struct cookie *c1 = outgoing[i].cookie;
1012       struct cookie *c2 = outgoing[i + 1].cookie;
1013       if (!strcmp (c1->attr, c2->attr) && !strcmp (c1->value, c2->value))
1014         {
1015           /* c1 and c2 are the same; get rid of c2. */
1016           if (count > i + 1)
1017             /* move all ptrs from positions [i + 1, count) to i. */
1018             memmove (outgoing + i, outgoing + i + 1,
1019                      (count - (i + 1)) * sizeof (struct weighed_cookie));
1020           /* We decrement i to counter the ++i above.  Remember that
1021              we've just removed the element in front of us; we need to
1022              remain in place to check whether outgoing[i] matches what
1023              used to be outgoing[i + 2].  */
1024           --i;
1025           --count;
1026         }
1027     }
1028   return count;
1029 }
1030
1031 /* Comparator used for sorting by quality. */
1032
1033 static int
1034 goodness_comparator (const void *p1, const void *p2)
1035 {
1036   struct weighed_cookie *wc1 = (struct weighed_cookie *)p1;
1037   struct weighed_cookie *wc2 = (struct weighed_cookie *)p2;
1038
1039   /* Subtractions take `wc2' as the first argument becauase we want a
1040      sort in *decreasing* order of goodness.  */
1041   int dgdiff = wc2->domain_goodness - wc1->domain_goodness;
1042   int pgdiff = wc2->path_goodness - wc1->path_goodness;
1043
1044   /* Sort by domain goodness; if these are the same, sort by path
1045      goodness.  (The sorting order isn't really specified; maybe it
1046      should be the other way around.)  */
1047   return dgdiff ? dgdiff : pgdiff;
1048 }
1049
1050 /* Generate a `Cookie' header for a request that goes to HOST:PORT and
1051    requests PATH from the server.  The resulting string is allocated
1052    with `malloc', and the caller is responsible for freeing it.  If no
1053    cookies pertain to this request, i.e. no cookie header should be
1054    generated, NULL is returned.  */
1055
1056 char *
1057 cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
1058                                    int port, const char *path,
1059                                    int connection_secure_p)
1060 {
1061   struct cookie *chain_default_store[20];
1062   struct cookie **all_chains = chain_default_store;
1063   int chain_store_size = countof (chain_default_store);
1064   int chain_count;
1065
1066   struct cookie *cookie;
1067   struct weighed_cookie *outgoing;
1068   int count, i, ocnt;
1069   char *result;
1070   int result_size, pos;
1071
1072  again:
1073   chain_count = find_matching_chains (jar, host, all_chains, chain_store_size);
1074   if (chain_count > chain_store_size)
1075     {
1076       /* It's extremely unlikely that more than 20 chains will ever
1077          match.  But since find_matching_chains reports the exact size
1078          it needs, it's easy to not have the limitation, so we
1079          don't.  */
1080       all_chains = alloca (chain_count * sizeof (struct cookie *));
1081       chain_store_size = chain_count;
1082       goto again;
1083     }
1084
1085   if (!chain_count)
1086     return NULL;
1087
1088   cookies_now = time (NULL);
1089
1090   /* Count the number of cookies whose path matches. */
1091   count = 0;
1092   for (i = 0; i < chain_count; i++)
1093     for (cookie = all_chains[i]; cookie; cookie = cookie->next)
1094       if (matching_cookie (cookie, path, port, connection_secure_p, NULL))
1095         ++count;
1096   if (!count)
1097     /* No matching cookies. */
1098     return NULL;
1099
1100   /* Allocate the array. */
1101   outgoing = alloca (count * sizeof (struct weighed_cookie));
1102
1103   /* Fill the array with all the matching cookies from all the
1104      matching chains. */
1105   ocnt = 0;
1106   for (i = 0; i < chain_count; i++)
1107     for (cookie = all_chains[i]; cookie; cookie = cookie->next)
1108       {
1109         int pg;
1110         if (!matching_cookie (cookie, path, port, connection_secure_p, &pg))
1111           continue;
1112         outgoing[ocnt].cookie = cookie;
1113         outgoing[ocnt].domain_goodness = strlen (cookie->domain);
1114         outgoing[ocnt].path_goodness   = pg;
1115         ++ocnt;
1116       }
1117   assert (ocnt == count);
1118
1119   /* Eliminate duplicate cookies; that is, those whose name and value
1120      are the same.  */
1121   count = eliminate_dups (outgoing, count);
1122
1123   /* Sort the array so that best-matching domains come first, and
1124      that, within one domain, best-matching paths come first. */
1125   qsort (outgoing, count, sizeof (struct weighed_cookie), goodness_comparator);
1126
1127   /* Count the space the name=value pairs will take. */
1128   result_size = 0;
1129   for (i = 0; i < count; i++)
1130     {
1131       struct cookie *c = outgoing[i].cookie;
1132       /* name=value */
1133       result_size += strlen (c->attr) + 1 + strlen (c->value);
1134     }
1135
1136   /* Allocate output buffer:
1137      "Cookie: "       -- 8
1138      name=value pairs -- result_size
1139      "; " separators  -- (count - 1) * 2
1140      \r\n line ending -- 2
1141      \0 terminator    -- 1 */
1142   result_size = 8 + result_size + (count - 1) * 2 + 2 + 1;
1143   result = xmalloc (result_size);
1144   pos = 0;
1145   strcpy (result, "Cookie: ");
1146   pos += 8;
1147   for (i = 0; i < count; i++)
1148     {
1149       struct cookie *c = outgoing[i].cookie;
1150       int namlen = strlen (c->attr);
1151       int vallen = strlen (c->value);
1152
1153       memcpy (result + pos, c->attr, namlen);
1154       pos += namlen;
1155       result[pos++] = '=';
1156       memcpy (result + pos, c->value, vallen);
1157       pos += vallen;
1158       if (i < count - 1)
1159         {
1160           result[pos++] = ';';
1161           result[pos++] = ' ';
1162         }
1163     }
1164   result[pos++] = '\r';
1165   result[pos++] = '\n';
1166   result[pos++] = '\0';
1167   assert (pos == result_size);
1168   return result;
1169 }
1170 \f
1171 /* Support for loading and saving cookies.  The format used for
1172    loading and saving roughly matches the format of `cookies.txt' file
1173    used by Netscape and Mozilla, at least the Unix versions.  The
1174    format goes like this:
1175
1176        DOMAIN DOMAIN-FLAG PATH SECURE-FLAG TIMESTAMP ATTR-NAME ATTR-VALUE
1177
1178      DOMAIN      -- cookie domain, optionally followed by :PORT
1179      DOMAIN-FLAG -- whether all hosts in the domain match
1180      PATH        -- cookie path
1181      SECURE-FLAG -- whether cookie requires secure connection
1182      TIMESTAMP   -- expiry timestamp, number of seconds since epoch
1183      ATTR-NAME   -- name of the cookie attribute
1184      ATTR-VALUE  -- value of the cookie attribute (empty if absent)
1185
1186    The fields are separated by TABs (but Wget's loader recognizes any
1187    whitespace).  All fields are mandatory, except for ATTR-VALUE.  The
1188    `-FLAG' fields are boolean, their legal values being "TRUE" and
1189    "FALSE'.  Empty lines, lines consisting of whitespace only, and
1190    comment lines (beginning with # optionally preceded by whitespace)
1191    are ignored.
1192
1193    Example line from cookies.txt (split in two lines for readability):
1194
1195        .google.com      TRUE    /       FALSE   2147368447      \
1196        PREF     ID=34bb47565bbcd47b:LD=en:NR=20:TM=985172580:LM=985739012
1197
1198    DOMAIN-FLAG is currently not honored by Wget.  The cookies whose
1199    domain begins with `.' are treated as if DOMAIN-FLAG were true,
1200    while all other cookies are treated as if it were FALSE. */
1201
1202
1203 /* If the region [B, E) ends with :<digits>, parse the number, return
1204    it, and store new boundary (location of the `:') to DOMAIN_E_PTR.
1205    If port is not specified, return 0.  */
1206
1207 static int
1208 domain_port (const char *domain_b, const char *domain_e,
1209              const char **domain_e_ptr)
1210 {
1211   int port = 0;
1212   const char *p;
1213   const char *colon = memchr (domain_b, ':', domain_e - domain_b);
1214   if (!colon)
1215     return 0;
1216   for (p = colon + 1; p < domain_e && ISDIGIT (*p); p++)
1217     port = 10 * port + (*p - '0');
1218   if (p < domain_e)
1219     /* Garbage following port number. */
1220     return 0;
1221   *domain_e_ptr = colon;
1222   return port;
1223 }
1224
1225 #define SKIP_WS(p) do {                         \
1226   while (*p && ISSPACE (*p))                    \
1227     ++p;                                        \
1228 } while (0)
1229
1230 #define SET_WORD_BOUNDARIES(p, b, e) do {       \
1231   SKIP_WS (p);                                  \
1232   b = p;                                        \
1233   /* skip non-ws */                             \
1234   while (*p && !ISSPACE (*p))                   \
1235     ++p;                                        \
1236   e = p;                                        \
1237   if (b == e)                                   \
1238     goto next;                                  \
1239 } while (0)
1240
1241 /* Load cookies from FILE.  */
1242
1243 void
1244 cookie_jar_load (struct cookie_jar *jar, const char *file)
1245 {
1246   char *line;
1247   FILE *fp = fopen (file, "r");
1248   if (!fp)
1249     {
1250       logprintf (LOG_NOTQUIET, "Cannot open cookies file `%s': %s\n",
1251                  file, strerror (errno));
1252       return;
1253     }
1254   cookies_now = time (NULL);
1255
1256   for (; ((line = read_whole_line (fp)) != NULL); xfree (line))
1257     {
1258       struct cookie *cookie;
1259       char *p = line;
1260
1261       double expiry;
1262       int port;
1263
1264       char *domain_b  = NULL, *domain_e  = NULL;
1265       char *ignore_b  = NULL, *ignore_e  = NULL;
1266       char *path_b    = NULL, *path_e    = NULL;
1267       char *secure_b  = NULL, *secure_e  = NULL;
1268       char *expires_b = NULL, *expires_e = NULL;
1269       char *name_b    = NULL, *name_e    = NULL;
1270       char *value_b   = NULL, *value_e   = NULL;
1271
1272       SKIP_WS (p);
1273
1274       if (!*p || *p == '#')
1275         /* empty line */
1276         continue;
1277
1278       SET_WORD_BOUNDARIES (p, domain_b,  domain_e);
1279       SET_WORD_BOUNDARIES (p, ignore_b,  ignore_e);
1280       SET_WORD_BOUNDARIES (p, path_b,    path_e);
1281       SET_WORD_BOUNDARIES (p, secure_b,  secure_e);
1282       SET_WORD_BOUNDARIES (p, expires_b, expires_e);
1283       SET_WORD_BOUNDARIES (p, name_b,    name_e);
1284
1285       /* Don't use SET_WORD_BOUNDARIES for value because it may
1286          contain whitespace.  Instead, set value_e to the end of line,
1287          modulo trailing space (this will skip the line separator.) */
1288       SKIP_WS (p);
1289       value_b = p;
1290       value_e = p + strlen (p);
1291       while (value_e > value_b && ISSPACE (*(value_e - 1)))
1292         --value_e;
1293       if (value_b == value_e)
1294         /* Hmm, should we check for empty value?  I guess that's
1295            legal, so I leave it.  */
1296         ;
1297
1298       cookie = cookie_new ();
1299
1300       cookie->attr    = strdupdelim (name_b, name_e);
1301       cookie->value   = strdupdelim (value_b, value_e);
1302       cookie->path    = strdupdelim (path_b, path_e);
1303
1304       if (BOUNDED_EQUAL (secure_b, secure_e, "TRUE"))
1305         cookie->secure = 1;
1306
1307       /* DOMAIN needs special treatment because we might need to
1308          extract the port.  */
1309       port = domain_port (domain_b, domain_e, (const char **)&domain_e);
1310       if (port)
1311         cookie->port = port;
1312       cookie->domain  = strdupdelim (domain_b, domain_e);
1313
1314       /* safe default in case EXPIRES field is garbled. */
1315       expiry = (double)cookies_now - 1;
1316
1317       /* I don't like changing the line, but it's completely safe.
1318          (line is malloced.)  */
1319       *expires_e = '\0';
1320       sscanf (expires_b, "%lf", &expiry);
1321       if (expiry < cookies_now)
1322         /* ignore stale cookie. */
1323         goto abort;
1324       cookie->expiry_time = expiry;
1325
1326       /* If the cookie has survived being saved into an external file,
1327          it is obviously permanent.  */
1328       cookie->permanent = 1;
1329
1330       store_cookie (jar, cookie);
1331
1332     next:
1333       continue;
1334
1335     abort:
1336       delete_cookie (cookie);
1337     }
1338   fclose (fp);
1339 }
1340
1341 /* Mapper for save_cookies callable by hash_table_map.  VALUE points
1342    to the head in a chain of cookies.  The function prints the entire
1343    chain.  */
1344
1345 static int
1346 save_cookies_mapper (void *key, void *value, void *arg)
1347 {
1348   FILE *fp = (FILE *)arg;
1349   char *domain = (char *)key;
1350   struct cookie *chain = (struct cookie *)value;
1351   for (; chain; chain = chain->next)
1352     {
1353       if (!chain->permanent)
1354         continue;
1355       if (COOKIE_EXPIRED_P (chain))
1356         continue;
1357       fputs (domain, fp);
1358       if (chain->port != PORT_ANY)
1359         fprintf (fp, ":%d", chain->port);
1360       fprintf (fp, "\t%s\t%s\t%s\t%.0f\t%s\t%s\n",
1361                *domain == '.' ? "TRUE" : "FALSE",
1362                chain->path, chain->secure ? "TRUE" : "FALSE",
1363                (double)chain->expiry_time,
1364                chain->attr, chain->value);
1365       if (ferror (fp))
1366         return 1;               /* stop mapping */
1367     }
1368   return 0;
1369 }
1370
1371 /* Save cookies, in format described above, to FILE. */
1372
1373 void
1374 cookie_jar_save (struct cookie_jar *jar, const char *file)
1375 {
1376   FILE *fp;
1377
1378   DEBUGP (("Saving cookies to %s.\n", file));
1379
1380   cookies_now = time (NULL);
1381
1382   fp = fopen (file, "w");
1383   if (!fp)
1384     {
1385       logprintf (LOG_NOTQUIET, _("Cannot open cookies file `%s': %s\n"),
1386                  file, strerror (errno));
1387       return;
1388     }
1389
1390   fputs ("# HTTP cookie file.\n", fp);
1391   fprintf (fp, "# Generated by Wget on %s.\n", datetime_str (NULL));
1392   fputs ("# Edit at your own risk.\n\n", fp);
1393
1394   hash_table_map (jar->chains_by_domain, save_cookies_mapper, fp);
1395
1396   if (ferror (fp))
1397     logprintf (LOG_NOTQUIET, _("Error writing to `%s': %s\n"),
1398                file, strerror (errno));
1399
1400   if (fclose (fp) < 0)
1401     logprintf (LOG_NOTQUIET, _("Error closing `%s': %s\n"),
1402                file, strerror (errno));
1403
1404   DEBUGP (("Done saving cookies.\n"));
1405 }
1406 \f
1407 /* Destroy all the elements in the chain and unhook it from the cookie
1408    jar.  This is written in the form of a callback to hash_table_map
1409    and used by cookie_jar_delete to delete all the cookies in a
1410    jar.  */
1411
1412 static int
1413 nuke_cookie_chain (void *value, void *key, void *arg)
1414 {
1415   char *chain_key = (char *)value;
1416   struct cookie *chain = (struct cookie *)key;
1417   struct cookie_jar *jar = (struct cookie_jar *)arg;
1418
1419   /* Remove the chain from the table and free the key. */
1420   hash_table_remove (jar->chains_by_domain, chain_key);
1421   xfree (chain_key);
1422
1423   /* Then delete all the cookies in the chain. */
1424   while (chain)
1425     {
1426       struct cookie *next = chain->next;
1427       delete_cookie (chain);
1428       chain = next;
1429     }
1430
1431   /* Keep mapping. */
1432   return 0;
1433 }
1434
1435 /* Clean up cookie-related data. */
1436
1437 void
1438 cookie_jar_delete (struct cookie_jar *jar)
1439 {
1440   hash_table_map (jar->chains_by_domain, nuke_cookie_chain, jar);
1441   hash_table_destroy (jar->chains_by_domain);
1442   xfree (jar);
1443 }
1444 \f
1445 /* Test cases.  Currently this is only tests parse_set_cookies.  To
1446    use, recompile Wget with -DTEST_COOKIES and call test_cookies()
1447    from main.  */
1448
1449 #ifdef TEST_COOKIES
1450 int test_count;
1451 char *test_results[10];
1452
1453 static int test_parse_cookies_callback (struct cookie *ignored,
1454                                         const char *nb, const char *ne,
1455                                         const char *vb, const char *ve)
1456 {
1457   test_results[test_count++] = strdupdelim (nb, ne);
1458   test_results[test_count++] = strdupdelim (vb, ve);
1459   return 1;
1460 }
1461
1462 void
1463 test_cookies (void)
1464 {
1465   /* Tests expected to succeed: */
1466   static struct {
1467     char *data;
1468     char *results[10];
1469   } tests_succ[] = {
1470     { "", {NULL} },
1471     { "arg=value", {"arg", "value", NULL} },
1472     { "arg1=value1;arg2=value2", {"arg1", "value1", "arg2", "value2", NULL} },
1473     { "arg1=value1; arg2=value2", {"arg1", "value1", "arg2", "value2", NULL} },
1474     { "arg1=value1;  arg2=value2;", {"arg1", "value1", "arg2", "value2", NULL} },
1475     { "arg1=value1;  arg2=value2;  ", {"arg1", "value1", "arg2", "value2", NULL} },
1476     { "arg1=\"value1\"; arg2=\"\"", {"arg1", "value1", "arg2", "", NULL} },
1477     { "arg=", {"arg", "", NULL} },
1478     { "arg1=; arg2=", {"arg1", "", "arg2", "", NULL} },
1479     { "arg1 = ; arg2= ", {"arg1", "", "arg2", "", NULL} },
1480   };
1481
1482   /* Tests expected to fail: */
1483   static char *tests_fail[] = {
1484     ";",
1485     "arg=\"unterminated",
1486     "=empty-name",
1487     "arg1=;=another-empty-name",
1488   };
1489   int i;
1490
1491   for (i = 0; i < countof (tests_succ); i++)
1492     {
1493       int ind;
1494       char *data = tests_succ[i].data;
1495       char **expected = tests_succ[i].results;
1496       struct cookie *c;
1497
1498       test_count = 0;
1499       c = parse_set_cookies (data, test_parse_cookies_callback, 1);
1500       if (!c)
1501         {
1502           printf ("NULL cookie returned for valid data: %s\n", data);
1503           continue;
1504         }
1505
1506       for (ind = 0; ind < test_count; ind += 2)
1507         {
1508           if (!expected[ind])
1509             break;
1510           if (0 != strcmp (expected[ind], test_results[ind]))
1511             printf ("Invalid name %d for '%s' (expected '%s', got '%s')\n",
1512                     ind / 2 + 1, data, expected[ind], test_results[ind]);
1513           if (0 != strcmp (expected[ind + 1], test_results[ind + 1]))
1514             printf ("Invalid value %d for '%s' (expected '%s', got '%s')\n",
1515                     ind / 2 + 1, data, expected[ind + 1], test_results[ind + 1]);
1516         }
1517       if (ind < test_count || expected[ind])
1518         printf ("Unmatched number of results: %s\n", data);
1519     }
1520
1521   for (i = 0; i < countof (tests_fail); i++)
1522     {
1523       struct cookie *c;
1524       char *data = tests_fail[i];
1525       test_count = 0;
1526       c = parse_set_cookies (data, test_parse_cookies_callback, 1);
1527       if (c)
1528         printf ("Failed to report error on invalid data: %s\n", data);
1529     }
1530 }
1531 #endif /* TEST_COOKIES */