1
0
mirror of https://github.com/moparisthebest/wget synced 2024-07-03 16:38:41 -04:00

[svn] Cookie interface and implementation changes

from <sxsofge9yao.fsf@florida.arsdigita.de>
and <sxslmbim7dg.fsf@florida.arsdigita.de>.
This commit is contained in:
hniksic 2002-04-20 13:46:38 -07:00
parent 40dcdf528c
commit 29a66192cf
6 changed files with 262 additions and 199 deletions

View File

@ -1,3 +1,31 @@
2002-04-20 Hrvoje Niksic <hniksic@arsdigita.com>
* init.c: Ditto.
* main.c: Ditto.
* http.c: Use the new interface.
* cookies.c: Provide an OO-style "cookie jar" interface to enable
separate cookie jars.
* http.c (http_atotm): Declare argument as const.
2002-04-20 Hrvoje Niksic <hniksic@arsdigita.com>
* cookies.c (cookie_new): Default to PORT_ANY.
(find_cookie_chain_exact): Only search by DOMAIN.
(find_matching_cookie): Also check that PORT matches.
(store_cookie): Only match the domain.
(set_cookie_header_cb): When a cookie "fakes" a domain, assume it
is valid for that host rather than discarding it completely.
(find_matching_chains): Don't search by PORT.
(matching_cookie): Also match PORT.
(load_cookies): Set the port if specified, otherwise leave it as
ANY.
(save_cookies_mapper): Save the port if specified, otherwise leave
it empty.
2002-04-19 Thomas Lussnig <thomas.lussnig@bewegungsmelder.de> 2002-04-19 Thomas Lussnig <thomas.lussnig@bewegungsmelder.de>
* init.c: The option `egdfile' was not in sort order. * init.c: The option `egdfile' was not in sort order.

View File

@ -1,5 +1,5 @@
/* Support for cookies. /* Support for cookies.
Copyright (C) 2001 Free Software Foundation, Inc. Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Wget. This file is part of GNU Wget.
@ -18,7 +18,13 @@ along with Wget; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* Written by Hrvoje Niksic. Parts are loosely inspired by cookie /* Written by Hrvoje Niksic. Parts are loosely inspired by cookie
code submitted by Tomasz Wegrzanowski. */ code 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. */
#include <config.h> #include <config.h>
@ -35,23 +41,34 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "wget.h" #include "wget.h"
#include "utils.h" #include "utils.h"
#include "hash.h" #include "hash.h"
#include "url.h"
#include "cookies.h" #include "cookies.h"
/* Hash table that maps domain names to cookie chains. */
static struct hash_table *cookies_hash_table;
/* This should be set by entry points in this file, so the low-level
functions don't need to call time() all the time. */
static time_t cookies_now;
/* This should *really* be in a .h file! */ /* This should *really* be in a .h file! */
time_t http_atotm PARAMS ((char *)); time_t http_atotm PARAMS ((const char *));
/* Definition of `struct cookie' and the most basic functions. */ /* Declarations of `struct cookie' and the most basic functions. */
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;
int cookie_count; /* number of cookies in the 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;
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);
jar->cookie_count = 0;
return jar;
}
struct cookie { struct cookie {
char *domain; /* domain of the cookie */ char *domain; /* domain of the cookie */
@ -62,7 +79,7 @@ struct cookie {
connections. */ connections. */
int permanent; /* whether the cookie should outlive int permanent; /* whether the cookie should outlive
the session */ the session */
unsigned long expiry_time; /* time when the cookie expires */ time_t expiry_time; /* time when the cookie expires */
int discard_requested; /* whether cookie was created to int discard_requested; /* whether cookie was created to
request discarding another request discarding another
cookie */ cookie */
@ -70,10 +87,15 @@ struct cookie {
char *attr; /* cookie attribute name */ char *attr; /* cookie attribute name */
char *value; /* cookie attribute value */ 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 struct cookie *next; /* used for chaining of cookies in the
same domain. */ 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. */ /* Allocate and return a new, empty cookie structure. */
static struct cookie * static struct cookie *
@ -82,13 +104,11 @@ cookie_new (void)
struct cookie *cookie = xmalloc (sizeof (struct cookie)); struct cookie *cookie = xmalloc (sizeof (struct cookie));
memset (cookie, '\0', sizeof (struct cookie)); memset (cookie, '\0', sizeof (struct cookie));
/* If we don't know better, assume cookie is non-permanent and valid /* Both cookie->permanent and cookie->expiry_time are now 0. By
for the entire session. */ default, we assume that the cookie is non-permanent and valid
cookie->expiry_time = ~(unsigned long)0; until the end of the session. */
/* Assume default port. */
cookie->port = 80;
cookie->port = PORT_ANY;
return cookie; return cookie;
} }
@ -106,59 +126,33 @@ delete_cookie (struct cookie *cookie)
/* Functions for storing cookies. /* Functions for storing cookies.
All cookies can be referenced through cookies_hash_table. The key All cookies can be reached beginning with jar->chains_by_domain.
in that table is the domain name, and the value is a linked list of The key in that table is the domain name, and the value is a linked
all cookies from that domain. Every new cookie is placed on the list of all cookies from that domain. Every new cookie is placed
head of the list. */ on the head of the list. */
/* Write "HOST:PORT" to a stack-allocated area and make RESULT point /* Find and return a cookie in JAR whose domain, path, and attribute
to that area. RESULT should be a character pointer. Useful for name correspond to COOKIE. If found, PREVPTR will point to the
creating HOST:PORT strings, which are the keys in the hash location of the cookie previous in chain, or NULL if the found
table. */ cookie is the head of a chain.
#define SET_HOSTPORT(host, port, result) do { \
int HP_len = strlen (host); \
result = alloca (HP_len + 1 + numdigit (port) + 1); \
memcpy (result, host, HP_len); \
result[HP_len] = ':'; \
number_to_string (result + HP_len + 1, port); \
} while (0)
/* Find cookie chain that corresponds to DOMAIN (exact) and PORT. */
static struct cookie *
find_cookie_chain_exact (const char *domain, int port)
{
char *key;
if (!cookies_hash_table)
return NULL;
SET_HOSTPORT (domain, port, key);
return hash_table_get (cookies_hash_table, key);
}
/* Find and return the cookie whose domain, path, and attribute name
correspond to COOKIE. If found, PREVPTR will point to the location
of the cookie previous in chain, or NULL if the found cookie is the
head of a chain.
If no matching cookie is found, return NULL. */ If no matching cookie is found, return NULL. */
static struct cookie * static struct cookie *
find_matching_cookie (struct cookie *cookie, struct cookie **prevptr) find_matching_cookie (struct cookie_jar *jar, struct cookie *cookie,
struct cookie **prevptr)
{ {
struct cookie *chain, *prev; struct cookie *chain, *prev;
if (!cookies_hash_table) chain = hash_table_get (jar->chains_by_domain, cookie->domain);
goto nomatch;
chain = find_cookie_chain_exact (cookie->domain, cookie->port);
if (!chain) if (!chain)
goto nomatch; goto nomatch;
prev = NULL; prev = NULL;
for (; chain; prev = chain, chain = chain->next) for (; chain; prev = chain, chain = chain->next)
if (!strcmp (cookie->path, chain->path) if (0 == strcmp (cookie->path, chain->path)
&& !strcmp (cookie->attr, chain->attr)) && 0 == strcmp (cookie->attr, chain->attr)
&& cookie->port == chain->port)
{ {
*prevptr = prev; *prevptr = prev;
return chain; return chain;
@ -169,7 +163,7 @@ find_matching_cookie (struct cookie *cookie, struct cookie **prevptr)
return NULL; return NULL;
} }
/* Store COOKIE to memory. /* Store COOKIE to the jar.
This is done by placing COOKIE at the head of its chain. However, This is done by placing COOKIE at the head of its chain. However,
if COOKIE matches a cookie already in memory, as determined by if COOKIE matches a cookie already in memory, as determined by
@ -179,29 +173,19 @@ find_matching_cookie (struct cookie *cookie, struct cookie **prevptr)
first time; next hash_table_put's reuse the same key. */ first time; next hash_table_put's reuse the same key. */
static void static void
store_cookie (struct cookie *cookie) store_cookie (struct cookie_jar *jar, struct cookie *cookie)
{ {
struct cookie *chain_head; struct cookie *chain_head;
char *hostport;
char *chain_key; char *chain_key;
if (!cookies_hash_table) if (hash_table_get_pair (jar->chains_by_domain, cookie->domain,
/* If the hash table is not initialized, do so now, because we'll
need to store things. */
cookies_hash_table = make_nocase_string_hash_table (0);
/* Initialize hash table key. */
SET_HOSTPORT (cookie->domain, cookie->port, hostport);
if (hash_table_get_pair (cookies_hash_table, hostport,
&chain_key, &chain_head)) &chain_key, &chain_head))
{ {
/* There already exists a chain of cookies with this exact /* A chain of cookies in this domain already exists. Check for
domain. We need to check for duplicates -- if an existing duplicates -- if an extant cookie exactly matches our domain,
cookie exactly matches our domain, path and name, we replace port, path, and name, replace it. */
it. */
struct cookie *prev; struct cookie *prev;
struct cookie *victim = find_matching_cookie (cookie, &prev); struct cookie *victim = find_matching_cookie (jar, cookie, &prev);
if (victim) if (victim)
{ {
@ -220,6 +204,7 @@ store_cookie (struct cookie *cookie)
cookie->next = victim->next; cookie->next = victim->next;
} }
delete_cookie (victim); delete_cookie (victim);
--jar->cookie_count;
DEBUGP (("Deleted old cookie (to be replaced.)\n")); DEBUGP (("Deleted old cookie (to be replaced.)\n"));
} }
else else
@ -232,36 +217,39 @@ store_cookie (struct cookie *cookie)
that, because it might get deallocated by the above code at that, because it might get deallocated by the above code at
some point later. */ some point later. */
cookie->next = NULL; cookie->next = NULL;
chain_key = xstrdup (hostport); chain_key = xstrdup (cookie->domain);
} }
hash_table_put (cookies_hash_table, chain_key, cookie); hash_table_put (jar->chains_by_domain, chain_key, cookie);
++jar->cookie_count;
DEBUGP (("\nStored cookie %s %d %s %s %d %s %s %s\n", DEBUGP (("\nStored cookie %s %d%s %s %s %d %s %s %s\n",
cookie->domain, cookie->port, cookie->path, cookie->domain, cookie->port,
cookie->port == PORT_ANY ? " (ANY)" : "",
cookie->path,
cookie->permanent ? "permanent" : "nonpermanent", cookie->permanent ? "permanent" : "nonpermanent",
cookie->secure, cookie->secure,
asctime (localtime ((time_t *)&cookie->expiry_time)), cookie->expiry_time
? asctime (localtime (&cookie->expiry_time)) : "<indefinitely>",
cookie->attr, cookie->value)); cookie->attr, cookie->value));
} }
/* Discard a cookie matching COOKIE's domain, path, and attribute /* Discard a cookie matching COOKIE's domain, port, path, and
name. This gets called when we encounter a cookie whose expiry attribute name. This gets called when we encounter a cookie whose
date is in the past, or whose max-age is set to 0. The former expiry date is in the past, or whose max-age is set to 0. The
corresponds to netscape cookie spec, while the latter is specified former corresponds to netscape cookie spec, while the latter is
by rfc2109. */ specified by rfc2109. */
static void static void
discard_matching_cookie (struct cookie *cookie) discard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie)
{ {
struct cookie *prev, *victim; struct cookie *prev, *victim;
if (!cookies_hash_table if (!hash_table_count (jar->chains_by_domain))
|| !hash_table_count (cookies_hash_table))
/* No elements == nothing to discard. */ /* No elements == nothing to discard. */
return; return;
victim = find_matching_cookie (cookie, &prev); victim = find_matching_cookie (jar, cookie, &prev);
if (victim) if (victim)
{ {
if (prev) if (prev)
@ -271,25 +259,21 @@ discard_matching_cookie (struct cookie *cookie)
{ {
/* VICTIM was head of its chain. We need to place a new /* VICTIM was head of its chain. We need to place a new
cookie at the head. */ cookie at the head. */
char *hostport;
char *chain_key = NULL; char *chain_key = NULL;
int res; int res;
SET_HOSTPORT (victim->domain, victim->port, hostport); res = hash_table_get_pair (jar->chains_by_domain, victim->domain,
res = hash_table_get_pair (cookies_hash_table, hostport,
&chain_key, NULL); &chain_key, NULL);
assert (res != 0); assert (res != 0);
if (!victim->next) if (!victim->next)
{ {
/* VICTIM was the only cookie in the chain. Destroy the /* VICTIM was the only cookie in the chain. Destroy the
chain and deallocate the chain key. */ chain and deallocate the chain key. */
hash_table_remove (jar->chains_by_domain, victim->domain);
hash_table_remove (cookies_hash_table, hostport);
xfree (chain_key); xfree (chain_key);
} }
else else
hash_table_put (cookies_hash_table, chain_key, victim->next); hash_table_put (jar->chains_by_domain, chain_key, victim->next);
} }
delete_cookie (victim); delete_cookie (victim);
DEBUGP (("Discarded old cookie.\n")); DEBUGP (("Discarded old cookie.\n"));
@ -365,12 +349,11 @@ update_cookie_field (struct cookie *cookie,
if (expires != -1) if (expires != -1)
{ {
cookie->permanent = 1; cookie->permanent = 1;
cookie->expiry_time = (unsigned long)expires; cookie->expiry_time = (time_t)expires;
} }
else else
/* Error in expiration spec. Assume default (cookie valid for /* Error in expiration spec. Assume default (cookie valid for
this session.) #### Should we return 0 and invalidate the this session.) */
cookie? */
; ;
/* According to netscape's specification, expiry time in the /* According to netscape's specification, expiry time in the
@ -392,10 +375,10 @@ update_cookie_field (struct cookie *cookie,
sscanf (value_copy, "%lf", &maxage); sscanf (value_copy, "%lf", &maxage);
if (maxage == -1) if (maxage == -1)
/* something is wrong. */ /* something went wrong. */
return 0; return 0;
cookie->permanent = 1; cookie->permanent = 1;
cookie->expiry_time = (unsigned long)cookies_now + (unsigned long)maxage; cookie->expiry_time = cookies_now + maxage;
/* According to rfc2109, a cookie with max-age of 0 means that /* According to rfc2109, a cookie with max-age of 0 means that
discarding of a matching cookie is requested. */ discarding of a matching cookie is requested. */
@ -678,7 +661,10 @@ static int
check_domain_match (const char *cookie_domain, const char *host) check_domain_match (const char *cookie_domain, const char *host)
{ {
static char *special_toplevel_domains[] = { static char *special_toplevel_domains[] = {
".com", ".edu", ".net", ".org", ".gov", ".mil", ".int" /* This is a total crock of shit, but we're living with it until
something better is devised. */
".com", ".edu", ".net", ".org", ".gov", ".mil", ".int",
".de", ".fr", ".hr"
}; };
int i, required_dots; int i, required_dots;
@ -748,62 +734,64 @@ check_path_match (const char *cookie_path, const char *path)
return path_matches (path, cookie_path); return path_matches (path, cookie_path);
} }
/* Parse the `Set-Cookie' header and, if the cookie is legal, store it /* Process the HTTP `Set-Cookie' header. This results in storing the
to memory. */ cookie or discarding a matching one, or ignoring it completely, all
depending on the contents. */
int void
set_cookie_header_cb (const char *hdr, void *closure) cookie_jar_process_set_cookie (struct cookie_jar *jar,
const char *host, int port,
const char *path, const char *set_cookie)
{ {
struct url *u = (struct url *)closure;
struct cookie *cookie; struct cookie *cookie;
cookies_now = time (NULL); cookies_now = time (NULL);
cookie = parse_set_cookies (hdr); cookie = parse_set_cookies (set_cookie);
if (!cookie) if (!cookie)
goto out; goto out;
/* Sanitize parts of cookie. */ /* Sanitize parts of cookie. */
if (!cookie->domain) if (!cookie->domain)
cookie->domain = xstrdup (u->host); {
copy_domain:
cookie->domain = xstrdup (host);
cookie->port = port;
}
else else
{ {
if (!check_domain_match (cookie->domain, u->host)) if (!check_domain_match (cookie->domain, host))
{ {
DEBUGP (("Attempt to fake the domain: %s, %s\n", logprintf (LOG_NOTQUIET,
cookie->domain, u->host)); "Cookie coming from %s attempted to set domain to %s\n",
goto out; host, cookie->domain);
goto copy_domain;
} }
} }
if (!cookie->path) if (!cookie->path)
cookie->path = xstrdup (u->path); cookie->path = xstrdup (path);
else else
{ {
if (!check_path_match (cookie->path, u->path)) if (!check_path_match (cookie->path, path))
{ {
DEBUGP (("Attempt to fake the path: %s, %s\n", DEBUGP (("Attempt to fake the path: %s, %s\n",
cookie->path, u->path)); cookie->path, path));
goto out; goto out;
} }
} }
cookie->port = u->port;
if (cookie->discard_requested) if (cookie->discard_requested)
{ {
discard_matching_cookie (cookie); discard_matching_cookie (jar, cookie);
delete_cookie (cookie); delete_cookie (cookie);
return 1;
} }
store_cookie (cookie); store_cookie (jar, cookie);
return 1; return;
out: out:
if (cookie) if (cookie)
delete_cookie (cookie); delete_cookie (cookie);
return 1;
} }
/* Support for sending out cookies in HTTP requests, based on /* Support for sending out cookies in HTTP requests, based on
@ -820,13 +808,13 @@ set_cookie_header_cb (const char *hdr, void *closure)
++st_count; \ ++st_count; \
} while (0) } while (0)
/* Store cookie chains that match HOST, PORT. Since more than one /* Store cookie chains that match HOST. Since more than one chain can
chain can match, the matches are written to STORE. No more than match, the matches are written to STORE. No more than SIZE matches
SIZE matches are written; if more matches are present, return the are written; if more matches are present, return the number of
number of chains that would have been written. */ chains that would have been written. */
static int static int
find_matching_chains (const char *host, int port, find_matching_chains (struct cookie_jar *jar, const char *host,
struct cookie *store[], int size) struct cookie *store[], int size)
{ {
struct cookie *chain; struct cookie *chain;
@ -834,13 +822,13 @@ find_matching_chains (const char *host, int port,
char *hash_key; char *hash_key;
int count = 0; int count = 0;
if (!cookies_hash_table) if (!hash_table_count (jar->chains_by_domain))
return 0; return 0;
SET_HOSTPORT (host, port, hash_key); STRDUP_ALLOCA (hash_key, host);
/* Exact match. */ /* Look for an exact match. */
chain = hash_table_get (cookies_hash_table, hash_key); chain = hash_table_get (jar->chains_by_domain, hash_key);
if (chain) if (chain)
STORE_CHAIN (chain, store, size, count); STORE_CHAIN (chain, store, size, count);
@ -855,7 +843,7 @@ find_matching_chains (const char *host, int port,
loop. */ loop. */
char *p = strchr (hash_key, '.'); char *p = strchr (hash_key, '.');
assert (p != NULL); assert (p != NULL);
chain = hash_table_get (cookies_hash_table, p); chain = hash_table_get (jar->chains_by_domain, p);
if (chain) if (chain)
STORE_CHAIN (chain, store, size, count); STORE_CHAIN (chain, store, size, count);
hash_key = p + 1; hash_key = p + 1;
@ -888,20 +876,33 @@ path_matches (const char *full_path, const char *prefix)
return len + 1; return len + 1;
} }
/* Return non-zero iff COOKIE matches the given PATH, PORT, and
security flag. HOST is not a flag because it is assumed that the
cookie comes from the correct chain.
If PATH_GOODNESS is non-NULL, store the "path goodness" there. The
said goodness is a measure of how well COOKIE matches PATH. It is
used for ordering cookies. */
static int static int
matching_cookie (const struct cookie *cookie, const char *path, matching_cookie (const struct cookie *cookie, const char *path, int port,
int connection_secure_p, int *path_goodness) int connection_secure_p, int *path_goodness)
{ {
int pg; int pg;
if (cookie->expiry_time < cookies_now) if (COOKIE_EXPIRED_P (cookie))
/* Ignore stale cookies. There is no need to unchain the cookie /* Ignore stale cookies. Don't bother unchaining the cookie at
at this point -- Wget is a relatively short-lived application, this point -- Wget is a relatively short-lived application, and
and stale cookies will not be saved by `save_cookies'. */ stale cookies will not be saved by `save_cookies'. On the
other hand, this function should be as efficient as
possible. */
return 0; return 0;
if (cookie->secure && !connection_secure_p) if (cookie->secure && !connection_secure_p)
/* Don't transmit secure cookies over an insecure connection. */ /* Don't transmit secure cookies over an insecure connection. */
return 0; return 0;
if (cookie->port != PORT_ANY && cookie->port != port)
return 0;
pg = path_matches (path, cookie->path); pg = path_matches (path, cookie->path);
if (!pg) if (!pg)
return 0; return 0;
@ -992,15 +993,16 @@ goodness_comparator (const void *p1, const void *p2)
return dgdiff ? dgdiff : pgdiff; return dgdiff ? dgdiff : pgdiff;
} }
/* Build a `Cookie' header for a request that goes to HOST:PORT and /* Generate a `Cookie' header for a request that goes to HOST:PORT and
requests PATH from the server. The resulting string is allocated requests PATH from the server. The resulting string is allocated
with `malloc', and the caller is responsible for freeing it. If no with `malloc', and the caller is responsible for freeing it. If no
cookies pertain to this request, i.e. no cookie header should be cookies pertain to this request, i.e. no cookie header should be
generated, NULL is returned. */ generated, NULL is returned. */
char * char *
build_cookies_request (const char *host, int port, const char *path, cookie_jar_generate_cookie_header (struct cookie_jar *jar, const char *host,
int connection_secure_p) int port, const char *path,
int connection_secure_p)
{ {
struct cookie *chain_default_store[20]; struct cookie *chain_default_store[20];
struct cookie **all_chains = chain_default_store; struct cookie **all_chains = chain_default_store;
@ -1014,7 +1016,7 @@ build_cookies_request (const char *host, int port, const char *path,
int result_size, pos; int result_size, pos;
again: again:
chain_count = find_matching_chains (host, port, all_chains, chain_store_size); chain_count = find_matching_chains (jar, host, all_chains, chain_store_size);
if (chain_count > chain_store_size) if (chain_count > chain_store_size)
{ {
/* It's extremely unlikely that more than 20 chains will ever /* It's extremely unlikely that more than 20 chains will ever
@ -1035,7 +1037,7 @@ build_cookies_request (const char *host, int port, const char *path,
count = 0; count = 0;
for (i = 0; i < chain_count; i++) for (i = 0; i < chain_count; i++)
for (cookie = all_chains[i]; cookie; cookie = cookie->next) for (cookie = all_chains[i]; cookie; cookie = cookie->next)
if (matching_cookie (cookie, path, connection_secure_p, NULL)) if (matching_cookie (cookie, path, port, connection_secure_p, NULL))
++count; ++count;
if (!count) if (!count)
/* No matching cookies. */ /* No matching cookies. */
@ -1051,7 +1053,7 @@ build_cookies_request (const char *host, int port, const char *path,
for (cookie = all_chains[i]; cookie; cookie = cookie->next) for (cookie = all_chains[i]; cookie; cookie = cookie->next)
{ {
int pg; int pg;
if (!matching_cookie (cookie, path, connection_secure_p, &pg)) if (!matching_cookie (cookie, path, port, connection_secure_p, &pg))
continue; continue;
outgoing[ocnt].cookie = cookie; outgoing[ocnt].cookie = cookie;
outgoing[ocnt].domain_goodness = strlen (cookie->domain); outgoing[ocnt].domain_goodness = strlen (cookie->domain);
@ -1185,7 +1187,7 @@ domain_port (const char *domain_b, const char *domain_e,
/* Load cookies from FILE. */ /* Load cookies from FILE. */
void void
load_cookies (const char *file) cookie_jar_load (struct cookie_jar *jar, const char *file)
{ {
char *line; char *line;
FILE *fp = fopen (file, "r"); FILE *fp = fopen (file, "r");
@ -1202,6 +1204,7 @@ load_cookies (const char *file)
struct cookie *cookie; struct cookie *cookie;
char *p = line; char *p = line;
double expiry;
int port; int port;
char *domain_b = NULL, *domain_e = NULL; char *domain_b = NULL, *domain_e = NULL;
@ -1252,24 +1255,25 @@ load_cookies (const char *file)
port = domain_port (domain_b, domain_e, (const char **)&domain_e); port = domain_port (domain_b, domain_e, (const char **)&domain_e);
if (port) if (port)
cookie->port = port; cookie->port = port;
else
cookie->port = cookie->secure ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
cookie->domain = strdupdelim (domain_b, domain_e); cookie->domain = strdupdelim (domain_b, domain_e);
/* safe default in case EXPIRES field is garbled. */ /* safe default in case EXPIRES field is garbled. */
cookie->expiry_time = cookies_now - 1; expiry = (double)cookies_now - 1;
/* I don't like changing the line, but it's completely safe. /* I don't like changing the line, but it's completely safe.
(line is malloced.) */ (line is malloced.) */
*expires_e = '\0'; *expires_e = '\0';
sscanf (expires_b, "%lu", &cookie->expiry_time); sscanf (expires_b, "%lf", &expiry);
if (cookie->expiry_time < cookies_now) if (expiry < cookies_now)
/* ignore stale cookie. */ /* ignore stale cookie. */
goto abort; goto abort;
cookie->expiry_time = expiry;
/* If the cookie has survived being saved into an external file,
it is obviously permanent. */
cookie->permanent = 1; cookie->permanent = 1;
store_cookie (cookie); store_cookie (jar, cookie);
next: next:
continue; continue;
@ -1294,12 +1298,15 @@ save_cookies_mapper (void *key, void *value, void *arg)
{ {
if (!chain->permanent) if (!chain->permanent)
continue; continue;
if (chain->expiry_time < cookies_now) if (COOKIE_EXPIRED_P (chain))
continue; continue;
fprintf (fp, "%s\t%s\t%s\t%s\t%lu\t%s\t%s\n", fputs (domain, fp);
domain, *domain == '.' ? "TRUE" : "FALSE", if (chain->port != PORT_ANY)
fprintf (fp, ":%d", chain->port);
fprintf (fp, "\t%s\t%s\t%s\t%.0f\t%s\t%s\n",
*domain == '.' ? "TRUE" : "FALSE",
chain->path, chain->secure ? "TRUE" : "FALSE", chain->path, chain->secure ? "TRUE" : "FALSE",
chain->expiry_time, (double)chain->expiry_time,
chain->attr, chain->value); chain->attr, chain->value);
if (ferror (fp)) if (ferror (fp))
return 1; /* stop mapping */ return 1; /* stop mapping */
@ -1310,15 +1317,10 @@ save_cookies_mapper (void *key, void *value, void *arg)
/* Save cookies, in format described above, to FILE. */ /* Save cookies, in format described above, to FILE. */
void void
save_cookies (const char *file) cookie_jar_save (struct cookie_jar *jar, const char *file)
{ {
FILE *fp; FILE *fp;
if (!cookies_hash_table
|| !hash_table_count (cookies_hash_table))
/* no cookies stored; nothing to do. */
return;
DEBUGP (("Saving cookies to %s.\n", file)); DEBUGP (("Saving cookies to %s.\n", file));
cookies_now = time (NULL); cookies_now = time (NULL);
@ -1335,7 +1337,7 @@ save_cookies (const char *file)
fprintf (fp, "# Generated by Wget on %s.\n", datetime_str (NULL)); fprintf (fp, "# Generated by Wget on %s.\n", datetime_str (NULL));
fputs ("# Edit at your own risk.\n\n", fp); fputs ("# Edit at your own risk.\n\n", fp);
hash_table_map (cookies_hash_table, save_cookies_mapper, fp); hash_table_map (jar->chains_by_domain, save_cookies_mapper, fp);
if (ferror (fp)) if (ferror (fp))
logprintf (LOG_NOTQUIET, _("Error writing to `%s': %s\n"), logprintf (LOG_NOTQUIET, _("Error writing to `%s': %s\n"),
@ -1348,14 +1350,20 @@ save_cookies (const char *file)
DEBUGP (("Done saving cookies.\n")); 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 static int
delete_cookie_chain_mapper (void *value, void *key, void *arg_ignored) nuke_cookie_chain (void *value, void *key, void *arg)
{ {
char *chain_key = (char *)value; char *chain_key = (char *)value;
struct cookie *chain = (struct cookie *)key; struct cookie *chain = (struct cookie *)key;
struct cookie_jar *jar = (struct cookie_jar *)arg;
/* Remove the chain from the table and free the key. */ /* Remove the chain from the table and free the key. */
hash_table_remove (cookies_hash_table, chain_key); hash_table_remove (jar->chains_by_domain, chain_key);
xfree (chain_key); xfree (chain_key);
/* Then delete all the cookies in the chain. */ /* Then delete all the cookies in the chain. */
@ -1373,11 +1381,9 @@ delete_cookie_chain_mapper (void *value, void *key, void *arg_ignored)
/* Clean up cookie-related data. */ /* Clean up cookie-related data. */
void void
cookies_cleanup (void) cookie_jar_delete (struct cookie_jar *jar)
{ {
if (!cookies_hash_table) hash_table_map (jar->chains_by_domain, nuke_cookie_chain, jar);
return; hash_table_destroy (jar->chains_by_domain);
hash_table_map (cookies_hash_table, delete_cookie_chain_mapper, NULL); xfree (jar);
hash_table_destroy (cookies_hash_table);
cookies_hash_table = NULL;
} }

View File

@ -1,5 +1,5 @@
/* Support for cookies. /* Support for cookies.
Copyright (C) 2001 Free Software Foundation, Inc. Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Wget. This file is part of GNU Wget.
@ -20,16 +20,18 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#ifndef COOKIES_H #ifndef COOKIES_H
#define COOKIES_H #define COOKIES_H
/* struct cookie is not exported; this file only exports functions for struct cookie_jar;
manipulating cookie contents. */
int set_cookie_header_cb PARAMS ((const char *, void *)); struct cookie_jar *cookie_jar_new PARAMS ((void));
void cookie_jar_delete PARAMS ((struct cookie_jar *));
char *build_cookies_request PARAMS ((const char *, int, const char *, int)); void cookie_jar_process_set_cookie PARAMS ((struct cookie_jar *, const char *,
int, const char *, const char *));
char *cookie_jar_generate_cookie_header PARAMS ((struct cookie_jar *,
const char *, int,
const char *, int));
void load_cookies PARAMS ((const char *)); void cookie_jar_load PARAMS ((struct cookie_jar *, const char *));
void save_cookies PARAMS ((const char *)); void cookie_jar_save PARAMS ((struct cookie_jar *, const char *));
void cookies_cleanup PARAMS ((void));
#endif /* COOKIES_H */ #endif /* COOKIES_H */

View File

@ -69,6 +69,7 @@ extern int errno;
#endif #endif
static int cookies_loaded_p; static int cookies_loaded_p;
struct cookie_jar *wget_cookie_jar;
#define TEXTHTML_S "text/html" #define TEXTHTML_S "text/html"
#define HTTP_ACCEPT "*/*" #define HTTP_ACCEPT "*/*"
@ -334,6 +335,22 @@ http_process_connection (const char *hdr, void *arg)
*flag = 1; *flag = 1;
return 1; return 1;
} }
/* Commit the cookie to the cookie jar. */
int
http_process_set_cookie (const char *hdr, void *arg)
{
struct url *u = (struct url *)arg;
/* The jar should have been created by now. */
assert (wget_cookie_jar != NULL);
cookie_jar_process_set_cookie (wget_cookie_jar, u->host, u->port, u->path,
hdr);
return 1;
}
/* Persistent connections. Currently, we cache the most recently used /* Persistent connections. Currently, we cache the most recently used
connection as persistent, provided that the HTTP server agrees to connection as persistent, provided that the HTTP server agrees to
@ -584,7 +601,7 @@ static char *basic_authentication_encode PARAMS ((const char *, const char *,
const char *)); const char *));
static int known_authentication_scheme_p PARAMS ((const char *)); static int known_authentication_scheme_p PARAMS ((const char *));
time_t http_atotm PARAMS ((char *)); time_t http_atotm PARAMS ((const char *));
#define BEGINS_WITH(line, string_constant) \ #define BEGINS_WITH(line, string_constant) \
(!strncasecmp (line, string_constant, sizeof (string_constant) - 1) \ (!strncasecmp (line, string_constant, sizeof (string_constant) - 1) \
@ -891,13 +908,14 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
request_keep_alive = NULL; request_keep_alive = NULL;
if (opt.cookies) if (opt.cookies)
cookies = build_cookies_request (u->host, u->port, u->path, cookies = cookie_jar_generate_cookie_header (wget_cookie_jar, u->host,
u->port, u->path,
#ifdef HAVE_SSL #ifdef HAVE_SSL
u->scheme == SCHEME_HTTPS u->scheme == SCHEME_HTTPS
#else #else
0 0
#endif #endif
); );
if (opt.post_data || opt.post_file_name) if (opt.post_data || opt.post_file_name)
{ {
@ -1168,7 +1186,7 @@ Accept: %s\r\n\
goto done_header; goto done_header;
/* Try getting cookies. */ /* Try getting cookies. */
if (opt.cookies) if (opt.cookies)
if (header_process (hdr, "Set-Cookie", set_cookie_header_cb, u)) if (header_process (hdr, "Set-Cookie", http_process_set_cookie, u))
goto done_header; goto done_header;
/* Try getting www-authentication. */ /* Try getting www-authentication. */
if (!authenticate_h) if (!authenticate_h)
@ -1558,10 +1576,15 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
/* This used to be done in main(), but it's a better idea to do it /* This used to be done in main(), but it's a better idea to do it
here so that we don't go through the hoops if we're just using here so that we don't go through the hoops if we're just using
FTP or whatever. */ FTP or whatever. */
if (opt.cookies && opt.cookies_input && !cookies_loaded_p) if (opt.cookies)
{ {
load_cookies (opt.cookies_input); if (!wget_cookie_jar)
cookies_loaded_p = 1; wget_cookie_jar = cookie_jar_new ();
if (opt.cookies_input && !cookies_loaded_p)
{
cookie_jar_load (wget_cookie_jar, opt.cookies_input);
cookies_loaded_p = 1;
}
} }
*newloc = NULL; *newloc = NULL;
@ -2155,7 +2178,7 @@ check_end (const char *p)
it is not assigned to the FSF. So I stuck it with strptime. */ it is not assigned to the FSF. So I stuck it with strptime. */
time_t time_t
http_atotm (char *time_string) http_atotm (const char *time_string)
{ {
/* NOTE: Solaris strptime man page claims that %n and %t match white /* NOTE: Solaris strptime man page claims that %n and %t match white
space, but that's not universally available. Instead, we simply space, but that's not universally available. Instead, we simply

View File

@ -53,13 +53,15 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "host.h" #include "host.h"
#include "recur.h" #include "recur.h"
#include "netrc.h" #include "netrc.h"
#include "cookies.h" /* for cookies_cleanup */ #include "cookies.h" /* for cookie_jar_delete */
#include "progress.h" #include "progress.h"
#ifndef errno #ifndef errno
extern int errno; extern int errno;
#endif #endif
extern struct cookie_jar *wget_cookie_jar;
/* We want tilde expansion enabled only when reading `.wgetrc' lines; /* We want tilde expansion enabled only when reading `.wgetrc' lines;
otherwise, it will be performed by the shell. This variable will otherwise, it will be performed by the shell. This variable will
be set by the wgetrc-reading function. */ be set by the wgetrc-reading function. */
@ -1057,8 +1059,8 @@ cleanup (void)
http_cleanup (); http_cleanup ();
cleanup_html_url (); cleanup_html_url ();
downloaded_files_free (); downloaded_files_free ();
cookies_cleanup ();
host_cleanup (); host_cleanup ();
cookie_jar_delete (wget_cookie_jar);
{ {
extern acc_t *netrc_list; extern acc_t *netrc_list;

View File

@ -70,6 +70,8 @@ extern int errno;
struct options opt; struct options opt;
extern struct cookie_jar *wget_cookie_jar;
/* From log.c. */ /* From log.c. */
void log_init PARAMS ((const char *, int)); void log_init PARAMS ((const char *, int));
void log_close PARAMS ((void)); void log_close PARAMS ((void));
@ -860,8 +862,8 @@ Can't timestamp and not clobber old files at the same time.\n"));
legible (opt.quota)); legible (opt.quota));
} }
if (opt.cookies_output) if (opt.cookies_output && wget_cookie_jar)
save_cookies (opt.cookies_output); cookie_jar_save (wget_cookie_jar, opt.cookies_output);
if (opt.convert_links && !opt.delete_after) if (opt.convert_links && !opt.delete_after)
{ {