1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-11-04 08:17:17 -05:00
elinks/src/cookies/cookies.c
2022-06-25 16:24:15 +02:00

933 lines
23 KiB
C

/* Internal cookies implementation */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h> /* OS/2 needs this after sys/types.h */
#include <time.h>
#include "elinks.h"
#if 0
#define DEBUG_COOKIES
#endif
#include "bfu/dialog.h"
#include "cookies/cookies.h"
#include "cookies/dialogs.h"
#include "cookies/path.h"
#include "cookies/parser.h"
#include "config/home.h"
#include "config/kbdbind.h"
#include "config/options.h"
#include "intl/libintl.h"
#include "main/module.h"
#include "main/object.h"
#include "main/select.h"
#include "protocol/date.h"
#include "protocol/header.h"
#include "protocol/protocol.h"
#include "protocol/uri.h"
#include "session/session.h"
#include "terminal/terminal.h"
#include "util/conv.h"
#ifdef DEBUG_COOKIES
#include "util/error.h"
#endif
#include "util/file.h"
#include "util/memory.h"
#include "util/secsave.h"
#include "util/string.h"
#include "util/time.h"
#define COOKIES_FILENAME "cookies"
static int cookies_nosave = 0;
static INIT_LIST_OF(struct cookie, cookies);
struct c_domain {
LIST_HEAD(struct c_domain);
char domain[1]; /* Must be at end of struct. */
};
/* List of domains for which there may be cookies. This supposedly
* speeds up @send_cookies for other domains. Each element is a
* struct c_domain. No other data structures have pointers to these
* objects. Currently the domains remain in the list until
* @done_cookies clears the whole list. */
static INIT_LIST_OF(struct c_domain, c_domains);
/* List of servers for which there are cookies. */
static INIT_LIST_OF(struct cookie_server, cookie_servers);
/* Only @set_cookies_dirty may make this nonzero. */
static int cookies_dirty = 0;
enum cookies_option {
COOKIES_TREE,
COOKIES_ACCEPT_POLICY,
COOKIES_MAX_AGE,
COOKIES_PARANOID_SECURITY,
COOKIES_SAVE,
COOKIES_RESAVE,
COOKIES_OPTIONS,
};
static union option_info cookies_options[] = {
INIT_OPT_TREE("", N_("Cookies"),
"cookies", OPT_ZERO,
N_("Cookies options.")),
INIT_OPT_INT("cookies", N_("Accept policy"),
"accept_policy", OPT_ZERO,
COOKIES_ACCEPT_NONE, COOKIES_ACCEPT_ALL, COOKIES_ACCEPT_ALL,
N_("Cookies accepting policy:\n"
"0 is accept no cookies\n"
"1 is ask for confirmation before accepting cookie\n"
"2 is accept all cookies")),
INIT_OPT_INT("cookies", N_("Maximum age"),
"max_age", OPT_ZERO, -1, 10000, -1,
N_("Cookie maximum age (in days):\n"
"-1 is use cookie's expiration date if any\n"
"0 is force expiration at the end of session, ignoring\n"
" cookie's expiration date\n"
"1+ is use cookie's expiration date, but limit age to the\n"
" given number of days")),
INIT_OPT_BOOL("cookies", N_("Paranoid security"),
"paranoid_security", OPT_ZERO, 0,
N_("When enabled, we'll require three dots in cookies domain "
"for all non-international domains (instead of just two "
"dots). Some countries have generic second level domains "
"(eg. .com.pl, .co.uk) and allowing sites to set cookies "
"for these generic domains could potentially be very bad. "
"Note, it is off by default as it breaks a lot of sites.")),
INIT_OPT_BOOL("cookies", N_("Saving"),
"save", OPT_ZERO, 1,
N_("Whether cookies should be loaded from and saved to "
"disk.")),
INIT_OPT_BOOL("cookies", N_("Resaving"),
"resave", OPT_ZERO, 1,
N_("Save cookies after each change in cookies list? "
"No effect when cookie saving (cookies.save) is off.")),
NULL_OPTION_INFO,
};
#define get_opt_cookies(which) cookies_options[(which)].option.value
#define get_cookies_accept_policy() get_opt_cookies(COOKIES_ACCEPT_POLICY).number
#define get_cookies_max_age() get_opt_cookies(COOKIES_MAX_AGE).number
#define get_cookies_paranoid_security() get_opt_cookies(COOKIES_PARANOID_SECURITY).number
#define get_cookies_save() get_opt_cookies(COOKIES_SAVE).number
#define get_cookies_resave() get_opt_cookies(COOKIES_RESAVE).number
struct cookie_server *
get_cookie_server(char *host, int hostlen)
{
struct cookie_server *sort_spot = NULL;
struct cookie_server *cs;
foreach (cs, cookie_servers) {
/* XXX: We must count with cases like "x.co" vs "x.co.uk"
* below! */
int cslen = strlen(cs->host);
int cmp = c_strncasecmp(cs->host, host, hostlen);
if (!sort_spot && (cmp > 0 || (cmp == 0 && cslen > hostlen))) {
/* This is the first @cs with name greater than @host,
* our dream sort spot! */
sort_spot = cs->prev;
}
if (cmp || cslen != hostlen)
continue;
object_lock(cs);
return cs;
}
cs = (struct cookie_server *)mem_calloc(1, sizeof(*cs) + hostlen);
if (!cs) return NULL;
memcpy(cs->host, host, hostlen);
object_nolock(cs, "cookie_server");
cs->box_item = add_listbox_folder(&cookie_browser, NULL, cs);
object_lock(cs);
if (!sort_spot) {
/* No sort spot found, therefore this sorts at the end. */
add_to_list_end(cookie_servers, cs);
del_from_list(cs->box_item);
add_to_list_end(cookie_browser.root.child, cs->box_item);
} else {
/* Sort spot found, sort after it. */
add_at_pos(sort_spot, cs);
if (sort_spot != (struct cookie_server *) &cookie_servers) {
del_from_list(cs->box_item);
add_at_pos(sort_spot->box_item, cs->box_item);
} /* else we are already at the top anyway. */
}
return cs;
}
static void
done_cookie_server(struct cookie_server *cs)
{
object_unlock(cs);
if (is_object_used(cs)) return;
if (cs->box_item) done_listbox_item(&cookie_browser, cs->box_item);
del_from_list(cs);
mem_free(cs);
}
void
done_cookie(struct cookie *c)
{
if (c->box_item) done_listbox_item(&cookie_browser, c->box_item);
if (c->server) done_cookie_server(c->server);
mem_free_if(c->name);
mem_free_if(c->value);
mem_free_if(c->path);
mem_free_if(c->domain);
mem_free(c);
}
/* The cookie @c can be either in @cookies or in @cookie_queries.
* Because changes in @cookie_queries should not affect the cookie
* file, this function does not set @cookies_dirty. Instead, the
* caller must do that if appropriate. */
void
delete_cookie(struct cookie *c)
{
del_from_list(c);
done_cookie(c);
}
/* Check whether cookie's domain matches server.
* It returns 1 if ok, 0 else. */
static int
is_domain_security_ok(char *domain, char *server, int server_len)
{
int i;
int domain_len;
int need_dots;
if (domain[0] == '.') domain++;
domain_len = strlen(domain);
/* Match domain and server.. */
/* XXX: Hmm, can't we use c_strlcasecmp() here? --pasky */
if (domain_len > server_len) return 0;
/* Ensure that the domain is atleast a substring of the server before
* continuing. */
if (c_strncasecmp(domain, server + server_len - domain_len, domain_len))
return 0;
/* Allow domains which are same as servers. --<rono@sentuny.com.au> */
/* Mozilla does it as well ;))) and I can't figure out any security
* risk. --pasky */
if (server_len == domain_len)
return 1;
/* Check whether the server is an IP address, and require an exact host
* match for the cookie, so any chance of IP address funkiness is
* eliminated (e.g. the alias 127.1 domain-matching 99.54.127.1). Idea
* from mozilla. (bug 562) */
if (is_ip_address(server, server_len))
return 0;
/* Also test if domain is secure en ugh.. */
need_dots = 1;
if (get_cookies_paranoid_security()) {
/* This is somehow controversial attempt (by the way violating
* RFC) to increase cookies security in national domains, done
* by Mikulas. As it breaks a lot of sites, I decided to make
* this optional and off by default. I also don't think this
* improves security considerably, as it's SITE'S fault and
* also no other browser probably does it. --pasky */
/* Mikulas' comment: Some countries have generic 2-nd level
* domains (like .com.pl, .co.uk ...) and it would be very bad
* if someone set cookies for these generic domains. Imagine
* for example that server http://brutalporn.com.pl sets cookie
* Set-Cookie: user_is=perverse_pig; domain=.com.pl -- then
* this cookie would be sent to all commercial servers in
* Poland. */
need_dots = 2;
if (domain_len > 0) {
int pos = end_with_known_tld(domain, domain_len);
if (pos >= 1 && domain[pos - 1] == '.')
need_dots = 1;
}
}
for (i = 0; domain[i]; i++)
if (domain[i] == '.' && !--need_dots)
break;
if (need_dots > 0) return 0;
return 1;
}
/* Allocate a struct cookie and initialize it with the specified
* values (rather than copies). Returns NULL on error. On success,
* the cookie is basically safe for @done_cookie or @accept_cookie,
* although you may also want to set the remaining members and check
* @get_cookies_accept_policy and @is_domain_security_ok.
*
* The char * arguments must be allocated with @mem_alloc or
* equivalent, because @done_cookie will @mem_free them. Likewise,
* the caller must already have locked @server. If @init_cookie
* fails, then it frees the strings itself, and unlocks @server.
*
* If any parameter is NULL, then @init_cookie fails and does not
* consider that a bug. This means callers can use e.g. @stracpy
* and let @init_cookie check whether the call ran out of memory. */
struct cookie *
init_cookie(char *name, char *value,
char *path, char *domain,
struct cookie_server *server)
{
struct cookie *cookie = (struct cookie *)mem_calloc(1, sizeof(*cookie));
if (!cookie || !name || !value || !path || !domain || !server) {
mem_free_if(cookie);
mem_free_if(name);
mem_free_if(value);
mem_free_if(path);
mem_free_if(domain);
done_cookie_server(server);
return NULL;
}
object_nolock(cookie, "cookie"); /* Debugging purpose. */
cookie->name = name;
cookie->value = value;
cookie->domain = domain;
cookie->path = path;
cookie->server = server; /* the caller already locked it for us */
return cookie;
}
void
set_cookie(struct uri *uri, char *str)
{
char *path, *domain;
struct cookie *cookie;
struct cookie_str cstr;
int max_age;
if (get_cookies_accept_policy() == COOKIES_ACCEPT_NONE)
return;
#ifdef DEBUG_COOKIES
DBG("set_cookie -> (%s) %s", struri(uri), str);
#endif
if (!parse_cookie_str(&cstr, str)) return;
switch (parse_header_param(str, "path", &path, 0)) {
char *path_end;
case HEADER_PARAM_FOUND:
if (!path[0])
add_to_strn(&path, "/");
if (path[0] != '/') {
add_to_strn(&path, "x");
memmove(path + 1, path, strlen(path) - 1);
path[0] = '/';
}
break;
case HEADER_PARAM_NOT_FOUND:
path = get_uri_string(uri, URI_PATH);
if (!path)
return;
path_end = strrchr(path, '/');
if (path_end)
path_end[0] = '\0';
break;
default: /* error */
return;
}
if (parse_header_param(str, "domain", &domain, 0) == HEADER_PARAM_NOT_FOUND)
domain = memacpy(uri->host, uri->hostlen);
if (domain && domain[0] == '.')
memmove(domain, domain + 1, strlen(domain));
cookie = init_cookie(memacpy(str, cstr.nam_end - str),
memacpy(cstr.val_start, cstr.val_end - cstr.val_start),
path,
domain,
get_cookie_server(uri->host, uri->hostlen));
if (!cookie) return;
/* @cookie now owns @path and @domain. */
#if 0
/* We don't actually set ->accept at the moment. But I have kept it
* since it will maybe help to fix bug 77 - Support for more
* finegrained control upon accepting of cookies. */
if (!cookie->server->accept) {
#ifdef DEBUG_COOKIES
DBG("Dropped.");
#endif
done_cookie(cookie);
return;
}
#endif
/* Set cookie expiration if needed.
* Cookie expires at end of session by default,
* set to 0 by calloc().
*
* max_age:
* -1 is use cookie's expiration date if any
* 0 is force expiration at the end of session,
* ignoring cookie's expiration date
* 1+ is use cookie's expiration date,
* but limit age to the given number of days.
*/
max_age = get_cookies_max_age();
if (max_age) {
char *date;
time_t expires;
switch (parse_header_param(str, "expires", &date, 0)) {
case HEADER_PARAM_FOUND:
expires = parse_date(&date, NULL, 0, 1); /* Convert date to seconds. */
mem_free(date);
if (expires) {
if (max_age > 0) {
time_t seconds = ((time_t) max_age)*24*3600;
time_t deadline = time(NULL) + seconds;
if (expires > deadline) /* Over-aged cookie ? */
expires = deadline;
}
cookie->expires = expires;
}
break;
case HEADER_PARAM_NOT_FOUND:
break;
default: /* error */
done_cookie(cookie);
return;
}
}
cookie->secure = (parse_header_param(str, "secure", NULL, 0)
== HEADER_PARAM_FOUND);
cookie->httponly = (parse_header_param(str, "httponly", NULL, 0)
== HEADER_PARAM_FOUND);
#ifdef DEBUG_COOKIES
{
DBG("Got cookie %s = %s from %s, domain %s, "
"expires at %"TIME_PRINT_FORMAT", secure %d, httponly %d", cookie->name,
cookie->value, cookie->server->host, cookie->domain,
(time_print_T) cookie->expires, cookie->secure, cookie->httponly);
}
#endif
if (!is_domain_security_ok(cookie->domain, uri->host, uri->hostlen)) {
#ifdef DEBUG_COOKIES
DBG("Domain security violated: %s vs %.*s", cookie->domain,
uri->hostlen, uri->host);
#endif
mem_free(cookie->domain);
cookie->domain = memacpy(uri->host, uri->hostlen);
}
/* We have already check COOKIES_ACCEPT_NONE */
if (get_cookies_accept_policy() == COOKIES_ACCEPT_ASK) {
add_to_list(cookie_queries, cookie);
add_questions_entry(accept_cookie_dialog, cookie);
return;
}
accept_cookie(cookie);
}
void
accept_cookie(struct cookie *cookie)
{
struct c_domain *cd;
struct listbox_item *root = cookie->server->box_item;
int domain_len;
if (root)
cookie->box_item = add_listbox_leaf(&cookie_browser, root, cookie);
/* Do not weed out duplicates when loading the cookie file. It doesn't
* scale at all, being O(N^2) and taking about 2s with my 500 cookies
* (so if you don't notice that 100ms with your 100 cookies, that's
* not an argument). --pasky */
if (!cookies_nosave) {
struct cookie *c, *next;
foreachsafe (c, next, cookies) {
if (c_strcasecmp(c->name, cookie->name)
|| c_strcasecmp(c->domain, cookie->domain))
continue;
delete_cookie(c);
/* @set_cookies_dirty will be called below. */
}
}
add_to_list(cookies, cookie);
set_cookies_dirty();
/* XXX: This crunches CPU too. --pasky */
foreach (cd, c_domains)
if (!c_strcasecmp(cd->domain, cookie->domain))
return;
domain_len = strlen(cookie->domain);
/* One byte is reserved for domain in struct c_domain. */
cd = (struct c_domain *)mem_alloc(sizeof(*cd) + domain_len);
if (!cd) return;
memcpy(cd->domain, cookie->domain, domain_len + 1);
add_to_list(c_domains, cd);
}
#if 0
static unsigned int cookie_id = 0;
static void
delete_cookie(struct cookie *c)
{
struct c_domain *cd;
struct cookie *d;
foreach (d, cookies)
if (!c_strcasecmp(d->domain, c->domain))
goto end;
foreach (cd, c_domains) {
if (!c_strcasecmp(cd->domain, c->domain)) {
del_from_list(cd);
mem_free(cd);
break;
}
}
end:
del_from_list(c);
done_cookie(c);
}
static struct
cookie *find_cookie_id(void *idp)
{
int id = (int) idp;
struct cookie *c;
foreach (c, cookies)
if (c->id == id)
return c;
return NULL;
}
static void
reject_cookie(void *idp)
{
struct cookie *c = find_cookie_id(idp);
if (!c) return;
delete_cookie(c);
set_cookies_dirty(); /* @find_cookie_id doesn't use @cookie_queries */
}
static void
cookie_default(void *idp, int a)
{
struct cookie *c = find_cookie_id(idp);
if (c) c->server->accept = a;
}
static void
accept_cookie_always(void *idp)
{
cookie_default(idp, 1);
}
static void
accept_cookie_never(void *idp)
{
cookie_default(idp, 0);
reject_cookie(idp);
}
#endif
static struct string *
send_cookies_common(struct uri *uri, unsigned int httponly)
{
struct c_domain *cd;
struct cookie *c, *next;
char *path = NULL;
static struct string header;
time_t now;
if (!uri->host || !uri->data)
return NULL;
foreach (cd, c_domains)
if (is_in_domain(cd->domain, uri->host, uri->hostlen)) {
path = get_uri_string(uri, URI_PATH);
break;
}
if (!path) return NULL;
if (!init_string(&header)) {
mem_free(path);
return NULL;
}
now = time(NULL);
foreachsafe (c, next, cookies) {
if (!is_in_domain(c->domain, uri->host, uri->hostlen)
|| !is_path_prefix(c->path, path))
continue;
if (c->expires && c->expires <= now) {
#ifdef DEBUG_COOKIES
DBG("Cookie %s=%s (exp %"TIME_PRINT_FORMAT") expired.",
c->name, c->value, (time_print_T) c->expires);
#endif
delete_cookie(c);
set_cookies_dirty();
continue;
}
/* Not sure if this is 100% right..? --pasky */
if (c->secure && uri->protocol != PROTOCOL_HTTPS)
continue;
if (c->httponly && httponly)
continue;
if (header.length)
add_to_string(&header, "; ");
add_to_string(&header, c->name);
add_char_to_string(&header, '=');
add_to_string(&header, c->value);
#ifdef DEBUG_COOKIES
DBG("Cookie: %s=%s", c->name, c->value);
#endif
}
mem_free(path);
if (!header.length) {
done_string(&header);
return NULL;
}
return &header;
}
struct string *
send_cookies(struct uri *uri)
{
return send_cookies_common(uri, 0);
}
struct string *
send_cookies_js(struct uri *uri)
{
return send_cookies_common(uri, 1);
}
static void done_cookies(struct module *module);
void
load_cookies(void) {
/* Buffer size is set to be enough to read long lines that
* save_cookies may write. 6 is choosen after the fprintf(..) call
* in save_cookies(). --Zas */
char in_buffer[6 * MAX_STR_LEN];
const char *cookfile_orig = COOKIES_FILENAME;
char *cookfile = NULL;
FILE *fp;
time_t now;
if (elinks_home) {
cookfile = straconcat(elinks_home, cookfile_orig,
(char *) NULL);
if (!cookfile) return;
}
/* Do it here, as we will delete whole cookies list if the file was
* removed */
cookies_nosave = 1;
done_cookies(&cookies_module);
cookies_nosave = 0;
if (elinks_home) {
fp = fopen(cookfile, "rb");
mem_free(cookfile);
} else {
fp = fopen(cookfile_orig, "rb");
}
if (!fp) return;
/* XXX: We don't want to overwrite the cookies file
* periodically to our death. */
cookies_nosave = 1;
now = time(NULL);
while (fgets(in_buffer, 6 * MAX_STR_LEN, fp)) {
struct cookie *cookie;
char *p, *q = in_buffer;
enum { NAME = 0, VALUE, SERVER, PATH, DOMAIN, EXPIRES, SECURE, HTTPONLY, MEMBERS };
int member;
struct {
char *pos;
int len;
} members[MEMBERS];
time_t expires;
/* First find all members. */
for (member = NAME; member < MEMBERS; member++, q = ++p) {
p = strchr(q, '\t');
if (!p) {
if (member + 1 != MEMBERS) break; /* last field ? */
p = strchr(q, '\n');
if (!p) break;
}
members[member].pos = q;
members[member].len = p - q;
}
if ((member != HTTPONLY) && (member != MEMBERS)) continue; /* Invalid line. */
/* Skip expired cookies if any. */
expires = str_to_time_t(members[EXPIRES].pos);
if (!expires || expires <= now) {
set_cookies_dirty();
continue;
}
/* Prepare cookie if all members and fields was read. */
cookie = (struct cookie *)mem_calloc(1, sizeof(*cookie));
if (!cookie) continue;
cookie->server = get_cookie_server(members[SERVER].pos, members[SERVER].len);
cookie->name = memacpy(members[NAME].pos, members[NAME].len);
cookie->value = memacpy(members[VALUE].pos, members[VALUE].len);
cookie->path = memacpy(members[PATH].pos, members[PATH].len);
cookie->domain = memacpy(members[DOMAIN].pos, members[DOMAIN].len);
/* Check whether all fields were correctly allocated. */
if (!cookie->server || !cookie->name || !cookie->value
|| !cookie->path || !cookie->domain) {
done_cookie(cookie);
continue;
}
cookie->expires = expires;
cookie->secure = !!atoi(members[SECURE].pos);
cookie->httponly = (member == MEMBERS) && !!atoi(members[HTTPONLY].pos);
accept_cookie(cookie);
}
cookies_nosave = 0;
fclose(fp);
}
static void
resave_cookies_bottom_half(void *always_null)
{
if (get_cookies_save() && get_cookies_resave())
save_cookies(NULL); /* checks cookies_dirty */
}
/* Note that the cookies have been modified, and register a bottom
* half for saving them if appropriate. We use a bottom half so that
* if something makes multiple changes and calls this for each change,
* the cookies get saved only once at the end. */
void
set_cookies_dirty(void)
{
/* Do not check @cookies_dirty here. If the previous attempt
* to save cookies failed, @cookies_dirty can still be nonzero
* even though @resave_cookies_bottom_half is no longer in the
* queue. */
cookies_dirty = 1;
/* If @resave_cookies_bottom_half is already in the queue,
* @register_bottom_half does nothing. */
register_bottom_half(resave_cookies_bottom_half, NULL);
}
/* @term is non-NULL if the user told ELinks to save cookies, or NULL
* if ELinks decided that on its own. In the former case, this
* function reports errors to @term, unless CONFIG_SMALL is defined.
* In the latter case, this function does not save the cookies if it
* thinks the file is already up to date. */
void
save_cookies(struct terminal *term) {
struct cookie *c;
char *cookfile;
struct secure_save_info *ssi;
time_t now;
#ifdef CONFIG_SMALL
# define CANNOT_SAVE_COOKIES(flags, message)
#else
# define CANNOT_SAVE_COOKIES(flags, message) \
do { \
if (term) \
info_box(term, flags, N_("Cannot save cookies"),\
ALIGN_LEFT, message); \
} while (0)
#endif
if (cookies_nosave) {
assert(term == NULL);
if_assert_failed {}
return;
}
if (!elinks_home) {
CANNOT_SAVE_COOKIES(0, N_("ELinks was started without a home directory."));
return;
}
if (!cookies_dirty && !term)
return;
if (get_cmd_opt_bool("anonymous")) {
CANNOT_SAVE_COOKIES(0, N_("ELinks was started with the -anonymous option."));
return;
}
cookfile = straconcat(elinks_home, COOKIES_FILENAME,
(char *) NULL);
if (!cookfile) {
CANNOT_SAVE_COOKIES(0, N_("Out of memory"));
return;
}
ssi = secure_open(cookfile);
mem_free(cookfile);
if (!ssi) {
CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
secsave_strerror(secsave_errno, term));
return;
}
now = time(NULL);
foreach (c, cookies) {
if (!c->expires || c->expires <= now) continue;
if (secure_fprintf(ssi, "%s\t%s\t%s\t%s\t%s\t%" TIME_PRINT_FORMAT "\t%d\t%d\n",
c->name, c->value,
c->server->host,
empty_string_or_(c->path),
empty_string_or_(c->domain),
(time_print_T) c->expires, c->secure, c->httponly) < 0)
break;
}
secsave_errno = SS_ERR_OTHER; /* @secure_close doesn't always set it */
if (!secure_close(ssi)) cookies_dirty = 0;
else {
CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
secsave_strerror(secsave_errno, term));
}
#undef CANNOT_SAVE_COOKIES
}
static void
init_cookies(struct module *module)
{
if (get_cookies_save())
load_cookies();
}
/* Like @delete_cookie, this function does not set @cookies_dirty.
* The caller must do that if appropriate. */
static void
free_cookies_list(LIST_OF(struct cookie) *list)
{
while (!list_empty(*list)) {
struct cookie *cookie = (struct cookie *)list->next;
delete_cookie(cookie);
}
}
static void
done_cookies(struct module *module)
{
free_list(c_domains);
if (!cookies_nosave && get_cookies_save())
save_cookies(NULL);
free_cookies_list(&cookies);
free_cookies_list(&cookie_queries);
/* If @save_cookies failed above, @cookies_dirty can still be
* nonzero. Now if @resave_cookies_bottom_half were in the
* queue, it could save the empty @cookies list to the file.
* Prevent that. */
cookies_dirty = 0;
}
struct module cookies_module = struct_module(
/* name: */ N_("Cookies"),
/* options: */ cookies_options,
/* events: */ NULL,
/* submodules: */ NULL,
/* data: */ NULL,
/* init: */ init_cookies,
/* done: */ done_cookies
);