📄 cookies.c
字号:
/* Support for cookies. Copyright (C) 2001, 2002 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 2 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, write to the Free SoftwareFoundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.In addition, as a special exception, the Free Software Foundationgives permission to link the code of its release of Wget with theOpenSSL project's "OpenSSL" library (or with modified versions of itthat use the same license as the "OpenSSL" library), and distributethe linked executables. You must obey the GNU General Public Licensein all respects for all of the code used other than "OpenSSL". If youmodify this file, you may extend this exception to your version of thefile, but you are not obligated to do so. If you do not wish to doso, delete this exception statement from your version. *//* 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>#ifdef HAVE_STRING_H# include <string.h>#else# include <strings.h>#endif#include <stdlib.h>#include <assert.h>#include <errno.h>#include "wget.h"#include "utils.h"#include "hash.h"#include "cookies.h"/* This should *really* be in a .h file! */time_t http_atotm PARAMS ((const char *));/* 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 */ int secure; /* whether cookie should be transmitted over non-https connections. */ int domain_exact; /* 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, 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 */ 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 intcookie_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;#ifdef ENABLE_DEBUG if (opt.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)); }#endif}/* 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 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 intupdate_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.) */ ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -