Bug 1004: NEWS and AUTHORS
[elinks/kon.git] / src / cookies / cookies.c
blob303567d9a81bb83bc4c3ad66ad8163eba8ad58c7
1 /* Internal cookies implementation */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/types.h>
11 #include <sys/stat.h> /* OS/2 needs this after sys/types.h */
12 #ifdef HAVE_TIME_H
13 #include <time.h>
14 #endif
16 #include "elinks.h"
18 #if 0
19 #define DEBUG_COOKIES
20 #endif
22 #include "bfu/dialog.h"
23 #include "cookies/cookies.h"
24 #include "cookies/dialogs.h"
25 #include "cookies/parser.h"
26 #include "config/home.h"
27 #include "config/kbdbind.h"
28 #include "config/options.h"
29 #include "intl/gettext/libintl.h"
30 #include "main/module.h"
31 #include "main/object.h"
32 #include "main/select.h"
33 #include "protocol/date.h"
34 #include "protocol/header.h"
35 #include "protocol/protocol.h"
36 #include "protocol/uri.h"
37 #include "session/session.h"
38 #include "terminal/terminal.h"
39 #include "util/conv.h"
40 #ifdef DEBUG_COOKIES
41 #include "util/error.h"
42 #endif
43 #include "util/file.h"
44 #include "util/memory.h"
45 #include "util/secsave.h"
46 #include "util/string.h"
47 #include "util/time.h"
49 #define COOKIES_FILENAME "cookies"
52 static int cookies_nosave = 0;
54 static INIT_LIST_OF(struct cookie, cookies);
56 struct c_domain {
57 LIST_HEAD(struct c_domain);
59 unsigned char domain[1]; /* Must be at end of struct. */
62 /* List of domains for which there may be cookies. This supposedly
63 * speeds up @send_cookies for other domains. Each element is a
64 * struct c_domain. No other data structures have pointers to these
65 * objects. Currently the domains remain in the list until
66 * @done_cookies clears the whole list. */
67 static INIT_LIST_OF(struct c_domain, c_domains);
69 /* List of servers for which there are cookies. */
70 static INIT_LIST_OF(struct cookie_server, cookie_servers);
72 /* Only @set_cookies_dirty may make this nonzero. */
73 static int cookies_dirty = 0;
75 enum cookies_option {
76 COOKIES_TREE,
78 COOKIES_ACCEPT_POLICY,
79 COOKIES_MAX_AGE,
80 COOKIES_PARANOID_SECURITY,
81 COOKIES_SAVE,
82 COOKIES_RESAVE,
84 COOKIES_OPTIONS,
87 static struct option_info cookies_options[] = {
88 INIT_OPT_TREE("", N_("Cookies"),
89 "cookies", 0,
90 N_("Cookies options.")),
92 INIT_OPT_INT("cookies", N_("Accept policy"),
93 "accept_policy", 0,
94 COOKIES_ACCEPT_NONE, COOKIES_ACCEPT_ALL, COOKIES_ACCEPT_ALL,
95 N_("Cookies accepting policy:\n"
96 "0 is accept no cookies\n"
97 "1 is ask for confirmation before accepting cookie\n"
98 "2 is accept all cookies")),
100 INIT_OPT_INT("cookies", N_("Maximum age"),
101 "max_age", 0, -1, 10000, -1,
102 N_("Cookie maximum age (in days):\n"
103 "-1 is use cookie's expiration date if any\n"
104 "0 is force expiration at the end of session, ignoring cookie's\n"
105 " expiration date\n"
106 "1+ is use cookie's expiration date, but limit age to the given\n"
107 " number of days")),
109 INIT_OPT_BOOL("cookies", N_("Paranoid security"),
110 "paranoid_security", 0, 0,
111 N_("When enabled, we'll require three dots in cookies domain for all\n"
112 "non-international domains (instead of just two dots). Some countries\n"
113 "have generic second level domains (eg. .com.pl, .co.uk) and allowing\n"
114 "sites to set cookies for these generic domains could potentially be\n"
115 "very bad. Note, it is off by default as it breaks a lot of sites.")),
117 INIT_OPT_BOOL("cookies", N_("Saving"),
118 "save", 0, 1,
119 N_("Whether cookies should be loaded from and saved to disk.")),
121 INIT_OPT_BOOL("cookies", N_("Resaving"),
122 "resave", 0, 1,
123 N_("Save cookies after each change in cookies list? No effect when\n"
124 "cookie saving (cookies.save) is off.")),
126 NULL_OPTION_INFO,
129 #define get_opt_cookies(which) cookies_options[(which)].option.value
130 #define get_cookies_accept_policy() get_opt_cookies(COOKIES_ACCEPT_POLICY).number
131 #define get_cookies_max_age() get_opt_cookies(COOKIES_MAX_AGE).number
132 #define get_cookies_paranoid_security() get_opt_cookies(COOKIES_PARANOID_SECURITY).number
133 #define get_cookies_save() get_opt_cookies(COOKIES_SAVE).number
134 #define get_cookies_resave() get_opt_cookies(COOKIES_RESAVE).number
136 struct cookie_server *
137 get_cookie_server(unsigned char *host, int hostlen)
139 struct cookie_server *sort_spot = NULL;
140 struct cookie_server *cs;
142 foreach (cs, cookie_servers) {
143 /* XXX: We must count with cases like "x.co" vs "x.co.uk"
144 * below! */
145 int cslen = strlen(cs->host);
146 int cmp = c_strncasecmp(cs->host, host, hostlen);
148 if (!sort_spot && (cmp > 0 || (cmp == 0 && cslen > hostlen))) {
149 /* This is the first @cs with name greater than @host,
150 * our dream sort spot! */
151 sort_spot = cs->prev;
154 if (cmp || cslen != hostlen)
155 continue;
157 object_lock(cs);
158 return cs;
161 cs = mem_calloc(1, sizeof(*cs) + hostlen);
162 if (!cs) return NULL;
164 memcpy(cs->host, host, hostlen);
165 object_nolock(cs, "cookie_server");
167 cs->box_item = add_listbox_folder(&cookie_browser, NULL, cs);
169 object_lock(cs);
171 if (!sort_spot) {
172 /* No sort spot found, therefore this sorts at the end. */
173 add_to_list_end(cookie_servers, cs);
174 del_from_list(cs->box_item);
175 add_to_list_end(cookie_browser.root.child, cs->box_item);
176 } else {
177 /* Sort spot found, sort after it. */
178 add_at_pos(sort_spot, cs);
179 if (sort_spot != (struct cookie_server *) &cookie_servers) {
180 del_from_list(cs->box_item);
181 add_at_pos(sort_spot->box_item, cs->box_item);
182 } /* else we are already at the top anyway. */
185 return cs;
188 static void
189 done_cookie_server(struct cookie_server *cs)
191 object_unlock(cs);
192 if (is_object_used(cs)) return;
194 if (cs->box_item) done_listbox_item(&cookie_browser, cs->box_item);
195 del_from_list(cs);
196 mem_free(cs);
199 void
200 done_cookie(struct cookie *c)
202 if (c->box_item) done_listbox_item(&cookie_browser, c->box_item);
203 if (c->server) done_cookie_server(c->server);
204 mem_free_if(c->name);
205 mem_free_if(c->value);
206 mem_free_if(c->path);
207 mem_free_if(c->domain);
208 mem_free(c);
211 /* The cookie @c can be either in @cookies or in @cookie_queries.
212 * Because changes in @cookie_queries should not affect the cookie
213 * file, this function does not set @cookies_dirty. Instead, the
214 * caller must do that if appropriate. */
215 void
216 delete_cookie(struct cookie *c)
218 del_from_list(c);
219 done_cookie(c);
223 /* Check whether cookie's domain matches server.
224 * It returns 1 if ok, 0 else. */
225 static int
226 is_domain_security_ok(unsigned char *domain, unsigned char *server, int server_len)
228 int i;
229 int domain_len;
230 int need_dots;
232 if (domain[0] == '.') domain++;
233 domain_len = strlen(domain);
235 /* Match domain and server.. */
237 /* XXX: Hmm, can't we use c_strlcasecmp() here? --pasky */
239 if (domain_len > server_len) return 0;
241 /* Ensure that the domain is atleast a substring of the server before
242 * continuing. */
243 if (c_strncasecmp(domain, server + server_len - domain_len, domain_len))
244 return 0;
246 /* Allow domains which are same as servers. --<rono@sentuny.com.au> */
247 /* Mozilla does it as well ;))) and I can't figure out any security
248 * risk. --pasky */
249 if (server_len == domain_len)
250 return 1;
252 /* Check whether the server is an IP address, and require an exact host
253 * match for the cookie, so any chance of IP address funkiness is
254 * eliminated (e.g. the alias 127.1 domain-matching 99.54.127.1). Idea
255 * from mozilla. (bug 562) */
256 if (is_ip_address(server, server_len))
257 return 0;
259 /* Also test if domain is secure en ugh.. */
261 need_dots = 1;
263 if (get_cookies_paranoid_security()) {
264 /* This is somehow controversial attempt (by the way violating
265 * RFC) to increase cookies security in national domains, done
266 * by Mikulas. As it breaks a lot of sites, I decided to make
267 * this optional and off by default. I also don't think this
268 * improves security considerably, as it's SITE'S fault and
269 * also no other browser probably does it. --pasky */
270 /* Mikulas' comment: Some countries have generic 2-nd level
271 * domains (like .com.pl, .co.uk ...) and it would be very bad
272 * if someone set cookies for these generic domains. Imagine
273 * for example that server http://brutalporn.com.pl sets cookie
274 * Set-Cookie: user_is=perverse_pig; domain=.com.pl -- then
275 * this cookie would be sent to all commercial servers in
276 * Poland. */
277 need_dots = 2;
279 if (domain_len > 0) {
280 int pos = end_with_known_tld(domain, domain_len);
282 if (pos >= 1 && domain[pos - 1] == '.')
283 need_dots = 1;
287 for (i = 0; domain[i]; i++)
288 if (domain[i] == '.' && !--need_dots)
289 break;
291 if (need_dots > 0) return 0;
292 return 1;
295 /* Allocate a struct cookie and initialize it with the specified
296 * values (rather than copies). Returns NULL on error. On success,
297 * the cookie is basically safe for @done_cookie or @accept_cookie,
298 * although you may also want to set the remaining members and check
299 * @get_cookies_accept_policy and @is_domain_security_ok.
301 * The unsigned char * arguments must be allocated with @mem_alloc or
302 * equivalent, because @done_cookie will @mem_free them. Likewise,
303 * the caller must already have locked @server. If @init_cookie
304 * fails, then it frees the strings itself, and unlocks @server.
306 * If any parameter is NULL, then @init_cookie fails and does not
307 * consider that a bug. This means callers can use e.g. @stracpy
308 * and let @init_cookie check whether the call ran out of memory. */
309 struct cookie *
310 init_cookie(unsigned char *name, unsigned char *value,
311 unsigned char *path, unsigned char *domain,
312 struct cookie_server *server)
314 struct cookie *cookie = mem_calloc(1, sizeof(*cookie));
315 if (!cookie || !name || !value || !path || !domain || !server) {
316 mem_free_if(cookie);
317 mem_free_if(name);
318 mem_free_if(value);
319 mem_free_if(path);
320 mem_free_if(domain);
321 done_cookie_server(server);
322 return NULL;
324 object_nolock(cookie, "cookie"); /* Debugging purpose. */
326 cookie->name = name;
327 cookie->value = value;
328 cookie->domain = domain;
329 cookie->path = path;
330 cookie->server = server; /* the caller already locked it for us */
332 return cookie;
335 void
336 set_cookie(struct uri *uri, unsigned char *str)
338 unsigned char *path, *domain;
339 struct cookie *cookie;
340 struct cookie_str cstr;
341 int max_age;
343 if (get_cookies_accept_policy() == COOKIES_ACCEPT_NONE)
344 return;
346 #ifdef DEBUG_COOKIES
347 DBG("set_cookie -> (%s) %s", struri(uri), str);
348 #endif
350 if (!parse_cookie_str(&cstr, str)) return;
352 switch (parse_header_param(str, "path", &path)) {
353 unsigned char *path_end;
355 case HEADER_PARAM_FOUND:
356 if (!path[0]
357 || path[strlen(path) - 1] != '/')
358 add_to_strn(&path, "/");
360 if (path[0] != '/') {
361 add_to_strn(&path, "x");
362 memmove(path + 1, path, strlen(path) - 1);
363 path[0] = '/';
365 break;
367 case HEADER_PARAM_NOT_FOUND:
368 path = get_uri_string(uri, URI_PATH);
369 if (!path)
370 return;
372 path_end = strrchr(path, '/');
373 if (path_end)
374 path_end[1] = '\0';
375 break;
377 default: /* error */
378 return;
381 if (parse_header_param(str, "domain", &domain) == HEADER_PARAM_NOT_FOUND)
382 domain = memacpy(uri->host, uri->hostlen);
383 if (domain && domain[0] == '.')
384 memmove(domain, domain + 1, strlen(domain));
386 cookie = init_cookie(memacpy(str, cstr.nam_end - str),
387 memacpy(cstr.val_start, cstr.val_end - cstr.val_start),
388 path,
389 domain,
390 get_cookie_server(uri->host, uri->hostlen));
391 if (!cookie) return;
392 /* @cookie now owns @path and @domain. */
394 #if 0
395 /* We don't actually set ->accept at the moment. But I have kept it
396 * since it will maybe help to fix bug 77 - Support for more
397 * finegrained control upon accepting of cookies. */
398 if (!cookie->server->accept) {
399 #ifdef DEBUG_COOKIES
400 DBG("Dropped.");
401 #endif
402 done_cookie(cookie);
403 return;
405 #endif
407 /* Set cookie expiration if needed.
408 * Cookie expires at end of session by default,
409 * set to 0 by calloc().
411 * max_age:
412 * -1 is use cookie's expiration date if any
413 * 0 is force expiration at the end of session,
414 * ignoring cookie's expiration date
415 * 1+ is use cookie's expiration date,
416 * but limit age to the given number of days.
419 max_age = get_cookies_max_age();
420 if (max_age) {
421 unsigned char *date;
422 time_t expires;
424 switch (parse_header_param(str, "expires", &date)) {
425 case HEADER_PARAM_FOUND:
426 expires = parse_date(&date, NULL, 0, 1); /* Convert date to seconds. */
428 mem_free(date);
430 if (expires) {
431 if (max_age > 0) {
432 time_t seconds = ((time_t) max_age)*24*3600;
433 time_t deadline = time(NULL) + seconds;
435 if (expires > deadline) /* Over-aged cookie ? */
436 expires = deadline;
439 cookie->expires = expires;
441 break;
443 case HEADER_PARAM_NOT_FOUND:
444 break;
446 default: /* error */
447 done_cookie(cookie);
448 return;
452 cookie->secure = (parse_header_param(str, "secure", NULL)
453 == HEADER_PARAM_FOUND);
455 #ifdef DEBUG_COOKIES
457 DBG("Got cookie %s = %s from %s, domain %s, "
458 "expires at %"TIME_PRINT_FORMAT", secure %d", cookie->name,
459 cookie->value, cookie->server->host, cookie->domain,
460 (time_print_T) cookie->expires, cookie->secure);
462 #endif
464 if (!is_domain_security_ok(cookie->domain, uri->host, uri->hostlen)) {
465 #ifdef DEBUG_COOKIES
466 DBG("Domain security violated: %s vs %.*s", cookie->domain,
467 uri->hostlen, uri->host);
468 #endif
469 mem_free(cookie->domain);
470 cookie->domain = memacpy(uri->host, uri->hostlen);
473 /* We have already check COOKIES_ACCEPT_NONE */
474 if (get_cookies_accept_policy() == COOKIES_ACCEPT_ASK) {
475 add_to_list(cookie_queries, cookie);
476 add_questions_entry(accept_cookie_dialog, cookie);
477 return;
480 accept_cookie(cookie);
483 void
484 accept_cookie(struct cookie *cookie)
486 struct c_domain *cd;
487 struct listbox_item *root = cookie->server->box_item;
488 int domain_len;
490 if (root)
491 cookie->box_item = add_listbox_leaf(&cookie_browser, root, cookie);
493 /* Do not weed out duplicates when loading the cookie file. It doesn't
494 * scale at all, being O(N^2) and taking about 2s with my 500 cookies
495 * (so if you don't notice that 100ms with your 100 cookies, that's
496 * not an argument). --pasky */
497 if (!cookies_nosave) {
498 struct cookie *c, *next;
500 foreachsafe (c, next, cookies) {
501 if (c_strcasecmp(c->name, cookie->name)
502 || c_strcasecmp(c->domain, cookie->domain))
503 continue;
505 delete_cookie(c);
506 /* @set_cookies_dirty will be called below. */
510 add_to_list(cookies, cookie);
511 set_cookies_dirty();
513 /* XXX: This crunches CPU too. --pasky */
514 foreach (cd, c_domains)
515 if (!c_strcasecmp(cd->domain, cookie->domain))
516 return;
518 domain_len = strlen(cookie->domain);
519 /* One byte is reserved for domain in struct c_domain. */
520 cd = mem_alloc(sizeof(*cd) + domain_len);
521 if (!cd) return;
523 memcpy(cd->domain, cookie->domain, domain_len + 1);
524 add_to_list(c_domains, cd);
527 #if 0
528 static unsigned int cookie_id = 0;
530 static void
531 delete_cookie(struct cookie *c)
533 struct c_domain *cd;
534 struct cookie *d;
536 foreach (d, cookies)
537 if (!c_strcasecmp(d->domain, c->domain))
538 goto end;
540 foreach (cd, c_domains) {
541 if (!c_strcasecmp(cd->domain, c->domain)) {
542 del_from_list(cd);
543 mem_free(cd);
544 break;
548 end:
549 del_from_list(c);
550 done_cookie(c);
554 static struct
555 cookie *find_cookie_id(void *idp)
557 int id = (int) idp;
558 struct cookie *c;
560 foreach (c, cookies)
561 if (c->id == id)
562 return c;
564 return NULL;
568 static void
569 reject_cookie(void *idp)
571 struct cookie *c = find_cookie_id(idp);
573 if (!c) return;
575 delete_cookie(c);
576 set_cookies_dirty(); /* @find_cookie_id doesn't use @cookie_queries */
580 static void
581 cookie_default(void *idp, int a)
583 struct cookie *c = find_cookie_id(idp);
585 if (c) c->server->accept = a;
589 static void
590 accept_cookie_always(void *idp)
592 cookie_default(idp, 1);
596 static void
597 accept_cookie_never(void *idp)
599 cookie_default(idp, 0);
600 reject_cookie(idp);
602 #endif
605 static inline int
606 is_path_prefix(unsigned char *d, unsigned char *s)
608 int dl = strlen(d);
610 /* TODO: strlcmp()? --pasky */
612 if (dl > strlen(s)) return 0;
614 return !memcmp(d, s, dl);
618 struct string *
619 send_cookies(struct uri *uri)
621 struct c_domain *cd;
622 struct cookie *c, *next;
623 unsigned char *path = NULL;
624 static struct string header;
625 time_t now;
627 if (!uri->host || !uri->data)
628 return NULL;
630 foreach (cd, c_domains)
631 if (is_in_domain(cd->domain, uri->host, uri->hostlen)) {
632 path = get_uri_string(uri, URI_PATH);
633 break;
636 if (!path) return NULL;
638 init_string(&header);
640 now = time(NULL);
641 foreachsafe (c, next, cookies) {
642 if (!is_in_domain(c->domain, uri->host, uri->hostlen)
643 || !is_path_prefix(c->path, path))
644 continue;
646 if (c->expires && c->expires <= now) {
647 #ifdef DEBUG_COOKIES
648 DBG("Cookie %s=%s (exp %"TIME_PRINT_FORMAT") expired.",
649 c->name, c->value, (time_print_T) c->expires);
650 #endif
651 delete_cookie(c);
653 set_cookies_dirty();
654 continue;
657 /* Not sure if this is 100% right..? --pasky */
658 if (c->secure && uri->protocol != PROTOCOL_HTTPS)
659 continue;
661 if (header.length)
662 add_to_string(&header, "; ");
664 add_to_string(&header, c->name);
665 add_char_to_string(&header, '=');
666 add_to_string(&header, c->value);
667 #ifdef DEBUG_COOKIES
668 DBG("Cookie: %s=%s", c->name, c->value);
669 #endif
672 mem_free(path);
674 if (!header.length) {
675 done_string(&header);
676 return NULL;
679 return &header;
682 static void done_cookies(struct module *module);
685 void
686 load_cookies(void) {
687 /* Buffer size is set to be enough to read long lines that
688 * save_cookies may write. 6 is choosen after the fprintf(..) call
689 * in save_cookies(). --Zas */
690 unsigned char in_buffer[6 * MAX_STR_LEN];
691 unsigned char *cookfile = COOKIES_FILENAME;
692 FILE *fp;
693 time_t now;
695 if (elinks_home) {
696 cookfile = straconcat(elinks_home, cookfile,
697 (unsigned char *) NULL);
698 if (!cookfile) return;
701 /* Do it here, as we will delete whole cookies list if the file was
702 * removed */
703 cookies_nosave = 1;
704 done_cookies(&cookies_module);
705 cookies_nosave = 0;
707 fp = fopen(cookfile, "rb");
708 if (elinks_home) mem_free(cookfile);
709 if (!fp) return;
711 /* XXX: We don't want to overwrite the cookies file
712 * periodically to our death. */
713 cookies_nosave = 1;
715 now = time(NULL);
716 while (fgets(in_buffer, 6 * MAX_STR_LEN, fp)) {
717 struct cookie *cookie;
718 unsigned char *p, *q = in_buffer;
719 enum { NAME = 0, VALUE, SERVER, PATH, DOMAIN, EXPIRES, SECURE, MEMBERS } member;
720 struct {
721 unsigned char *pos;
722 int len;
723 } members[MEMBERS];
724 time_t expires;
726 /* First find all members. */
727 for (member = NAME; member < MEMBERS; member++, q = ++p) {
728 p = strchr(q, '\t');
729 if (!p) {
730 if (member + 1 != MEMBERS) break; /* last field ? */
731 p = strchr(q, '\n');
732 if (!p) break;
735 members[member].pos = q;
736 members[member].len = p - q;
739 if (member != MEMBERS) continue; /* Invalid line. */
741 /* Skip expired cookies if any. */
742 expires = str_to_time_t(members[EXPIRES].pos);
743 if (!expires || expires <= now) {
744 set_cookies_dirty();
745 continue;
748 /* Prepare cookie if all members and fields was read. */
749 cookie = mem_calloc(1, sizeof(*cookie));
750 if (!cookie) continue;
752 cookie->server = get_cookie_server(members[SERVER].pos, members[SERVER].len);
753 cookie->name = memacpy(members[NAME].pos, members[NAME].len);
754 cookie->value = memacpy(members[VALUE].pos, members[VALUE].len);
755 cookie->path = memacpy(members[PATH].pos, members[PATH].len);
756 cookie->domain = memacpy(members[DOMAIN].pos, members[DOMAIN].len);
758 /* Check whether all fields were correctly allocated. */
759 if (!cookie->server || !cookie->name || !cookie->value
760 || !cookie->path || !cookie->domain) {
761 done_cookie(cookie);
762 continue;
765 cookie->expires = expires;
766 cookie->secure = !!atoi(members[SECURE].pos);
768 accept_cookie(cookie);
771 cookies_nosave = 0;
772 fclose(fp);
775 static void
776 resave_cookies_bottom_half(void *always_null)
778 if (get_cookies_save() && get_cookies_resave())
779 save_cookies(NULL); /* checks cookies_dirty */
782 /* Note that the cookies have been modified, and register a bottom
783 * half for saving them if appropriate. We use a bottom half so that
784 * if something makes multiple changes and calls this for each change,
785 * the cookies get saved only once at the end. */
786 void
787 set_cookies_dirty(void)
789 /* Do not check @cookies_dirty here. If the previous attempt
790 * to save cookies failed, @cookies_dirty can still be nonzero
791 * even though @resave_cookies_bottom_half is no longer in the
792 * queue. */
793 cookies_dirty = 1;
794 /* If @resave_cookies_bottom_half is already in the queue,
795 * @register_bottom_half does nothing. */
796 register_bottom_half(resave_cookies_bottom_half, NULL);
799 /* @term is non-NULL if the user told ELinks to save cookies, or NULL
800 * if ELinks decided that on its own. In the former case, this
801 * function reports errors to @term, unless CONFIG_SMALL is defined.
802 * In the latter case, this function does not save the cookies if it
803 * thinks the file is already up to date. */
804 void
805 save_cookies(struct terminal *term) {
806 struct cookie *c;
807 unsigned char *cookfile;
808 struct secure_save_info *ssi;
809 time_t now;
811 #ifdef CONFIG_SMALL
812 # define CANNOT_SAVE_COOKIES(flags, message)
813 #else
814 # define CANNOT_SAVE_COOKIES(flags, message) \
815 do { \
816 if (term) \
817 info_box(term, flags, N_("Cannot save cookies"),\
818 ALIGN_LEFT, message); \
819 } while (0)
820 #endif
822 if (cookies_nosave) {
823 assert(term == NULL);
824 if_assert_failed {}
825 return;
827 if (!elinks_home) {
828 CANNOT_SAVE_COOKIES(0, N_("ELinks was started without a home directory."));
829 return;
831 if (!cookies_dirty && !term)
832 return;
833 if (get_cmd_opt_bool("anonymous")) {
834 CANNOT_SAVE_COOKIES(0, N_("ELinks was started with the -anonymous option."));
835 return;
838 cookfile = straconcat(elinks_home, COOKIES_FILENAME,
839 (unsigned char *) NULL);
840 if (!cookfile) {
841 CANNOT_SAVE_COOKIES(0, N_("Out of memory"));
842 return;
845 ssi = secure_open(cookfile);
846 mem_free(cookfile);
847 if (!ssi) {
848 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
849 secsave_strerror(secsave_errno, term));
850 return;
853 now = time(NULL);
854 foreach (c, cookies) {
855 if (!c->expires || c->expires <= now) continue;
856 if (secure_fprintf(ssi, "%s\t%s\t%s\t%s\t%s\t%"TIME_PRINT_FORMAT"\t%d\n",
857 c->name, c->value,
858 c->server->host,
859 empty_string_or_(c->path),
860 empty_string_or_(c->domain),
861 (time_print_T) c->expires, c->secure) < 0)
862 break;
865 secsave_errno = SS_ERR_OTHER; /* @secure_close doesn't always set it */
866 if (!secure_close(ssi)) cookies_dirty = 0;
867 else {
868 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
869 secsave_strerror(secsave_errno, term));
871 #undef CANNOT_SAVE_COOKIES
874 static void
875 init_cookies(struct module *module)
877 if (get_cookies_save())
878 load_cookies();
881 /* Like @delete_cookie, this function does not set @cookies_dirty.
882 * The caller must do that if appropriate. */
883 static void
884 free_cookies_list(LIST_OF(struct cookie) *list)
886 while (!list_empty(*list)) {
887 struct cookie *cookie = list->next;
889 delete_cookie(cookie);
893 static void
894 done_cookies(struct module *module)
896 free_list(c_domains);
898 if (!cookies_nosave && get_cookies_save())
899 save_cookies(NULL);
901 free_cookies_list(&cookies);
902 free_cookies_list(&cookie_queries);
903 /* If @save_cookies failed above, @cookies_dirty can still be
904 * nonzero. Now if @resave_cookies_bottom_half were in the
905 * queue, it could save the empty @cookies list to the file.
906 * Prevent that. */
907 cookies_dirty = 0;
910 struct module cookies_module = struct_module(
911 /* name: */ N_("Cookies"),
912 /* options: */ cookies_options,
913 /* events: */ NULL,
914 /* submodules: */ NULL,
915 /* data: */ NULL,
916 /* init: */ init_cookies,
917 /* done: */ done_cookies