Handle mailcap's copiousoutput without an external pager.
[elinks.git] / src / cookies / cookies.c
blobbc02d16d517e05c2a6dec9df6b76aeafe3aaeca2
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\n"
105 " cookie's expiration date\n"
106 "1+ is use cookie's expiration date, but limit age to the\n"
107 " given 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 "
112 "for all non-international domains (instead of just two "
113 "dots). Some countries have generic second level domains "
114 "(eg. .com.pl, .co.uk) and allowing sites to set cookies "
115 "for these generic domains could potentially be very bad. "
116 "Note, it is off by default as it breaks a lot of sites.")),
118 INIT_OPT_BOOL("cookies", N_("Saving"),
119 "save", 0, 1,
120 N_("Whether cookies should be loaded from and saved to "
121 "disk.")),
123 INIT_OPT_BOOL("cookies", N_("Resaving"),
124 "resave", 0, 1,
125 N_("Save cookies after each change in cookies list? "
126 "No effect when cookie saving (cookies.save) is off.")),
128 NULL_OPTION_INFO,
131 #define get_opt_cookies(which) cookies_options[(which)].option.value
132 #define get_cookies_accept_policy() get_opt_cookies(COOKIES_ACCEPT_POLICY).number
133 #define get_cookies_max_age() get_opt_cookies(COOKIES_MAX_AGE).number
134 #define get_cookies_paranoid_security() get_opt_cookies(COOKIES_PARANOID_SECURITY).number
135 #define get_cookies_save() get_opt_cookies(COOKIES_SAVE).number
136 #define get_cookies_resave() get_opt_cookies(COOKIES_RESAVE).number
138 struct cookie_server *
139 get_cookie_server(unsigned char *host, int hostlen)
141 struct cookie_server *sort_spot = NULL;
142 struct cookie_server *cs;
144 foreach (cs, cookie_servers) {
145 /* XXX: We must count with cases like "x.co" vs "x.co.uk"
146 * below! */
147 int cslen = strlen(cs->host);
148 int cmp = c_strncasecmp(cs->host, host, hostlen);
150 if (!sort_spot && (cmp > 0 || (cmp == 0 && cslen > hostlen))) {
151 /* This is the first @cs with name greater than @host,
152 * our dream sort spot! */
153 sort_spot = cs->prev;
156 if (cmp || cslen != hostlen)
157 continue;
159 object_lock(cs);
160 return cs;
163 cs = mem_calloc(1, sizeof(*cs) + hostlen);
164 if (!cs) return NULL;
166 memcpy(cs->host, host, hostlen);
167 object_nolock(cs, "cookie_server");
169 cs->box_item = add_listbox_folder(&cookie_browser, NULL, cs);
171 object_lock(cs);
173 if (!sort_spot) {
174 /* No sort spot found, therefore this sorts at the end. */
175 add_to_list_end(cookie_servers, cs);
176 del_from_list(cs->box_item);
177 add_to_list_end(cookie_browser.root.child, cs->box_item);
178 } else {
179 /* Sort spot found, sort after it. */
180 add_at_pos(sort_spot, cs);
181 if (sort_spot != (struct cookie_server *) &cookie_servers) {
182 del_from_list(cs->box_item);
183 add_at_pos(sort_spot->box_item, cs->box_item);
184 } /* else we are already at the top anyway. */
187 return cs;
190 static void
191 done_cookie_server(struct cookie_server *cs)
193 object_unlock(cs);
194 if (is_object_used(cs)) return;
196 if (cs->box_item) done_listbox_item(&cookie_browser, cs->box_item);
197 del_from_list(cs);
198 mem_free(cs);
201 void
202 done_cookie(struct cookie *c)
204 if (c->box_item) done_listbox_item(&cookie_browser, c->box_item);
205 if (c->server) done_cookie_server(c->server);
206 mem_free_if(c->name);
207 mem_free_if(c->value);
208 mem_free_if(c->path);
209 mem_free_if(c->domain);
210 mem_free(c);
213 /* The cookie @c can be either in @cookies or in @cookie_queries.
214 * Because changes in @cookie_queries should not affect the cookie
215 * file, this function does not set @cookies_dirty. Instead, the
216 * caller must do that if appropriate. */
217 void
218 delete_cookie(struct cookie *c)
220 del_from_list(c);
221 done_cookie(c);
225 /* Check whether cookie's domain matches server.
226 * It returns 1 if ok, 0 else. */
227 static int
228 is_domain_security_ok(unsigned char *domain, unsigned char *server, int server_len)
230 int i;
231 int domain_len;
232 int need_dots;
234 if (domain[0] == '.') domain++;
235 domain_len = strlen(domain);
237 /* Match domain and server.. */
239 /* XXX: Hmm, can't we use c_strlcasecmp() here? --pasky */
241 if (domain_len > server_len) return 0;
243 /* Ensure that the domain is atleast a substring of the server before
244 * continuing. */
245 if (c_strncasecmp(domain, server + server_len - domain_len, domain_len))
246 return 0;
248 /* Allow domains which are same as servers. --<rono@sentuny.com.au> */
249 /* Mozilla does it as well ;))) and I can't figure out any security
250 * risk. --pasky */
251 if (server_len == domain_len)
252 return 1;
254 /* Check whether the server is an IP address, and require an exact host
255 * match for the cookie, so any chance of IP address funkiness is
256 * eliminated (e.g. the alias 127.1 domain-matching 99.54.127.1). Idea
257 * from mozilla. (bug 562) */
258 if (is_ip_address(server, server_len))
259 return 0;
261 /* Also test if domain is secure en ugh.. */
263 need_dots = 1;
265 if (get_cookies_paranoid_security()) {
266 /* This is somehow controversial attempt (by the way violating
267 * RFC) to increase cookies security in national domains, done
268 * by Mikulas. As it breaks a lot of sites, I decided to make
269 * this optional and off by default. I also don't think this
270 * improves security considerably, as it's SITE'S fault and
271 * also no other browser probably does it. --pasky */
272 /* Mikulas' comment: Some countries have generic 2-nd level
273 * domains (like .com.pl, .co.uk ...) and it would be very bad
274 * if someone set cookies for these generic domains. Imagine
275 * for example that server http://brutalporn.com.pl sets cookie
276 * Set-Cookie: user_is=perverse_pig; domain=.com.pl -- then
277 * this cookie would be sent to all commercial servers in
278 * Poland. */
279 need_dots = 2;
281 if (domain_len > 0) {
282 int pos = end_with_known_tld(domain, domain_len);
284 if (pos >= 1 && domain[pos - 1] == '.')
285 need_dots = 1;
289 for (i = 0; domain[i]; i++)
290 if (domain[i] == '.' && !--need_dots)
291 break;
293 if (need_dots > 0) return 0;
294 return 1;
297 /* Allocate a struct cookie and initialize it with the specified
298 * values (rather than copies). Returns NULL on error. On success,
299 * the cookie is basically safe for @done_cookie or @accept_cookie,
300 * although you may also want to set the remaining members and check
301 * @get_cookies_accept_policy and @is_domain_security_ok.
303 * The unsigned char * arguments must be allocated with @mem_alloc or
304 * equivalent, because @done_cookie will @mem_free them. Likewise,
305 * the caller must already have locked @server. If @init_cookie
306 * fails, then it frees the strings itself, and unlocks @server.
308 * If any parameter is NULL, then @init_cookie fails and does not
309 * consider that a bug. This means callers can use e.g. @stracpy
310 * and let @init_cookie check whether the call ran out of memory. */
311 struct cookie *
312 init_cookie(unsigned char *name, unsigned char *value,
313 unsigned char *path, unsigned char *domain,
314 struct cookie_server *server)
316 struct cookie *cookie = mem_calloc(1, sizeof(*cookie));
318 if (!cookie || !name || !value || !path || !domain || !server) {
319 mem_free_if(cookie);
320 mem_free_if(name);
321 mem_free_if(value);
322 mem_free_if(path);
323 mem_free_if(domain);
324 done_cookie_server(server);
325 return NULL;
327 object_nolock(cookie, "cookie"); /* Debugging purpose. */
329 cookie->name = name;
330 cookie->value = value;
331 cookie->domain = domain;
332 cookie->path = path;
333 cookie->server = server; /* the caller already locked it for us */
335 return cookie;
338 void
339 set_cookie(struct uri *uri, unsigned char *str)
341 unsigned char *path, *domain;
342 struct cookie *cookie;
343 struct cookie_str cstr;
344 int max_age;
346 if (get_cookies_accept_policy() == COOKIES_ACCEPT_NONE)
347 return;
349 #ifdef DEBUG_COOKIES
350 DBG("set_cookie -> (%s) %s", struri(uri), str);
351 #endif
353 if (!parse_cookie_str(&cstr, str)) return;
355 switch (parse_header_param(str, "path", &path)) {
356 unsigned char *path_end;
358 case HEADER_PARAM_FOUND:
359 if (!path[0]
360 || path[strlen(path) - 1] != '/')
361 add_to_strn(&path, "/");
363 if (path[0] != '/') {
364 add_to_strn(&path, "x");
365 memmove(path + 1, path, strlen(path) - 1);
366 path[0] = '/';
368 break;
370 case HEADER_PARAM_NOT_FOUND:
371 path = get_uri_string(uri, URI_PATH);
372 if (!path)
373 return;
375 path_end = strrchr(path, '/');
376 if (path_end)
377 path_end[1] = '\0';
378 break;
380 default: /* error */
381 return;
384 if (parse_header_param(str, "domain", &domain) == HEADER_PARAM_NOT_FOUND)
385 domain = memacpy(uri->host, uri->hostlen);
386 if (domain && domain[0] == '.')
387 memmove(domain, domain + 1, strlen(domain));
389 cookie = init_cookie(memacpy(str, cstr.nam_end - str),
390 memacpy(cstr.val_start, cstr.val_end - cstr.val_start),
391 path,
392 domain,
393 get_cookie_server(uri->host, uri->hostlen));
394 if (!cookie) return;
395 /* @cookie now owns @path and @domain. */
397 #if 0
398 /* We don't actually set ->accept at the moment. But I have kept it
399 * since it will maybe help to fix bug 77 - Support for more
400 * finegrained control upon accepting of cookies. */
401 if (!cookie->server->accept) {
402 #ifdef DEBUG_COOKIES
403 DBG("Dropped.");
404 #endif
405 done_cookie(cookie);
406 return;
408 #endif
410 /* Set cookie expiration if needed.
411 * Cookie expires at end of session by default,
412 * set to 0 by calloc().
414 * max_age:
415 * -1 is use cookie's expiration date if any
416 * 0 is force expiration at the end of session,
417 * ignoring cookie's expiration date
418 * 1+ is use cookie's expiration date,
419 * but limit age to the given number of days.
422 max_age = get_cookies_max_age();
423 if (max_age) {
424 unsigned char *date;
425 time_t expires;
427 switch (parse_header_param(str, "expires", &date)) {
428 case HEADER_PARAM_FOUND:
429 expires = parse_date(&date, NULL, 0, 1); /* Convert date to seconds. */
431 mem_free(date);
433 if (expires) {
434 if (max_age > 0) {
435 time_t seconds = ((time_t) max_age)*24*3600;
436 time_t deadline = time(NULL) + seconds;
438 if (expires > deadline) /* Over-aged cookie ? */
439 expires = deadline;
442 cookie->expires = expires;
444 break;
446 case HEADER_PARAM_NOT_FOUND:
447 break;
449 default: /* error */
450 done_cookie(cookie);
451 return;
455 cookie->secure = (parse_header_param(str, "secure", NULL)
456 == HEADER_PARAM_FOUND);
458 #ifdef DEBUG_COOKIES
460 DBG("Got cookie %s = %s from %s, domain %s, "
461 "expires at %"TIME_PRINT_FORMAT", secure %d", cookie->name,
462 cookie->value, cookie->server->host, cookie->domain,
463 (time_print_T) cookie->expires, cookie->secure);
465 #endif
467 if (!is_domain_security_ok(cookie->domain, uri->host, uri->hostlen)) {
468 #ifdef DEBUG_COOKIES
469 DBG("Domain security violated: %s vs %.*s", cookie->domain,
470 uri->hostlen, uri->host);
471 #endif
472 mem_free(cookie->domain);
473 cookie->domain = memacpy(uri->host, uri->hostlen);
476 /* We have already check COOKIES_ACCEPT_NONE */
477 if (get_cookies_accept_policy() == COOKIES_ACCEPT_ASK) {
478 add_to_list(cookie_queries, cookie);
479 add_questions_entry(accept_cookie_dialog, cookie);
480 return;
483 accept_cookie(cookie);
486 void
487 accept_cookie(struct cookie *cookie)
489 struct c_domain *cd;
490 struct listbox_item *root = cookie->server->box_item;
491 int domain_len;
493 if (root)
494 cookie->box_item = add_listbox_leaf(&cookie_browser, root, cookie);
496 /* Do not weed out duplicates when loading the cookie file. It doesn't
497 * scale at all, being O(N^2) and taking about 2s with my 500 cookies
498 * (so if you don't notice that 100ms with your 100 cookies, that's
499 * not an argument). --pasky */
500 if (!cookies_nosave) {
501 struct cookie *c, *next;
503 foreachsafe (c, next, cookies) {
504 if (c_strcasecmp(c->name, cookie->name)
505 || c_strcasecmp(c->domain, cookie->domain))
506 continue;
508 delete_cookie(c);
509 /* @set_cookies_dirty will be called below. */
513 add_to_list(cookies, cookie);
514 set_cookies_dirty();
516 /* XXX: This crunches CPU too. --pasky */
517 foreach (cd, c_domains)
518 if (!c_strcasecmp(cd->domain, cookie->domain))
519 return;
521 domain_len = strlen(cookie->domain);
522 /* One byte is reserved for domain in struct c_domain. */
523 cd = mem_alloc(sizeof(*cd) + domain_len);
524 if (!cd) return;
526 memcpy(cd->domain, cookie->domain, domain_len + 1);
527 add_to_list(c_domains, cd);
530 #if 0
531 static unsigned int cookie_id = 0;
533 static void
534 delete_cookie(struct cookie *c)
536 struct c_domain *cd;
537 struct cookie *d;
539 foreach (d, cookies)
540 if (!c_strcasecmp(d->domain, c->domain))
541 goto end;
543 foreach (cd, c_domains) {
544 if (!c_strcasecmp(cd->domain, c->domain)) {
545 del_from_list(cd);
546 mem_free(cd);
547 break;
551 end:
552 del_from_list(c);
553 done_cookie(c);
557 static struct
558 cookie *find_cookie_id(void *idp)
560 int id = (int) idp;
561 struct cookie *c;
563 foreach (c, cookies)
564 if (c->id == id)
565 return c;
567 return NULL;
571 static void
572 reject_cookie(void *idp)
574 struct cookie *c = find_cookie_id(idp);
576 if (!c) return;
578 delete_cookie(c);
579 set_cookies_dirty(); /* @find_cookie_id doesn't use @cookie_queries */
583 static void
584 cookie_default(void *idp, int a)
586 struct cookie *c = find_cookie_id(idp);
588 if (c) c->server->accept = a;
592 static void
593 accept_cookie_always(void *idp)
595 cookie_default(idp, 1);
599 static void
600 accept_cookie_never(void *idp)
602 cookie_default(idp, 0);
603 reject_cookie(idp);
605 #endif
608 static inline int
609 is_path_prefix(unsigned char *d, unsigned char *s)
611 int dl = strlen(d);
613 /* TODO: strlcmp()? --pasky */
615 if (dl > strlen(s)) return 0;
617 return !memcmp(d, s, dl);
621 struct string *
622 send_cookies(struct uri *uri)
624 struct c_domain *cd;
625 struct cookie *c, *next;
626 unsigned char *path = NULL;
627 static struct string header;
628 time_t now;
630 if (!uri->host || !uri->data)
631 return NULL;
633 foreach (cd, c_domains)
634 if (is_in_domain(cd->domain, uri->host, uri->hostlen)) {
635 path = get_uri_string(uri, URI_PATH);
636 break;
639 if (!path) return NULL;
641 init_string(&header);
643 now = time(NULL);
644 foreachsafe (c, next, cookies) {
645 if (!is_in_domain(c->domain, uri->host, uri->hostlen)
646 || !is_path_prefix(c->path, path))
647 continue;
649 if (c->expires && c->expires <= now) {
650 #ifdef DEBUG_COOKIES
651 DBG("Cookie %s=%s (exp %"TIME_PRINT_FORMAT") expired.",
652 c->name, c->value, (time_print_T) c->expires);
653 #endif
654 delete_cookie(c);
656 set_cookies_dirty();
657 continue;
660 /* Not sure if this is 100% right..? --pasky */
661 if (c->secure && uri->protocol != PROTOCOL_HTTPS)
662 continue;
664 if (header.length)
665 add_to_string(&header, "; ");
667 add_to_string(&header, c->name);
668 add_char_to_string(&header, '=');
669 add_to_string(&header, c->value);
670 #ifdef DEBUG_COOKIES
671 DBG("Cookie: %s=%s", c->name, c->value);
672 #endif
675 mem_free(path);
677 if (!header.length) {
678 done_string(&header);
679 return NULL;
682 return &header;
685 static void done_cookies(struct module *module);
688 void
689 load_cookies(void) {
690 /* Buffer size is set to be enough to read long lines that
691 * save_cookies may write. 6 is choosen after the fprintf(..) call
692 * in save_cookies(). --Zas */
693 unsigned char in_buffer[6 * MAX_STR_LEN];
694 unsigned char *cookfile = COOKIES_FILENAME;
695 FILE *fp;
696 time_t now;
698 if (elinks_home) {
699 cookfile = straconcat(elinks_home, cookfile,
700 (unsigned char *) NULL);
701 if (!cookfile) return;
704 /* Do it here, as we will delete whole cookies list if the file was
705 * removed */
706 cookies_nosave = 1;
707 done_cookies(&cookies_module);
708 cookies_nosave = 0;
710 fp = fopen(cookfile, "rb");
711 if (elinks_home) mem_free(cookfile);
712 if (!fp) return;
714 /* XXX: We don't want to overwrite the cookies file
715 * periodically to our death. */
716 cookies_nosave = 1;
718 now = time(NULL);
719 while (fgets(in_buffer, 6 * MAX_STR_LEN, fp)) {
720 struct cookie *cookie;
721 unsigned char *p, *q = in_buffer;
722 enum { NAME = 0, VALUE, SERVER, PATH, DOMAIN, EXPIRES, SECURE, MEMBERS } member;
723 struct {
724 unsigned char *pos;
725 int len;
726 } members[MEMBERS];
727 time_t expires;
729 /* First find all members. */
730 for (member = NAME; member < MEMBERS; member++, q = ++p) {
731 p = strchr(q, '\t');
732 if (!p) {
733 if (member + 1 != MEMBERS) break; /* last field ? */
734 p = strchr(q, '\n');
735 if (!p) break;
738 members[member].pos = q;
739 members[member].len = p - q;
742 if (member != MEMBERS) continue; /* Invalid line. */
744 /* Skip expired cookies if any. */
745 expires = str_to_time_t(members[EXPIRES].pos);
746 if (!expires || expires <= now) {
747 set_cookies_dirty();
748 continue;
751 /* Prepare cookie if all members and fields was read. */
752 cookie = mem_calloc(1, sizeof(*cookie));
753 if (!cookie) continue;
755 cookie->server = get_cookie_server(members[SERVER].pos, members[SERVER].len);
756 cookie->name = memacpy(members[NAME].pos, members[NAME].len);
757 cookie->value = memacpy(members[VALUE].pos, members[VALUE].len);
758 cookie->path = memacpy(members[PATH].pos, members[PATH].len);
759 cookie->domain = memacpy(members[DOMAIN].pos, members[DOMAIN].len);
761 /* Check whether all fields were correctly allocated. */
762 if (!cookie->server || !cookie->name || !cookie->value
763 || !cookie->path || !cookie->domain) {
764 done_cookie(cookie);
765 continue;
768 cookie->expires = expires;
769 cookie->secure = !!atoi(members[SECURE].pos);
771 accept_cookie(cookie);
774 cookies_nosave = 0;
775 fclose(fp);
778 static void
779 resave_cookies_bottom_half(void *always_null)
781 if (get_cookies_save() && get_cookies_resave())
782 save_cookies(NULL); /* checks cookies_dirty */
785 /* Note that the cookies have been modified, and register a bottom
786 * half for saving them if appropriate. We use a bottom half so that
787 * if something makes multiple changes and calls this for each change,
788 * the cookies get saved only once at the end. */
789 void
790 set_cookies_dirty(void)
792 /* Do not check @cookies_dirty here. If the previous attempt
793 * to save cookies failed, @cookies_dirty can still be nonzero
794 * even though @resave_cookies_bottom_half is no longer in the
795 * queue. */
796 cookies_dirty = 1;
797 /* If @resave_cookies_bottom_half is already in the queue,
798 * @register_bottom_half does nothing. */
799 register_bottom_half(resave_cookies_bottom_half, NULL);
802 /* @term is non-NULL if the user told ELinks to save cookies, or NULL
803 * if ELinks decided that on its own. In the former case, this
804 * function reports errors to @term, unless CONFIG_SMALL is defined.
805 * In the latter case, this function does not save the cookies if it
806 * thinks the file is already up to date. */
807 void
808 save_cookies(struct terminal *term) {
809 struct cookie *c;
810 unsigned char *cookfile;
811 struct secure_save_info *ssi;
812 time_t now;
814 #ifdef CONFIG_SMALL
815 # define CANNOT_SAVE_COOKIES(flags, message)
816 #else
817 # define CANNOT_SAVE_COOKIES(flags, message) \
818 do { \
819 if (term) \
820 info_box(term, flags, N_("Cannot save cookies"),\
821 ALIGN_LEFT, message); \
822 } while (0)
823 #endif
825 if (cookies_nosave) {
826 assert(term == NULL);
827 if_assert_failed {}
828 return;
830 if (!elinks_home) {
831 CANNOT_SAVE_COOKIES(0, N_("ELinks was started without a home directory."));
832 return;
834 if (!cookies_dirty && !term)
835 return;
836 if (get_cmd_opt_bool("anonymous")) {
837 CANNOT_SAVE_COOKIES(0, N_("ELinks was started with the -anonymous option."));
838 return;
841 cookfile = straconcat(elinks_home, COOKIES_FILENAME,
842 (unsigned char *) NULL);
843 if (!cookfile) {
844 CANNOT_SAVE_COOKIES(0, N_("Out of memory"));
845 return;
848 ssi = secure_open(cookfile);
849 mem_free(cookfile);
850 if (!ssi) {
851 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
852 secsave_strerror(secsave_errno, term));
853 return;
856 now = time(NULL);
857 foreach (c, cookies) {
858 if (!c->expires || c->expires <= now) continue;
859 if (secure_fprintf(ssi, "%s\t%s\t%s\t%s\t%s\t%"TIME_PRINT_FORMAT"\t%d\n",
860 c->name, c->value,
861 c->server->host,
862 empty_string_or_(c->path),
863 empty_string_or_(c->domain),
864 (time_print_T) c->expires, c->secure) < 0)
865 break;
868 secsave_errno = SS_ERR_OTHER; /* @secure_close doesn't always set it */
869 if (!secure_close(ssi)) cookies_dirty = 0;
870 else {
871 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
872 secsave_strerror(secsave_errno, term));
874 #undef CANNOT_SAVE_COOKIES
877 static void
878 init_cookies(struct module *module)
880 if (get_cookies_save())
881 load_cookies();
884 /* Like @delete_cookie, this function does not set @cookies_dirty.
885 * The caller must do that if appropriate. */
886 static void
887 free_cookies_list(LIST_OF(struct cookie) *list)
889 while (!list_empty(*list)) {
890 struct cookie *cookie = list->next;
892 delete_cookie(cookie);
896 static void
897 done_cookies(struct module *module)
899 free_list(c_domains);
901 if (!cookies_nosave && get_cookies_save())
902 save_cookies(NULL);
904 free_cookies_list(&cookies);
905 free_cookies_list(&cookie_queries);
906 /* If @save_cookies failed above, @cookies_dirty can still be
907 * nonzero. Now if @resave_cookies_bottom_half were in the
908 * queue, it could save the empty @cookies list to the file.
909 * Prevent that. */
910 cookies_dirty = 0;
913 struct module cookies_module = struct_module(
914 /* name: */ N_("Cookies"),
915 /* options: */ cookies_options,
916 /* events: */ NULL,
917 /* submodules: */ NULL,
918 /* data: */ NULL,
919 /* init: */ init_cookies,
920 /* done: */ done_cookies