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.
- Ideas for future work:
+ This implements the client-side cookie support, as specified
+ (loosely) by Netscape's "preliminary specification", currently
+ available at:
- * Implement limits on cookie-related sizes, such as max. cookie
- size, max. number of cookies, etc.
+ http://wp.netscape.com/newsref/std/cookie_spec.html
- * Add more "cookie jar" methods, such as methods to iterate over
- stored cookies, to clear temporary cookies, to perform
- intelligent auto-saving, etc.
-
- * Support `Set-Cookie2' and `Cookie2' headers? Does anyone really
- use them? */
+ 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"
#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. */
struct cookie_jar *
cookie_jar_new (void)
{
- struct cookie_jar *jar = xmalloc (sizeof (struct cookie_jar));
+ struct cookie_jar *jar = xnew (struct cookie_jar);
jar->chains = make_nocase_string_hash_table (0);
jar->cookie_count = 0;
return jar;
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
- the session */
- time_t expiry_time; /* time when the cookie expires */
-
- int discard_requested; /* whether cookie was created to
- request discarding another
- cookie */
+ unsigned permanent :1; /* whether the cookie should outlive
+ the session. */
+ time_t expiry_time; /* time when the cookie expires, 0
+ means undetermined. */
char *attr; /* cookie attribute name */
char *value; /* cookie attribute value */
};
#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 bool
+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
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));
+ IF_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));
+ }
}
/* Discard a cookie matching COOKIE's domain, port, path, and
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
+ Returns true on success. Returns false in case a syntax error is
found; such a cookie should be discarded. */
-static int
+static bool
update_cookie_field (struct cookie *cookie,
const char *name_b, const char *name_e,
const char *value_b, const char *value_e)
if (!cookie->attr)
{
if (!VALUE_EXISTS)
- return 0;
+ return false;
cookie->attr = strdupdelim (name_b, name_e);
cookie->value = strdupdelim (value_b, value_e);
- return 1;
+ return true;
}
if (NAME_IS ("domain"))
{
if (!VALUE_NON_EMPTY)
- return 0;
- FREE_MAYBE (cookie->domain);
+ return false;
+ 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
if (*value_b == '.')
++value_b;
cookie->domain = strdupdelim (value_b, value_e);
- return 1;
+ return true;
}
else if (NAME_IS ("path"))
{
if (!VALUE_NON_EMPTY)
- return 0;
- FREE_MAYBE (cookie->path);
+ return false;
+ xfree_null (cookie->path);
cookie->path = strdupdelim (value_b, value_e);
- return 1;
+ return true;
}
else if (NAME_IS ("expires"))
{
time_t expires;
if (!VALUE_NON_EMPTY)
- return 0;
+ return false;
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
if (cookie->expiry_time < cookies_now)
cookie->discard_requested = 1;
- return 1;
+ return true;
}
else if (NAME_IS ("max-age"))
{
char *value_copy;
if (!VALUE_NON_EMPTY)
- return 0;
+ return false;
BOUNDED_TO_ALLOCA (value_b, value_e, value_copy);
sscanf (value_copy, "%lf", &maxage);
if (maxage == -1)
/* something went wrong. */
- return 0;
+ return false;
cookie->permanent = 1;
cookie->expiry_time = cookies_now + maxage;
if (maxage == 0)
cookie->discard_requested = 1;
- return 1;
+ return true;
}
else if (NAME_IS ("secure"))
{
/* ignore value completely */
cookie->secure = 1;
- return 1;
+ return true;
}
else
/* Unrecognized attribute; ignore it. */
- return 1;
+ return true;
}
#undef NAME_IS
-/* Returns non-zero for characters that are legal in the name of an
+/* Returns true 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"
static struct cookie *
parse_set_cookies (const char *sc,
- int (*callback) (struct cookie *,
- const char *, const char *,
- const char *, const char *),
- int silent)
+ bool (*callback) (struct cookie *,
+ const char *, const char *,
+ const char *, const char *),
+ bool silent)
{
struct cookie *cookie = cookie_new ();
break;
case S_ATTR_ACTION:
{
- int legal = callback (cookie, name_b, name_e, value_b, value_e);
+ bool 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'"), name);
+ _("Error in Set-Cookie, field `%s'"),
+ escnonprint (name));
}
state = S_ERROR;
break;
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
#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>.
- 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
+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;
- static char *known_toplevel_domains[] = {
+ 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 PARAMS ((const char *, const char *));
+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;
}
\f
/* Process the HTTP `Set-Cookie' header. This results in storing the
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);
- cookie = parse_set_cookies (set_cookie, update_cookie_field, 0);
+ cookie = parse_set_cookies (set_cookie, update_cookie_field, false);
if (!cookie)
goto out;
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",
}
}
+ /* Now store the cookie, or discard an existing cookie, if
+ discarding was requested. */
+
if (cookie->discard_requested)
{
discard_matching_cookie (jar, cookie);
/* 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. */
+
+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.
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;
- 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
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
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, bool secflag)
{
struct cookie **chains;
int chain_count;
count = 0;
for (i = 0; i < chain_count; i++)
for (cookie = chains[i]; cookie; cookie = cookie->next)
- if (cookie_matches_url (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 */
for (cookie = chains[i]; cookie; cookie = cookie->next)
{
int pg;
- if (!cookie_matches_url (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);
}
/* 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;
result[pos++] = ' ';
}
}
- result[pos++] = '\r';
- result[pos++] = '\n';
result[pos++] = '\0';
assert (pos == result_size);
return result;
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;
}
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);
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);
}
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, 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));
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)
+static bool 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;
+ return true;
}
void
struct cookie *c;
test_count = 0;
- c = parse_set_cookies (data, test_parse_cookies_callback, 1);
+ c = parse_set_cookies (data, test_parse_cookies_callback, true);
if (!c)
{
printf ("NULL cookie returned for valid data: %s\n", data);