]> sjero.net Git - wget/blobdiff - src/cookies.c
[svn] Remove K&R support.
[wget] / src / cookies.c
index 31bc77e9c19824c1fe4c133798b096c6c29f05f2..a75799e5fbf2939283eebd19bfd593d4364a278f 100644 (file)
@@ -27,26 +27,29 @@ modify this file, you may extend this exception to your version of the
 file, but you are not obligated to do so.  If you do not wish to do
 so, delete this exception statement from your version.  */
 
-/* Written by Hrvoje Niksic.  Parts are loosely inspired by cookie
-   code submitted by Tomasz Wegrzanowski.
+/* Written by Hrvoje Niksic.  Parts are loosely inspired by the
+   cookie patch submitted by Tomasz Wegrzanowski.
 
-   TODO: Implement limits on cookie-related sizes, such as max. cookie
-   size, max. number of cookies, etc.  Add more "cookie jar" methods,
-   such as methods to over stored cookies, to clear temporary cookies,
-   to perform intelligent auto-saving, etc.  Ultimately support
-   `Set-Cookie2' and `Cookie2' headers.  */
+   This implements the client-side cookie support, as specified
+   (loosely) by Netscape's "preliminary specification", currently
+   available at:
+
+       http://wp.netscape.com/newsref/std/cookie_spec.html
+
+   rfc2109 is not supported because of its incompatibilities with the
+   above widely-used specification.  rfc2965 is entirely ignored,
+   since popular client software doesn't implement it, and even the
+   sites that do send Set-Cookie2 also emit Set-Cookie for
+   compatibility.  */
 
 #include <config.h>
 
 #include <stdio.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <errno.h>
+#include <time.h>
 
 #include "wget.h"
 #include "utils.h"
@@ -54,15 +57,25 @@ so, delete this exception statement from your version.  */
 #include "cookies.h"
 
 /* This should *really* be in a .h file!  */
-time_t http_atotm PARAMS ((const char *));
+time_t http_atotm (const char *);
 \f
 /* Declarations of `struct cookie' and the most basic functions. */
 
+/* Cookie jar serves as cookie storage and a means of retrieving
+   cookies efficiently.  All cookies with the same domain are stored
+   in a linked list called "chain".  A cookie chain can be reached by
+   looking up the domain in the cookie jar's chains_by_domain table.
+
+   For example, to reach all the cookies under google.com, one must
+   execute hash_table_get(jar->chains_by_domain, "google.com").  Of
+   course, when sending a cookie to `www.google.com', one must search
+   for cookies that belong to either `www.google.com' or `google.com'
+   -- but the point is that the code doesn't need to go through *all*
+   the cookies.  */
+
 struct cookie_jar {
-  /* Hash table that maps domain names to cookie chains.  A "cookie
-     chain" is a linked list of cookies that belong to the same
-     domain.  */
-  struct hash_table *chains_by_domain;
+  /* Cookie chains indexed by domain.  */
+  struct hash_table *chains;
 
   int cookie_count;            /* number of cookies in the jar. */
 };
@@ -74,8 +87,8 @@ time_t cookies_now;
 struct cookie_jar *
 cookie_jar_new (void)
 {
-  struct cookie_jar *jar = xmalloc (sizeof (struct cookie_jar));
-  jar->chains_by_domain = make_nocase_string_hash_table (0);
+  struct cookie_jar *jar = xnew (struct cookie_jar);
+  jar->chains = make_nocase_string_hash_table (0);
   jar->cookie_count = 0;
   return jar;
 }
@@ -92,59 +105,65 @@ struct cookie {
                                   whole. */
 
   int permanent;               /* whether the cookie should outlive
-                                  the session */
-  time_t expiry_time;          /* time when the cookie expires */
+                                  the session. */
+  time_t expiry_time;          /* time when the cookie expires, 0
+                                  means undetermined. */
 
   int discard_requested;       /* whether cookie was created to
                                   request discarding another
-                                  cookie */
+                                  cookie. */
 
   char *attr;                  /* cookie attribute name */
   char *value;                 /* cookie attribute value */
 
-  struct cookie_jar *jar;      /* pointer back to the cookie jar, for
-                                  convenience. */
   struct cookie *next;         /* used for chaining of cookies in the
                                   same domain. */
 };
 
 #define PORT_ANY (-1)
-#define COOKIE_EXPIRED_P(c) ((c)->expiry_time != 0 && (c)->expiry_time < cookies_now)
 
 /* Allocate and return a new, empty cookie structure. */
 
 static struct cookie *
 cookie_new (void)
 {
-  struct cookie *cookie = xmalloc (sizeof (struct cookie));
-  memset (cookie, '\0', sizeof (struct cookie));
+  struct cookie *cookie = xnew0 (struct cookie);
 
-  /* Both cookie->permanent and cookie->expiry_time are now 0.  By
-     default, we assume that the cookie is non-permanent and valid
-     until the end of the session.  */
+  /* Both cookie->permanent and cookie->expiry_time are now 0.  This
+     means that the cookie doesn't expire, but is only valid for this
+     session (i.e. not written out to disk).  */
 
   cookie->port = PORT_ANY;
   return cookie;
 }
 
+/* Non-zero if the cookie has expired.  Assumes cookies_now has been
+   set by one of the entry point functions.  */
+
+static int
+cookie_expired_p (const struct cookie *c)
+{
+  return c->expiry_time != 0 && c->expiry_time < cookies_now;
+}
+
 /* Deallocate COOKIE and its components. */
 
 static void
 delete_cookie (struct cookie *cookie)
 {
-  FREE_MAYBE (cookie->domain);
-  FREE_MAYBE (cookie->path);
-  FREE_MAYBE (cookie->attr);
-  FREE_MAYBE (cookie->value);
+  xfree_null (cookie->domain);
+  xfree_null (cookie->path);
+  xfree_null (cookie->attr);
+  xfree_null (cookie->value);
   xfree (cookie);
 }
 \f
 /* Functions for storing cookies.
 
-   All cookies can be reached beginning with jar->chains_by_domain.
-   The key in that table is the domain name, and the value is a linked
-   list of all cookies from that domain.  Every new cookie is placed
-   on the head of the list.  */
+   All cookies can be reached beginning with jar->chains.  The key in
+   that table is the domain name, and the value is a linked list of
+   all cookies from that domain.  Every new cookie is placed on the
+   head of the list.  */
 
 /* Find and return a cookie in JAR whose domain, path, and attribute
    name correspond to COOKIE.  If found, PREVPTR will point to the
@@ -159,7 +178,7 @@ find_matching_cookie (struct cookie_jar *jar, struct cookie *cookie,
 {
   struct cookie *chain, *prev;
 
-  chain = hash_table_get (jar->chains_by_domain, cookie->domain);
+  chain = hash_table_get (jar->chains, cookie->domain);
   if (!chain)
     goto nomatch;
 
@@ -193,7 +212,7 @@ store_cookie (struct cookie_jar *jar, struct cookie *cookie)
   struct cookie *chain_head;
   char *chain_key;
 
-  if (hash_table_get_pair (jar->chains_by_domain, cookie->domain,
+  if (hash_table_get_pair (jar->chains, cookie->domain,
                           &chain_key, &chain_head))
     {
       /* A chain of cookies in this domain already exists.  Check for
@@ -227,26 +246,32 @@ store_cookie (struct cookie_jar *jar, struct cookie *cookie)
     }
   else
     {
-      /* We are now creating the chain.  Allocate the string that will
-        be used as a key.  It is unsafe to use cookie->domain for
-        that, because it might get deallocated by the above code at
-        some point later.  */
+      /* We are now creating the chain.  Use a copy of cookie->domain
+        as the key for the life-time of the chain.  Using
+        cookie->domain would be unsafe because the life-time of the
+        chain may exceed the life-time of the cookie.  (Cookies may
+        be deleted from the chain by this very function.)  */
       cookie->next = NULL;
       chain_key = xstrdup (cookie->domain);
     }
 
-  hash_table_put (jar->chains_by_domain, chain_key, cookie);
+  hash_table_put (jar->chains, chain_key, cookie);
   ++jar->cookie_count;
 
-  DEBUGP (("\nStored cookie %s %d%s %s %s %d %s %s %s\n",
-          cookie->domain, cookie->port,
-          cookie->port == PORT_ANY ? " (ANY)" : "",
-          cookie->path,
-          cookie->permanent ? "permanent" : "nonpermanent",
-          cookie->secure,
-          cookie->expiry_time
-          ? asctime (localtime (&cookie->expiry_time)) : "<undefined>",
-          cookie->attr, cookie->value));
+#ifdef ENABLE_DEBUG
+  if (opt.debug)
+    {
+      time_t exptime = cookie->expiry_time;
+      DEBUGP (("\nStored cookie %s %d%s %s <%s> <%s> [expiry %s] %s %s\n",
+              cookie->domain, cookie->port,
+              cookie->port == PORT_ANY ? " (ANY)" : "",
+              cookie->path,
+              cookie->permanent ? "permanent" : "session",
+              cookie->secure ? "secure" : "insecure",
+              cookie->expiry_time ? datetime_str (&exptime) : "none",
+              cookie->attr, cookie->value));
+    }
+#endif
 }
 
 /* Discard a cookie matching COOKIE's domain, port, path, and
@@ -260,7 +285,7 @@ discard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie)
 {
   struct cookie *prev, *victim;
 
-  if (!hash_table_count (jar->chains_by_domain))
+  if (!hash_table_count (jar->chains))
     /* No elements == nothing to discard. */
     return;
 
@@ -277,18 +302,18 @@ discard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie)
          char *chain_key = NULL;
          int res;
 
-         res = hash_table_get_pair (jar->chains_by_domain, victim->domain,
+         res = hash_table_get_pair (jar->chains, victim->domain,
                                     &chain_key, NULL);
          assert (res != 0);
          if (!victim->next)
            {
              /* VICTIM was the only cookie in the chain.  Destroy the
                 chain and deallocate the chain key.  */
-             hash_table_remove (jar->chains_by_domain, victim->domain);
+             hash_table_remove (jar->chains, victim->domain);
              xfree (chain_key);
            }
          else
-           hash_table_put (jar->chains_by_domain, chain_key, victim->next);
+           hash_table_put (jar->chains, chain_key, victim->next);
        }
       delete_cookie (victim);
       DEBUGP (("Discarded old cookie.\n"));
@@ -298,7 +323,6 @@ discard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie)
 /* Functions for parsing the `Set-Cookie' header, and creating new
    cookies from the wire.  */
 
-
 #define NAME_IS(string_literal)                                        \
   BOUNDED_EQUAL_NO_CASE (name_b, name_e, string_literal)
 
@@ -339,7 +363,7 @@ update_cookie_field (struct cookie *cookie,
     {
       if (!VALUE_NON_EMPTY)
        return 0;
-      FREE_MAYBE (cookie->domain);
+      xfree_null (cookie->domain);
       /* Strictly speaking, we should set cookie->domain_exact if the
         domain doesn't begin with a dot.  But many sites set the
         domain to "foo.com" and expect "subhost.foo.com" to get the
@@ -353,7 +377,7 @@ update_cookie_field (struct cookie *cookie,
     {
       if (!VALUE_NON_EMPTY)
        return 0;
-      FREE_MAYBE (cookie->path);
+      xfree_null (cookie->path);
       cookie->path = strdupdelim (value_b, value_e);
       return 1;
     }
@@ -367,14 +391,14 @@ update_cookie_field (struct cookie *cookie,
       BOUNDED_TO_ALLOCA (value_b, value_e, value_copy);
 
       expires = http_atotm (value_copy);
-      if (expires != -1)
+      if (expires != (time_t) -1)
        {
          cookie->permanent = 1;
-         cookie->expiry_time = (time_t)expires;
+         cookie->expiry_time = expires;
        }
       else
-       /* Error in expiration spec.  Assume default (cookie valid for
-          this session.)  */
+       /* Error in expiration spec.  Assume default (cookie doesn't
+          expire, but valid only for this session.)  */
        ;
 
       /* According to netscape's specification, expiry time in the
@@ -589,7 +613,8 @@ parse_set_cookies (const char *sc,
                    char *name;
                    BOUNDED_TO_ALLOCA (name_b, name_e, name);
                    logprintf (LOG_NOTQUIET,
-                              _("Error in Set-Cookie, field `%s'"), name);
+                              _("Error in Set-Cookie, field `%s'"),
+                              escnonprint (name));
                  }
                state = S_ERROR;
                break;
@@ -613,7 +638,7 @@ parse_set_cookies (const char *sc,
   if (!silent)
     logprintf (LOG_NOTQUIET,
               _("Syntax error in Set-Cookie: %s at position %d.\n"),
-              sc, p - sc);
+              escnonprint (sc), p - sc);
   return NULL;
 }
 \f
@@ -636,9 +661,9 @@ parse_set_cookies (const char *sc,
 
 /* Check whether ADDR matches <digits>.<digits>.<digits>.<digits>.
 
-  We don't want to call network functions like inet_addr() because all
-  we need is a check, preferrably one that is small, fast, and
-  well-defined.  */
+   We don't want to call network functions like inet_addr() because
+   all we need is a check, preferrably one that is small, fast, and
+   well-defined.  */
 
 static int
 numeric_address_p (const char *addr)
@@ -756,7 +781,7 @@ check_domain_match (const char *cookie_domain, const char *host)
       {
        int i;
        int known_toplevel = 0;
-       static char *known_toplevel_domains[] = {
+       static const char *known_toplevel_domains[] = {
          ".com", ".edu", ".net", ".org", ".gov", ".mil", ".int"
        };
        for (i = 0; i < countof (known_toplevel_domains); i++)
@@ -772,7 +797,8 @@ check_domain_match (const char *cookie_domain, const char *host)
 
   DEBUGP ((" 7"));
 
-  /* Don't allow domain "bar.com" to match host "foobar.com".  */
+  /* Don't allow the host "foobar.com" to set a cookie for domain
+     "bar.com".  */
   if (*cookie_domain != '.')
     {
       int dlen = strlen (cookie_domain);
@@ -789,7 +815,7 @@ check_domain_match (const char *cookie_domain, const char *host)
   return 1;
 }
 
-static int path_matches PARAMS ((const char *, const char *));
+static int path_matches (const char *, const char *);
 
 /* Check whether PATH begins with COOKIE_PATH. */
 
@@ -804,9 +830,9 @@ check_path_match (const char *cookie_path, const char *path)
    depending on the contents.  */
 
 void
-cookie_jar_process_set_cookie (struct cookie_jar *jar,
-                              const char *host, int port,
-                              const char *path, const char *set_cookie)
+cookie_handle_set_cookie (struct cookie_jar *jar,
+                         const char *host, int port,
+                         const char *path, const char *set_cookie)
 {
   struct cookie *cookie;
   cookies_now = time (NULL);
@@ -820,23 +846,40 @@ cookie_jar_process_set_cookie (struct cookie_jar *jar,
   if (!cookie->domain)
     {
     copy_domain:
+      /* If the domain was not provided, we use the one we're talking
+        to, and set exact match.  */
       cookie->domain = xstrdup (host);
-      cookie->port = port;
+      cookie->domain_exact = 1;
+      /* Set the port, but only if it's non-default. */
+      if (port != 80 && port != 443)
+       cookie->port = port;
     }
   else
     {
       if (!check_domain_match (cookie->domain, host))
        {
          logprintf (LOG_NOTQUIET,
-                    "Cookie coming from %s attempted to set domain to %s\n",
-                    host, cookie->domain);
+                    _("Cookie coming from %s attempted to set domain to %s\n"),
+                    escnonprint (host), escnonprint (cookie->domain));
+         xfree (cookie->domain);
          goto copy_domain;
        }
     }
+
   if (!cookie->path)
-    cookie->path = xstrdup (path);
+    {
+      /* The cookie doesn't set path: set it to the URL path, sans the
+        file part ("/dir/file" truncated to "/dir/").  */
+      char *trailing_slash = strrchr (path, '/');
+      if (trailing_slash)
+       cookie->path = strdupdelim (path, trailing_slash + 1);
+      else
+       /* no slash in the string -- can this even happen? */
+       cookie->path = xstrdup (path);
+    }
   else
     {
+      /* The cookie sets its own path; verify that it is legal. */
       if (!check_path_match (cookie->path, path))
        {
          DEBUGP (("Attempt to fake the path: %s, %s\n",
@@ -845,6 +888,9 @@ cookie_jar_process_set_cookie (struct cookie_jar *jar,
        }
     }
 
+  /* Now store the cookie, or discard an existing cookie, if
+     discarding was requested.  */
+
   if (cookie->discard_requested)
     {
       discard_matching_cookie (jar, cookie);
@@ -862,25 +908,41 @@ cookie_jar_process_set_cookie (struct cookie_jar *jar,
 /* Support for sending out cookies in HTTP requests, based on
    previously stored cookies.  Entry point is
    `build_cookies_request'.  */
+   
+/* Return a count of how many times CHR occurs in STRING. */
 
-/* Find the cookie chains that match HOST and store them to DEST.
+static int
+count_char (const char *string, char chr)
+{
+  const char *p;
+  int count = 0;
+  for (p = string; *p; p++)
+    if (*p == chr)
+      ++count;
+  return count;
+}
+
+/* Find the cookie chains whose domains match HOST and store them to
+   DEST.
 
-   A cookie chain is the list of cookies declared under a domain.
-   Given HOST "img.search.xemacs.org", this function will store the
-   chains for "img.search.xemacs.org", "search.xemacs.org", and
-   "xemacs.org" -- those of them that exist (if any), that is.
+   A cookie chain is the head of a list of cookies that belong to a
+   host/domain.  Given HOST "img.search.xemacs.org", this function
+   will return the chains for "img.search.xemacs.org",
+   "search.xemacs.org", and "xemacs.org" -- those of them that exist
+   (if any), that is.
 
-   No more than SIZE matches are written; if more matches are present,
-   return the number of chains that would have been written.  */
+   DEST should be large enough to accept (in the worst case) as many
+   elements as there are domain components of HOST.  */
 
 static int
-find_matching_chains (struct cookie_jar *jar, const char *host,
-                     struct cookie *dest[], int dest_size)
+find_chains_of_host (struct cookie_jar *jar, const char *host,
+                    struct cookie *dest[])
 {
   int dest_count = 0;
   int passes, passcnt;
 
-  if (!hash_table_count (jar->chains_by_domain))
+  /* Bail out quickly if there are no cookies in the jar.  */
+  if (!hash_table_count (jar->chains))
     return 0;
 
   if (numeric_address_p (host))
@@ -900,13 +962,9 @@ find_matching_chains (struct cookie_jar *jar, const char *host,
      srk.fer.hr's, then fer.hr's.  */
   while (1)
     {
-      struct cookie *chain = hash_table_get (jar->chains_by_domain, host);
+      struct cookie *chain = hash_table_get (jar->chains, host);
       if (chain)
-       {
-         if (dest_count < dest_size)
-           dest[dest_count] = chain;
-         ++dest_count;
-       }
+       dest[dest_count++] = chain;
       if (++passcnt >= passes)
        break;
       host = strchr (host, '.') + 1;
@@ -925,8 +983,8 @@ path_matches (const char *full_path, const char *prefix)
 
   if (*prefix != '/')
     /* Wget's HTTP paths do not begin with '/' (the URL code treats it
-       as a separator), but the '/' is assumed when matching against
-       the cookie stuff.  */
+       as a mere separator, inspired by rfc1808), but the '/' is
+       assumed when matching against the cookie stuff.  */
     return 0;
 
   ++prefix;
@@ -940,21 +998,21 @@ path_matches (const char *full_path, const char *prefix)
   return len + 1;
 }
 
-/* Return non-zero iff COOKIE matches the given HOST, PORT, PATH, and
-   SECFLAG.
+/* Return non-zero iff COOKIE matches the provided parameters of the
+   URL being downloaded: HOST, PORT, PATH, and SECFLAG.
 
    If PATH_GOODNESS is non-NULL, store the "path goodness" value
-   there.  That value is a measure of how well COOKIE matches PATH,
+   there.  That value is a measure of how closely COOKIE matches PATH,
    used for ordering cookies.  */
 
 static int
-matching_cookie (const struct cookie *cookie,
-                const char *host, int port, const char *path,
-                int secure, int *path_goodness)
+cookie_matches_url (const struct cookie *cookie,
+                   const char *host, int port, const char *path,
+                   int secflag, int *path_goodness)
 {
   int pg;
 
-  if (COOKIE_EXPIRED_P (cookie))
+  if (cookie_expired_p (cookie))
     /* Ignore stale cookies.  Don't bother unchaining the cookie at
        this point -- Wget is a relatively short-lived application, and
        stale cookies will not be saved by `save_cookies'.  On the
@@ -962,7 +1020,7 @@ matching_cookie (const struct cookie *cookie,
        possible.  */
     return 0;
 
-  if (cookie->secure && !secure)
+  if (cookie->secure && !secflag)
     /* Don't transmit secure cookies over insecure connections.  */
     return 0;
   if (cookie->port != PORT_ANY && cookie->port != port)
@@ -970,7 +1028,7 @@ matching_cookie (const struct cookie *cookie,
 
   /* If exact domain match is required, verify that cookie's domain is
      equal to HOST.  If not, assume success on the grounds of the
-     cookie's chain having been found by find_matching_chains.  */
+     cookie's chain having been found by find_chains_of_host.  */
   if (cookie->domain_exact
       && 0 != strcasecmp (host, cookie->domain))
     return 0;
@@ -1015,40 +1073,45 @@ equality_comparator (const void *p1, const void *p2)
 }
 
 /* Eliminate duplicate cookies.  "Duplicate cookies" are any two
-   cookies whose name and value are the same.  Whenever a duplicate
+   cookies with the same attr name and value.  Whenever a duplicate
    pair is found, one of the cookies is removed.  */
 
 static int
 eliminate_dups (struct weighed_cookie *outgoing, int count)
 {
-  int i;
+  struct weighed_cookie *h;    /* hare */
+  struct weighed_cookie *t;    /* tortoise */
+  struct weighed_cookie *end = outgoing + count;
 
   /* We deploy a simple uniquify algorithm: first sort the array
-     according to our sort criteria, then uniquify it by comparing
-     each cookie with its neighbor.  */
+     according to our sort criteria, then copy it to itself, comparing
+     each cookie to its neighbor and ignoring the duplicates.  */
 
   qsort (outgoing, count, sizeof (struct weighed_cookie), equality_comparator);
 
-  for (i = 0; i < count - 1; i++)
+  /* "Hare" runs through all the entries in the array, followed by
+     "tortoise".  If a duplicate is found, the hare skips it.
+     Non-duplicate entries are copied to the tortoise ptr.  */
+
+  for (h = t = outgoing; h < end; h++)
     {
-      struct cookie *c1 = outgoing[i].cookie;
-      struct cookie *c2 = outgoing[i + 1].cookie;
-      if (!strcmp (c1->attr, c2->attr) && !strcmp (c1->value, c2->value))
+      if (h != end - 1)
        {
-         /* c1 and c2 are the same; get rid of c2. */
-         if (count > i + 1)
-           /* move all ptrs from positions [i + 1, count) to i. */
-           memmove (outgoing + i, outgoing + i + 1,
-                    (count - (i + 1)) * sizeof (struct weighed_cookie));
-         /* We decrement i to counter the ++i above.  Remember that
-            we've just removed the element in front of us; we need to
-            remain in place to check whether outgoing[i] matches what
-            used to be outgoing[i + 2].  */
-         --i;
-         --count;
+         struct cookie *c0 = h[0].cookie;
+         struct cookie *c1 = h[1].cookie;
+         if (!strcmp (c0->attr, c1->attr) && !strcmp (c0->value, c1->value))
+           continue;           /* ignore the duplicate */
        }
+
+      /* If the hare has advanced past the tortoise (because of
+        previous dups), make sure the values get copied.  Otherwise,
+        no copying is necessary.  */
+      if (h != t)
+       *t++ = *h;
+      else
+       t++;
     }
-  return count;
+  return t - outgoing;
 }
 
 /* Comparator used for sorting by quality. */
@@ -1077,13 +1140,10 @@ goodness_comparator (const void *p1, const void *p2)
    generated, NULL is returned.  */
 
 char *
-cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
-                                  int port, const char *path,
-                                  int connection_secure_p)
+cookie_header (struct cookie_jar *jar, const char *host,
+              int port, const char *path, int secflag)
 {
-  struct cookie *chain_default_store[5];
-  struct cookie **chains = chain_default_store;
-  int chain_store_size = countof (chain_default_store);
+  struct cookie **chains;
   int chain_count;
 
   struct cookie *cookie;
@@ -1092,19 +1152,15 @@ cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
   char *result;
   int result_size, pos;
 
-  /* First, find the chains that match HOST. */
- again:
-  chain_count = find_matching_chains (jar, host, chains, chain_store_size);
-  if (chain_count > chain_store_size)
-    {
-      /* It's unlikely that more than 5 chains will ever match.  But
-        since find_matching_chains reports the exact size it needs,
-        it's easy to not have the limitation, so we don't.  */
-      chains = alloca (chain_count * sizeof (struct cookie *));
-      chain_store_size = chain_count;
-      goto again;
-    }
+  /* First, find the cookie chains whose domains match HOST. */
+
+  /* Allocate room for find_chains_of_host to write to.  The number of
+     chains can at most equal the number of subdomains, hence
+     1+<number of dots>.  */
+  chains = alloca_array (struct cookie *, 1 + count_char (host, '.'));
+  chain_count = find_chains_of_host (jar, host, chains);
 
+  /* No cookies for this host. */
   if (!chain_count)
     return NULL;
 
@@ -1118,13 +1174,13 @@ cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
   count = 0;
   for (i = 0; i < chain_count; i++)
     for (cookie = chains[i]; cookie; cookie = cookie->next)
-      if (matching_cookie (cookie, host, port, path, connection_secure_p, NULL))
+      if (cookie_matches_url (cookie, host, port, path, secflag, NULL))
        ++count;
   if (!count)
     return NULL;               /* no cookies matched */
 
   /* Allocate the array. */
-  outgoing = alloca (count * sizeof (struct weighed_cookie));
+  outgoing = alloca_array (struct weighed_cookie, count);
 
   /* Fill the array with all the matching cookies from the chains that
      match HOST. */
@@ -1133,8 +1189,7 @@ cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
     for (cookie = chains[i]; cookie; cookie = cookie->next)
       {
        int pg;
-       if (!matching_cookie (cookie, host, port, path,
-                             connection_secure_p, &pg))
+       if (!cookie_matches_url (cookie, host, port, path, secflag, &pg))
          continue;
        outgoing[ocnt].cookie = cookie;
        outgoing[ocnt].domain_goodness = strlen (cookie->domain);
@@ -1161,16 +1216,12 @@ cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
     }
 
   /* Allocate output buffer:
-     "Cookie: "       -- 8
      name=value pairs -- result_size
      "; " separators  -- (count - 1) * 2
-     \r\n line ending -- 2
      \0 terminator    -- 1 */
-  result_size = 8 + result_size + (count - 1) * 2 + 2 + 1;
+  result_size = result_size + (count - 1) * 2 + 1;
   result = xmalloc (result_size);
   pos = 0;
-  strcpy (result, "Cookie: ");
-  pos += 8;
   for (i = 0; i < count; i++)
     {
       struct cookie *c = outgoing[i].cookie;
@@ -1188,8 +1239,6 @@ cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
          result[pos++] = ' ';
        }
     }
-  result[pos++] = '\r';
-  result[pos++] = '\n';
   result[pos++] = '\0';
   assert (pos == result_size);
   return result;
@@ -1265,7 +1314,7 @@ cookie_jar_load (struct cookie_jar *jar, const char *file)
   FILE *fp = fopen (file, "r");
   if (!fp)
     {
-      logprintf (LOG_NOTQUIET, "Cannot open cookies file `%s': %s\n",
+      logprintf (LOG_NOTQUIET, _("Cannot open cookies file `%s': %s\n"),
                 file, strerror (errno));
       return;
     }
@@ -1341,21 +1390,28 @@ cookie_jar_load (struct cookie_jar *jar, const char *file)
         malloced.)  */
       *expires_e = '\0';
       sscanf (expires_b, "%lf", &expiry);
-      if (expiry < cookies_now)
-       /* ignore stale cookie. */
-       goto abort;
-      cookie->expiry_time = expiry;
 
-      /* If the cookie has survived being saved into an external file,
-        it is obviously permanent.  */
-      cookie->permanent = 1;
+      if (expiry == 0)
+       {
+         /* EXPIRY can be 0 for session cookies saved because the
+            user specified `--keep-session-cookies' in the past.
+            They remain session cookies, and will be saved only if
+            the user has specified `keep-session-cookies' again.  */
+       }
+      else
+       {
+         if (expiry < cookies_now)
+           goto abort_cookie;  /* ignore stale cookie. */
+         cookie->expiry_time = expiry;
+         cookie->permanent = 1;
+       }
 
       store_cookie (jar, cookie);
 
     next:
       continue;
 
-    abort:
+    abort_cookie:
       delete_cookie (cookie);
     }
   fclose (fp);
@@ -1373,9 +1429,9 @@ save_cookies_mapper (void *key, void *value, void *arg)
   struct cookie *cookie = (struct cookie *)value;
   for (; cookie; cookie = cookie->next)
     {
-      if (!cookie->permanent)
+      if (!cookie->permanent && !opt.keep_session_cookies)
        continue;
-      if (COOKIE_EXPIRED_P (cookie))
+      if (cookie_expired_p (cookie))
        continue;
       if (!cookie->domain_exact)
        fputc ('.', fp);
@@ -1413,15 +1469,14 @@ cookie_jar_save (struct cookie_jar *jar, const char *file)
     }
 
   fputs ("# HTTP cookie file.\n", fp);
-  fprintf (fp, "# Generated by Wget on %s.\n", datetime_str (NULL));
+  fprintf (fp, "# Generated by Wget on %s.\n", datetime_str (&cookies_now));
   fputs ("# Edit at your own risk.\n\n", fp);
 
-  hash_table_map (jar->chains_by_domain, save_cookies_mapper, fp);
+  hash_table_map (jar->chains, save_cookies_mapper, fp);
 
   if (ferror (fp))
     logprintf (LOG_NOTQUIET, _("Error writing to `%s': %s\n"),
               file, strerror (errno));
-
   if (fclose (fp) < 0)
     logprintf (LOG_NOTQUIET, _("Error closing `%s': %s\n"),
               file, strerror (errno));
@@ -1442,7 +1497,7 @@ nuke_cookie_chain (void *value, void *key, void *arg)
   struct cookie_jar *jar = (struct cookie_jar *)arg;
 
   /* Remove the chain from the table and free the key. */
-  hash_table_remove (jar->chains_by_domain, chain_key);
+  hash_table_remove (jar->chains, chain_key);
   xfree (chain_key);
 
   /* Then delete all the cookies in the chain. */
@@ -1462,8 +1517,8 @@ nuke_cookie_chain (void *value, void *key, void *arg)
 void
 cookie_jar_delete (struct cookie_jar *jar)
 {
-  hash_table_map (jar->chains_by_domain, nuke_cookie_chain, jar);
-  hash_table_destroy (jar->chains_by_domain);
+  hash_table_map (jar->chains, nuke_cookie_chain, jar);
+  hash_table_destroy (jar->chains);
   xfree (jar);
 }
 \f