X-Git-Url: http://sjero.net/git/?p=wget;a=blobdiff_plain;f=src%2Fcookies.c;h=4f7d2ac39a1282a18cd93d5d75e64286c265a83e;hp=a75799e5fbf2939283eebd19bfd593d4364a278f;hb=4d7c5e087b2bc82c9f503dff003916d1047903ce;hpb=277e840a0f8e3ec8800cfe7407fe3c16000bc622 diff --git a/src/cookies.c b/src/cookies.c index a75799e5..4f7d2ac3 100644 --- a/src/cookies.c +++ b/src/cookies.c @@ -1,11 +1,11 @@ /* Support for cookies. - Copyright (C) 2001, 2002 Free Software Foundation, Inc. + Copyright (C) 2001-2006 Free Software Foundation, Inc. This file is part of GNU Wget. GNU Wget is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or (at +the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Wget is distributed in the hope that it will be useful, but @@ -14,8 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +along with Wget. If not, see . In addition, as a special exception, the Free Software Foundation gives permission to link the code of its release of Wget with the @@ -55,9 +54,7 @@ so, delete this exception statement from your version. */ #include "utils.h" #include "hash.h" #include "cookies.h" - -/* This should *really* be in a .h file! */ -time_t http_atotm (const char *); +#include "http.h" /* for http_atotm */ /* Declarations of `struct cookie' and the most basic functions. */ @@ -82,7 +79,7 @@ struct cookie_jar { /* Value set by entry point functions, so that the low-level routines don't need to call time() all the time. */ -time_t cookies_now; +static time_t cookies_now; struct cookie_jar * cookie_jar_new (void) @@ -98,21 +95,21 @@ struct cookie { int port; /* port number */ char *path; /* path prefix of the cookie */ - int secure; /* whether cookie should be + unsigned discard_requested :1; /* whether cookie was created to + request discarding another + cookie. */ + + unsigned secure :1; /* whether cookie should be transmitted over non-https connections. */ - int domain_exact; /* whether DOMAIN must match as a + unsigned domain_exact :1; /* whether DOMAIN must match as a whole. */ - int permanent; /* whether the cookie should outlive + unsigned permanent :1; /* whether the cookie should outlive 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. */ - char *attr; /* cookie attribute name */ char *value; /* cookie attribute value */ @@ -140,7 +137,7 @@ cookie_new (void) /* Non-zero if the cookie has expired. Assumes cookies_now has been set by one of the entry point functions. */ -static int +static bool cookie_expired_p (const struct cookie *c) { return c->expiry_time != 0 && c->expiry_time < cookies_now; @@ -258,8 +255,7 @@ store_cookie (struct cookie_jar *jar, struct cookie *cookie) hash_table_put (jar->chains, chain_key, cookie); ++jar->cookie_count; -#ifdef ENABLE_DEBUG - if (opt.debug) + IF_DEBUG { time_t exptime = cookie->expiry_time; DEBUGP (("\nStored cookie %s %d%s %s <%s> <%s> [expiry %s] %s %s\n", @@ -268,10 +264,9 @@ store_cookie (struct cookie_jar *jar, struct cookie *cookie) cookie->path, cookie->permanent ? "permanent" : "session", cookie->secure ? "secure" : "insecure", - cookie->expiry_time ? datetime_str (&exptime) : "none", + cookie->expiry_time ? datetime_str (exptime) : "none", cookie->attr, cookie->value)); } -#endif } /* Discard a cookie matching COOKIE's domain, port, path, and @@ -323,139 +318,10 @@ 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) - -#define VALUE_EXISTS (value_b && value_e) - -#define VALUE_NON_EMPTY (VALUE_EXISTS && (value_b != value_e)) - -/* Update the appropriate cookie field. [name_b, name_e) are expected - to delimit the attribute name, while [value_b, value_e) (optional) - should delimit the attribute value. - - When called the first time, it will set the cookie's attribute name - and value. After that, it will check the attribute name for - special fields such as `domain', `path', etc. Where appropriate, - it will parse the values of the fields it recognizes and fill the - corresponding fields in COOKIE. - - Returns 1 on success. Returns zero in case a syntax error is - found; such a cookie should be discarded. */ - -static int -update_cookie_field (struct cookie *cookie, - const char *name_b, const char *name_e, - const char *value_b, const char *value_e) -{ - assert (name_b != NULL && name_e != NULL); - - if (!cookie->attr) - { - if (!VALUE_EXISTS) - return 0; - cookie->attr = strdupdelim (name_b, name_e); - cookie->value = strdupdelim (value_b, value_e); - return 1; - } - - if (NAME_IS ("domain")) - { - if (!VALUE_NON_EMPTY) - return 0; - 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 - cookie, and it apparently works. */ - if (*value_b == '.') - ++value_b; - cookie->domain = strdupdelim (value_b, value_e); - return 1; - } - else if (NAME_IS ("path")) - { - if (!VALUE_NON_EMPTY) - return 0; - xfree_null (cookie->path); - cookie->path = strdupdelim (value_b, value_e); - return 1; - } - else if (NAME_IS ("expires")) - { - char *value_copy; - time_t expires; - - if (!VALUE_NON_EMPTY) - return 0; - BOUNDED_TO_ALLOCA (value_b, value_e, value_copy); - - expires = http_atotm (value_copy); - if (expires != (time_t) -1) - { - cookie->permanent = 1; - cookie->expiry_time = expires; - } - else - /* 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 - past means that discarding of a matching cookie is - requested. */ - if (cookie->expiry_time < cookies_now) - cookie->discard_requested = 1; - - return 1; - } - else if (NAME_IS ("max-age")) - { - double maxage = -1; - char *value_copy; - - if (!VALUE_NON_EMPTY) - return 0; - BOUNDED_TO_ALLOCA (value_b, value_e, value_copy); - - sscanf (value_copy, "%lf", &maxage); - if (maxage == -1) - /* something went wrong. */ - return 0; - cookie->permanent = 1; - cookie->expiry_time = cookies_now + maxage; - - /* According to rfc2109, a cookie with max-age of 0 means that - discarding of a matching cookie is requested. */ - if (maxage == 0) - cookie->discard_requested = 1; - - return 1; - } - else if (NAME_IS ("secure")) - { - /* ignore value completely */ - cookie->secure = 1; - return 1; - } - else - /* Unrecognized attribute; ignore it. */ - return 1; -} - -#undef NAME_IS - -/* Returns non-zero for characters that are legal in the name of an - attribute. This used to allow only alphanumerics, '-', and '_', - but we need to be more lenient because a number of sites wants to - use weirder attribute names. rfc2965 "informally specifies" - attribute name (token) as "a sequence of non-special, non-white - space characters". So we allow everything except the stuff we know - could harm us. */ +#define TOKEN_IS(token, string_literal) \ + BOUNDED_EQUAL_NO_CASE (token.b, token.e, string_literal) -#define ATTR_NAME_CHAR(c) ((c) > 32 && (c) < 127 \ - && (c) != '"' && (c) != '=' \ - && (c) != ';' && (c) != ',') +#define TOKEN_NON_EMPTY(token) (token.b != NULL && token.b != token.e) /* Parse the contents of the `Set-Cookie' header. The header looks like this: @@ -465,182 +331,123 @@ update_cookie_field (struct cookie *cookie, Trailing semicolon is optional; spaces are allowed between all tokens. Additionally, values may be quoted. - A new cookie is returned upon success, NULL otherwise. The - specified CALLBACK function (normally `update_cookie_field' is used - to update the fields of the newly created cookie structure. */ + A new cookie is returned upon success, NULL otherwise. + + The first name-value pair will be used to set the cookie's + attribute name and value. Subsequent parameters will be checked + against field names such as `domain', `path', etc. Recognized + fields will be parsed and the corresponding members of COOKIE + filled. */ static struct cookie * -parse_set_cookies (const char *sc, - int (*callback) (struct cookie *, - const char *, const char *, - const char *, const char *), - int silent) +parse_set_cookie (const char *set_cookie, bool silent) { + const char *ptr = set_cookie; struct cookie *cookie = cookie_new (); + param_token name, value; - /* #### Hand-written DFAs are no fun to debug. We'de be better off - to rewrite this as an inline parser. */ - - enum { S_START, S_NAME, S_NAME_POST, - S_VALUE_PRE, S_VALUE, S_QUOTED_VALUE, S_VALUE_TRAILSPACE, - S_ATTR_ACTION, S_DONE, S_ERROR - } state = S_START; - - const char *p = sc; - char c; + if (!extract_param (&ptr, &name, &value, ';')) + goto error; + if (!value.b) + goto error; + cookie->attr = strdupdelim (name.b, name.e); + cookie->value = strdupdelim (value.b, value.e); - const char *name_b = NULL, *name_e = NULL; - const char *value_b = NULL, *value_e = NULL; - - c = *p; - - while (state != S_DONE && state != S_ERROR) + while (extract_param (&ptr, &name, &value, ';')) { - switch (state) + if (TOKEN_IS (name, "domain")) { - case S_START: - if (!c) - state = S_DONE; - else if (ISSPACE (c)) - /* Strip all whitespace preceding the name. */ - c = *++p; - else if (ATTR_NAME_CHAR (c)) - { - name_b = p; - state = S_NAME; - } - else - /* empty attr name not allowed */ - state = S_ERROR; - break; - case S_NAME: - if (!c || c == ';' || c == '=' || ISSPACE (c)) - { - name_e = p; - state = S_NAME_POST; - } - else if (ATTR_NAME_CHAR (c)) - c = *++p; - else - state = S_ERROR; - break; - case S_NAME_POST: - if (!c || c == ';') - { - value_b = value_e = NULL; - if (c == ';') - c = *++p; - state = S_ATTR_ACTION; - } - else if (c == '=') - { - c = *++p; - state = S_VALUE_PRE; - } - else if (ISSPACE (c)) - /* Ignore space and keep the state. */ - c = *++p; - else - state = S_ERROR; - break; - case S_VALUE_PRE: - if (!c || c == ';') - { - value_b = value_e = p; - if (c == ';') - c = *++p; - state = S_ATTR_ACTION; - } - else if (c == '"') - { - c = *++p; - value_b = p; - state = S_QUOTED_VALUE; - } - else if (ISSPACE (c)) - c = *++p; - else - { - value_b = p; - value_e = NULL; - state = S_VALUE; - } - break; - case S_VALUE: - if (!c || c == ';' || ISSPACE (c)) - { - value_e = p; - state = S_VALUE_TRAILSPACE; - } - else - { - value_e = NULL; /* no trailing space */ - c = *++p; - } - break; - case S_QUOTED_VALUE: - if (c == '"') - { - value_e = p; - c = *++p; - state = S_VALUE_TRAILSPACE; - } - else if (!c) - state = S_ERROR; - else - c = *++p; - break; - case S_VALUE_TRAILSPACE: - if (c == ';') + if (!TOKEN_NON_EMPTY (value)) + goto error; + 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 + cookie, and it apparently works in browsers. */ + if (*value.b == '.') + ++value.b; + cookie->domain = strdupdelim (value.b, value.e); + } + else if (TOKEN_IS (name, "path")) + { + if (!TOKEN_NON_EMPTY (value)) + goto error; + xfree_null (cookie->path); + cookie->path = strdupdelim (value.b, value.e); + } + else if (TOKEN_IS (name, "expires")) + { + char *value_copy; + time_t expires; + + if (!TOKEN_NON_EMPTY (value)) + goto error; + BOUNDED_TO_ALLOCA (value.b, value.e, value_copy); + + expires = http_atotm (value_copy); + if (expires != (time_t) -1) { - c = *++p; - state = S_ATTR_ACTION; + cookie->permanent = 1; + cookie->expiry_time = expires; + /* According to netscape's specification, expiry time in + the past means that discarding of a matching cookie + is requested. */ + if (cookie->expiry_time < cookies_now) + cookie->discard_requested = 1; } - else if (!c) - state = S_ATTR_ACTION; - else if (ISSPACE (c)) - c = *++p; else - state = S_VALUE; - break; - case S_ATTR_ACTION: - { - int legal = callback (cookie, name_b, name_e, value_b, value_e); - if (!legal) - { - if (!silent) - { - char *name; - BOUNDED_TO_ALLOCA (name_b, name_e, name); - logprintf (LOG_NOTQUIET, - _("Error in Set-Cookie, field `%s'"), - escnonprint (name)); - } - state = S_ERROR; - break; - } - state = S_START; - } - break; - case S_DONE: - case S_ERROR: - /* handled by loop condition */ - break; + /* Error in expiration spec. Assume default (cookie doesn't + expire, but valid only for this session.) */ + ; } + else if (TOKEN_IS (name, "max-age")) + { + double maxage = -1; + char *value_copy; + + if (!TOKEN_NON_EMPTY (value)) + goto error; + BOUNDED_TO_ALLOCA (value.b, value.e, value_copy); + + sscanf (value_copy, "%lf", &maxage); + if (maxage == -1) + /* something went wrong. */ + goto error; + cookie->permanent = 1; + cookie->expiry_time = cookies_now + maxage; + + /* According to rfc2109, a cookie with max-age of 0 means that + discarding of a matching cookie is requested. */ + if (maxage == 0) + cookie->discard_requested = 1; + } + else if (TOKEN_IS (name, "secure")) + { + /* ignore value completely */ + cookie->secure = 1; + } + else + /* Ignore unrecognized attribute. */ + ; } - if (state == S_DONE) - return cookie; + if (*ptr) + /* extract_param has encountered a syntax error */ + goto error; - delete_cookie (cookie); - if (state != S_ERROR) - abort (); + /* The cookie has been successfully constructed; return it. */ + return cookie; + error: if (!silent) logprintf (LOG_NOTQUIET, _("Syntax error in Set-Cookie: %s at position %d.\n"), - escnonprint (sc), p - sc); + escnonprint (set_cookie), (int) (ptr - set_cookie)); + delete_cookie (cookie); return NULL; } + +#undef TOKEN_IS +#undef TOKEN_NON_EMPTY /* Sanity checks. These are important, otherwise it is possible for mailcious attackers to destroy important cookie information and/or @@ -649,14 +456,14 @@ parse_set_cookies (const char *sc, #define REQUIRE_DIGITS(p) do { \ if (!ISDIGIT (*p)) \ - return 0; \ + return false; \ for (++p; ISDIGIT (*p); p++) \ ; \ } while (0) #define REQUIRE_DOT(p) do { \ if (*p++ != '.') \ - return 0; \ + return false; \ } while (0) /* Check whether ADDR matches .... @@ -665,7 +472,7 @@ parse_set_cookies (const char *sc, all we need is a check, preferrably one that is small, fast, and well-defined. */ -static int +static bool numeric_address_p (const char *addr) { const char *p = addr; @@ -679,8 +486,8 @@ numeric_address_p (const char *addr) REQUIRE_DIGITS (p); /* D */ if (*p != '\0') - return 0; - return 1; + return false; + return true; } /* Check whether COOKIE_DOMAIN is an appropriate domain for HOST. @@ -688,7 +495,7 @@ numeric_address_p (const char *addr) the sites deviated too often, so I had to fall back to "tail matching", as defined by the original Netscape's cookie spec. */ -static int +static bool check_domain_match (const char *cookie_domain, const char *host) { DEBUGP (("cdm: 1")); @@ -702,13 +509,13 @@ check_domain_match (const char *cookie_domain, const char *host) /* For the sake of efficiency, check for exact match first. */ if (0 == strcasecmp (cookie_domain, host)) - return 1; + return true; DEBUGP ((" 3")); /* HOST must match the tail of cookie_domain. */ - if (!match_tail (host, cookie_domain, 1)) - return 0; + if (!match_tail (host, cookie_domain, true)) + return false; /* We know that COOKIE_DOMAIN is a subset of HOST; however, we must make sure that somebody is not trying to set the cookie for a @@ -754,7 +561,7 @@ check_domain_match (const char *cookie_domain, const char *host) case '.': if (ldcl == 0) /* Empty domain component found -- the domain is invalid. */ - return 0; + return false; if (*(p + 1) == '\0') { /* Tolerate trailing '.' by not treating the domain as @@ -773,25 +580,25 @@ check_domain_match (const char *cookie_domain, const char *host) DEBUGP ((" 5")); if (dccount < 2) - return 0; + return false; DEBUGP ((" 6")); if (dccount == 2) { int i; - int known_toplevel = 0; + int known_toplevel = false; static const char *known_toplevel_domains[] = { ".com", ".edu", ".net", ".org", ".gov", ".mil", ".int" }; for (i = 0; i < countof (known_toplevel_domains); i++) - if (match_tail (cookie_domain, known_toplevel_domains[i], 1)) + if (match_tail (cookie_domain, known_toplevel_domains[i], true)) { - known_toplevel = 1; + known_toplevel = true; break; } if (!known_toplevel && nldcl <= 3) - return 0; + return false; } } @@ -807,23 +614,34 @@ check_domain_match (const char *cookie_domain, const char *host) /* desired domain: bar.com */ /* '.' must be here in host-> ^ */ if (hlen > dlen && host[hlen - dlen - 1] != '.') - return 0; + return false; } DEBUGP ((" 8")); - return 1; + return true; } static int path_matches (const char *, const char *); /* Check whether PATH begins with COOKIE_PATH. */ -static int +static bool check_path_match (const char *cookie_path, const char *path) { - return path_matches (path, cookie_path); + return path_matches (path, cookie_path) != 0; } + +/* Prepend '/' to string S. S is copied to fresh stack-allocated + space and its value is modified to point to the new location. */ + +#define PREPEND_SLASH(s) do { \ + char *PS_newstr = (char *) alloca (1 + strlen (s) + 1); \ + *PS_newstr = '/'; \ + strcpy (PS_newstr + 1, s); \ + s = PS_newstr; \ +} while (0) + /* Process the HTTP `Set-Cookie' header. This results in storing the cookie or discarding a matching one, or ignoring it completely, all @@ -837,7 +655,12 @@ cookie_handle_set_cookie (struct cookie_jar *jar, struct cookie *cookie; cookies_now = time (NULL); - cookie = parse_set_cookies (set_cookie, update_cookie_field, 0); + /* Wget's paths don't begin with '/' (blame rfc1808), but cookie + usage assumes /-prefixed paths. Until the rest of Wget is fixed, + simply prepend slash to PATH. */ + PREPEND_SLASH (path); + + cookie = parse_set_cookie (set_cookie, false); if (!cookie) goto out; @@ -979,16 +802,7 @@ find_chains_of_host (struct cookie_jar *jar, const char *host, static int path_matches (const char *full_path, const char *prefix) { - int len; - - if (*prefix != '/') - /* Wget's HTTP paths do not begin with '/' (the URL code treats it - as a mere separator, inspired by rfc1808), but the '/' is - assumed when matching against the cookie stuff. */ - return 0; - - ++prefix; - len = strlen (prefix); + int len = strlen (prefix); if (0 != strncmp (full_path, prefix, len)) /* FULL_PATH doesn't begin with PREFIX. */ @@ -998,17 +812,17 @@ path_matches (const char *full_path, const char *prefix) return len + 1; } -/* Return non-zero iff COOKIE matches the provided parameters of the - URL being downloaded: HOST, PORT, PATH, and SECFLAG. +/* Return true 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 closely COOKIE matches PATH, used for ordering cookies. */ -static int +static bool cookie_matches_url (const struct cookie *cookie, const char *host, int port, const char *path, - int secflag, int *path_goodness) + bool secflag, int *path_goodness) { int pg; @@ -1018,31 +832,31 @@ cookie_matches_url (const struct cookie *cookie, stale cookies will not be saved by `save_cookies'. On the other hand, this function should be as efficient as possible. */ - return 0; + return false; if (cookie->secure && !secflag) /* Don't transmit secure cookies over insecure connections. */ - return 0; + return false; if (cookie->port != PORT_ANY && cookie->port != port) - return 0; + return false; /* 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_chains_of_host. */ if (cookie->domain_exact && 0 != strcasecmp (host, cookie->domain)) - return 0; + return false; pg = path_matches (path, cookie->path); - if (!pg) - return 0; + if (pg == 0) + return false; if (path_goodness) /* If the caller requested path_goodness, we return it. This is an optimization, so that the caller doesn't need to call path_matches() again. */ *path_goodness = pg; - return 1; + return true; } /* A structure that points to a cookie, along with the additional @@ -1141,7 +955,7 @@ goodness_comparator (const void *p1, const void *p2) char * cookie_header (struct cookie_jar *jar, const char *host, - int port, const char *path, int secflag) + int port, const char *path, bool secflag) { struct cookie **chains; int chain_count; @@ -1151,6 +965,7 @@ cookie_header (struct cookie_jar *jar, const char *host, int count, i, ocnt; char *result; int result_size, pos; + PREPEND_SLASH (path); /* see cookie_handle_set_cookie */ /* First, find the cookie chains whose domains match HOST. */ @@ -1417,44 +1232,13 @@ cookie_jar_load (struct cookie_jar *jar, const char *file) fclose (fp); } -/* Mapper for save_cookies callable by hash_table_map. VALUE points - to the head in a chain of cookies. The function prints the entire - chain. */ - -static int -save_cookies_mapper (void *key, void *value, void *arg) -{ - FILE *fp = (FILE *)arg; - char *domain = (char *)key; - struct cookie *cookie = (struct cookie *)value; - for (; cookie; cookie = cookie->next) - { - if (!cookie->permanent && !opt.keep_session_cookies) - continue; - if (cookie_expired_p (cookie)) - continue; - if (!cookie->domain_exact) - fputc ('.', fp); - fputs (domain, fp); - if (cookie->port != PORT_ANY) - fprintf (fp, ":%d", cookie->port); - fprintf (fp, "\t%s\t%s\t%s\t%.0f\t%s\t%s\n", - cookie->domain_exact ? "FALSE" : "TRUE", - cookie->path, cookie->secure ? "TRUE" : "FALSE", - (double)cookie->expiry_time, - cookie->attr, cookie->value); - if (ferror (fp)) - return 1; /* stop mapping */ - } - return 0; -} - /* Save cookies, in format described above, to FILE. */ void cookie_jar_save (struct cookie_jar *jar, const char *file) { FILE *fp; + hash_table_iterator iter; DEBUGP (("Saving cookies to %s.\n", file)); @@ -1469,11 +1253,36 @@ 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 (&cookies_now)); + 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, save_cookies_mapper, fp); - + for (hash_table_iterate (jar->chains, &iter); + hash_table_iter_next (&iter); + ) + { + const char *domain = iter.key; + struct cookie *cookie = iter.value; + for (; cookie; cookie = cookie->next) + { + if (!cookie->permanent && !opt.keep_session_cookies) + continue; + if (cookie_expired_p (cookie)) + continue; + if (!cookie->domain_exact) + fputc ('.', fp); + fputs (domain, fp); + if (cookie->port != PORT_ANY) + fprintf (fp, ":%d", cookie->port); + fprintf (fp, "\t%s\t%s\t%s\t%.0f\t%s\t%s\n", + cookie->domain_exact ? "FALSE" : "TRUE", + cookie->path, cookie->secure ? "TRUE" : "FALSE", + (double)cookie->expiry_time, + cookie->attr, cookie->value); + if (ferror (fp)) + goto out; + } + } + out: if (ferror (fp)) logprintf (LOG_NOTQUIET, _("Error writing to `%s': %s\n"), file, strerror (errno)); @@ -1484,40 +1293,25 @@ cookie_jar_save (struct cookie_jar *jar, const char *file) DEBUGP (("Done saving cookies.\n")); } -/* Destroy all the elements in the chain and unhook it from the cookie - jar. This is written in the form of a callback to hash_table_map - and used by cookie_jar_delete to delete all the cookies in a - jar. */ - -static int -nuke_cookie_chain (void *value, void *key, void *arg) -{ - char *chain_key = (char *)value; - struct cookie *chain = (struct cookie *)key; - struct cookie_jar *jar = (struct cookie_jar *)arg; - - /* Remove the chain from the table and free the key. */ - hash_table_remove (jar->chains, chain_key); - xfree (chain_key); - - /* Then delete all the cookies in the chain. */ - while (chain) - { - struct cookie *next = chain->next; - delete_cookie (chain); - chain = next; - } - - /* Keep mapping. */ - return 0; -} - /* Clean up cookie-related data. */ void cookie_jar_delete (struct cookie_jar *jar) { - hash_table_map (jar->chains, nuke_cookie_chain, jar); + /* Iterate over chains (indexed by domain) and free them. */ + hash_table_iterator iter; + for (hash_table_iterate (jar->chains, &iter); hash_table_iter_next (&iter); ) + { + struct cookie *chain = iter.value; + xfree (iter.key); + /* Then all cookies in this chain. */ + while (chain) + { + struct cookie *next = chain->next; + delete_cookie (chain); + chain = next; + } + } hash_table_destroy (jar->chains); xfree (jar); } @@ -1527,27 +1321,14 @@ cookie_jar_delete (struct cookie_jar *jar) from main. */ #ifdef TEST_COOKIES -int test_count; -char *test_results[10]; - -static int test_parse_cookies_callback (struct cookie *ignored, - const char *nb, const char *ne, - const char *vb, const char *ve) -{ - test_results[test_count++] = strdupdelim (nb, ne); - test_results[test_count++] = strdupdelim (vb, ve); - return 1; -} - void test_cookies (void) { /* Tests expected to succeed: */ static struct { - char *data; - char *results[10]; + const char *data; + const char *results[10]; } tests_succ[] = { - { "", {NULL} }, { "arg=value", {"arg", "value", NULL} }, { "arg1=value1;arg2=value2", {"arg1", "value1", "arg2", "value2", NULL} }, { "arg1=value1; arg2=value2", {"arg1", "value1", "arg2", "value2", NULL} }, @@ -1571,39 +1352,51 @@ test_cookies (void) for (i = 0; i < countof (tests_succ); i++) { int ind; - char *data = tests_succ[i].data; - char **expected = tests_succ[i].results; + const char *data = tests_succ[i].data; + const char **expected = tests_succ[i].results; struct cookie *c; - test_count = 0; - c = parse_set_cookies (data, test_parse_cookies_callback, 1); + c = parse_set_cookie (data, true); if (!c) { printf ("NULL cookie returned for valid data: %s\n", data); continue; } - for (ind = 0; ind < test_count; ind += 2) - { - if (!expected[ind]) - break; - if (0 != strcmp (expected[ind], test_results[ind])) - printf ("Invalid name %d for '%s' (expected '%s', got '%s')\n", - ind / 2 + 1, data, expected[ind], test_results[ind]); - if (0 != strcmp (expected[ind + 1], test_results[ind + 1])) - printf ("Invalid value %d for '%s' (expected '%s', got '%s')\n", - ind / 2 + 1, data, expected[ind + 1], test_results[ind + 1]); - } - if (ind < test_count || expected[ind]) - printf ("Unmatched number of results: %s\n", data); + /* Test whether extract_param handles these cases correctly. */ + { + param_token name, value; + const char *ptr = data; + int j = 0; + while (extract_param (&ptr, &name, &value, ';')) + { + char *n = strdupdelim (name.b, name.e); + char *v = strdupdelim (value.b, value.e); + if (!expected[j]) + { + printf ("Too many parameters for '%s'\n", data); + break; + } + if (0 != strcmp (expected[j], n)) + printf ("Invalid name %d for '%s' (expected '%s', got '%s')\n", + j / 2 + 1, data, expected[j], n); + if (0 != strcmp (expected[j + 1], v)) + printf ("Invalid value %d for '%s' (expected '%s', got '%s')\n", + j / 2 + 1, data, expected[j + 1], v); + j += 2; + free (n); + free (v); + } + if (expected[j]) + printf ("Too few parameters for '%s'\n", data); + } } for (i = 0; i < countof (tests_fail); i++) { struct cookie *c; char *data = tests_fail[i]; - test_count = 0; - c = parse_set_cookies (data, test_parse_cookies_callback, 1); + c = parse_set_cookie (data, true); if (c) printf ("Failed to report error on invalid data: %s\n", data); }