/* 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.
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, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
In addition, as a special exception, the Free Software Foundation
gives permission to link the code of its release of Wget with the
#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 */
\f
/* Declarations of `struct cookie' and the most basic functions. */
/* 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)
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 */
/* 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;
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));
}
}
/* 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:
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;
}
- 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.) */
+ ;
+
+ /* 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 (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
\f
/* Sanity checks. These are important, otherwise it is possible for
mailcious attackers to destroy important cookie information and/or
#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 <digits>.<digits>.<digits>.<digits>.
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;
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.
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"));
/* 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
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
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;
}
}
/* 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)
+
\f
/* Process the HTTP `Set-Cookie' header. This results in storing the
cookie or discarding a matching one, or ignoring it completely, all
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;
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. */
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;
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
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;
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. */
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));
}
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));
DEBUGP (("Done saving cookies.\n"));
}
\f
-/* 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);
}
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} },
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);
}