📄 cookies.c
字号:
/* Support for cookies. Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.This file is part of GNU Wget.GNU Wget is free software; you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation; either version 3 of the License, or (atyour option) any later version.GNU Wget is distributed in the hope that it will be useful, butWITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNUGeneral Public License for more details.You should have received a copy of the GNU General Public Licensealong with Wget. If not, see <http://www.gnu.org/licenses/>.Additional permission under GNU GPL version 3 section 7If you modify this program, or any covered work, by linking orcombining it with the OpenSSL project's OpenSSL library (or amodified version of that library), containing parts covered by theterms of the OpenSSL or SSLeay licenses, the Free Software Foundationgrants you additional permission to convey the resulting work.Corresponding Source for a non-source form of such a combinationshall include the source code for the parts of OpenSSL used as wellas that of the covered work. *//* Written by Hrvoje Niksic. Parts are loosely inspired by the cookie patch submitted by Tomasz Wegrzanowski. This implements the client-side cookie support, as specified (loosely) by Netscape's "preliminary specification", currently available at: http://wp.netscape.com/newsref/std/cookie_spec.html rfc2109 is not supported because of its incompatibilities with the above widely-used specification. rfc2965 is entirely ignored, since popular client software doesn't implement it, and even the sites that do send Set-Cookie2 also emit Set-Cookie for compatibility. */#include <config.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <assert.h>#include <errno.h>#include <time.h>#include "wget.h"#include "utils.h"#include "hash.h"#include "cookies.h"#include "http.h" /* for http_atotm *//* Declarations of `struct cookie' and the most basic functions. *//* Cookie jar serves as cookie storage and a means of retrieving cookies efficiently. All cookies with the same domain are stored in a linked list called "chain". A cookie chain can be reached by looking up the domain in the cookie jar's chains_by_domain table. For example, to reach all the cookies under google.com, one must execute hash_table_get(jar->chains_by_domain, "google.com"). Of course, when sending a cookie to `www.google.com', one must search for cookies that belong to either `www.google.com' or `google.com' -- but the point is that the code doesn't need to go through *all* the cookies. */struct cookie_jar { /* Cookie chains indexed by domain. */ struct hash_table *chains; 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. */static time_t cookies_now;struct cookie_jar *cookie_jar_new (void){ struct cookie_jar *jar = xnew (struct cookie_jar); jar->chains = make_nocase_string_hash_table (0); jar->cookie_count = 0; return jar;}struct cookie { char *domain; /* domain of the cookie */ int port; /* port number */ char *path; /* path prefix of the cookie */ 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. */ unsigned domain_exact :1; /* whether DOMAIN must match as a whole. */ 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 */ struct cookie *next; /* used for chaining of cookies in the same domain. */};#define PORT_ANY (-1)/* Allocate and return a new, empty cookie structure. */static struct cookie *cookie_new (void){ struct cookie *cookie = xnew0 (struct cookie); /* 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 boolcookie_expired_p (const struct cookie *c){ return c->expiry_time != 0 && c->expiry_time < cookies_now;}/* Deallocate COOKIE and its components. */static voiddelete_cookie (struct cookie *cookie){ xfree_null (cookie->domain); xfree_null (cookie->path); xfree_null (cookie->attr); xfree_null (cookie->value); xfree (cookie);}/* Functions for storing cookies. All cookies can be reached beginning with jar->chains. The key in that table is the domain name, and the value is a linked list of all cookies from that domain. Every new cookie is placed on the head of the list. *//* Find and return a cookie in JAR whose domain, path, and attribute name correspond to COOKIE. If found, PREVPTR will point to the 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. */static struct cookie *find_matching_cookie (struct cookie_jar *jar, struct cookie *cookie, struct cookie **prevptr){ struct cookie *chain, *prev; chain = hash_table_get (jar->chains, cookie->domain); if (!chain) goto nomatch; prev = NULL; for (; chain; prev = chain, chain = chain->next) if (0 == strcmp (cookie->path, chain->path) && 0 == strcmp (cookie->attr, chain->attr) && cookie->port == chain->port) { *prevptr = prev; return chain; } nomatch: *prevptr = NULL; return NULL;}/* Store COOKIE to the jar. This is done by placing COOKIE at the head of its chain. However, if COOKIE matches a cookie already in memory, as determined by find_matching_cookie, the old cookie is unlinked and destroyed. The key of each chain's hash table entry is allocated only the first time; next hash_table_put's reuse the same key. */static voidstore_cookie (struct cookie_jar *jar, struct cookie *cookie){ struct cookie *chain_head; char *chain_key; if (hash_table_get_pair (jar->chains, cookie->domain, &chain_key, &chain_head)) { /* A chain of cookies in this domain already exists. Check for duplicates -- if an extant cookie exactly matches our domain, port, path, and name, replace it. */ struct cookie *prev; struct cookie *victim = find_matching_cookie (jar, cookie, &prev); if (victim) { /* Remove VICTIM from the chain. COOKIE will be placed at the head. */ if (prev) { prev->next = victim->next; cookie->next = chain_head; } else { /* prev is NULL; apparently VICTIM was at the head of the chain. This place will be taken by COOKIE, so all we need to do is: */ cookie->next = victim->next; } delete_cookie (victim); --jar->cookie_count; DEBUGP (("Deleted old cookie (to be replaced.)\n")); } else cookie->next = chain_head; } else { /* We are now creating the chain. Use a copy of cookie->domain as the key for the life-time of the chain. Using cookie->domain would be unsafe because the life-time of the chain may exceed the life-time of the cookie. (Cookies may be deleted from the chain by this very function.) */ cookie->next = NULL; chain_key = xstrdup (cookie->domain); } hash_table_put (jar->chains, chain_key, cookie); ++jar->cookie_count; 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 attribute name. This gets called when we encounter a cookie whose expiry date is in the past, or whose max-age is set to 0. The former corresponds to netscape cookie spec, while the latter is specified by rfc2109. */static voiddiscard_matching_cookie (struct cookie_jar *jar, struct cookie *cookie){ struct cookie *prev, *victim; if (!hash_table_count (jar->chains)) /* No elements == nothing to discard. */ return; victim = find_matching_cookie (jar, cookie, &prev); if (victim) { if (prev) /* Simply unchain the victim. */ prev->next = victim->next; else { /* VICTIM was head of its chain. We need to place a new cookie at the head. */ char *chain_key = NULL; int res; res = hash_table_get_pair (jar->chains, victim->domain, &chain_key, NULL); assert (res != 0); if (!victim->next) { /* VICTIM was the only cookie in the chain. Destroy the chain and deallocate the chain key. */ hash_table_remove (jar->chains, victim->domain); xfree (chain_key); } else hash_table_put (jar->chains, chain_key, victim->next); } delete_cookie (victim); DEBUGP (("Discarded old cookie.\n")); }}/* Functions for parsing the `Set-Cookie' header, and creating new cookies from the wire. */#define TOKEN_IS(token, string_literal) \ BOUNDED_EQUAL_NO_CASE (token.b, token.e, string_literal)#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: name1=value1; name2=value2; ... 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 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_cookie (const char *set_cookie, bool silent){ const char *ptr = set_cookie; struct cookie *cookie = cookie_new (); param_token name, value; 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); while (extract_param (&ptr, &name, &value, ';')) { if (TOKEN_IS (name, "domain")) { 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) { 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 /* 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 (*ptr) /* extract_param has encountered a syntax error */ goto error; /* 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 (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 violate your privacy. */#define REQUIRE_DIGITS(p) do { \ if (!ISDIGIT (*p)) \ return false; \ for (++p; ISDIGIT (*p); p++) \ ; \} while (0)#define REQUIRE_DOT(p) do { \ if (*p++ != '.') \ return false; \} while (0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -