bug 764: Initialize the right member of union option_value
[elinks.git] / src / cookies / cookies.c
blobad5c9fb81f799dfdb9cc58c31bf2015ee6abe436
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 union 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));
317 if (!cookie || !name || !value || !path || !domain || !server) {
318 mem_free_if(cookie);
319 mem_free_if(name);
320 mem_free_if(value);
321 mem_free_if(path);
322 mem_free_if(domain);
323 done_cookie_server(server);
324 return NULL;
326 object_nolock(cookie, "cookie"); /* Debugging purpose. */
328 cookie->name = name;
329 cookie->value = value;
330 cookie->domain = domain;
331 cookie->path = path;
332 cookie->server = server; /* the caller already locked it for us */
334 return cookie;
337 void
338 set_cookie(struct uri *uri, unsigned char *str)
340 unsigned char *path, *domain;
341 struct cookie *cookie;
342 struct cookie_str cstr;
343 int max_age;
345 if (get_cookies_accept_policy() == COOKIES_ACCEPT_NONE)
346 return;
348 #ifdef DEBUG_COOKIES
349 DBG("set_cookie -> (%s) %s", struri(uri), str);
350 #endif
352 if (!parse_cookie_str(&cstr, str)) return;
354 switch (parse_header_param(str, "path", &path)) {
355 unsigned char *path_end;
357 case HEADER_PARAM_FOUND:
358 if (!path[0]
359 || path[strlen(path) - 1] != '/')
360 add_to_strn(&path, "/");
362 if (path[0] != '/') {
363 add_to_strn(&path, "x");
364 memmove(path + 1, path, strlen(path) - 1);
365 path[0] = '/';
367 break;
369 case HEADER_PARAM_NOT_FOUND:
370 path = get_uri_string(uri, URI_PATH);
371 if (!path)
372 return;
374 path_end = strrchr(path, '/');
375 if (path_end)
376 path_end[1] = '\0';
377 break;
379 default: /* error */
380 return;
383 if (parse_header_param(str, "domain", &domain) == HEADER_PARAM_NOT_FOUND)
384 domain = memacpy(uri->host, uri->hostlen);
385 if (domain && domain[0] == '.')
386 memmove(domain, domain + 1, strlen(domain));
388 cookie = init_cookie(memacpy(str, cstr.nam_end - str),
389 memacpy(cstr.val_start, cstr.val_end - cstr.val_start),
390 path,
391 domain,
392 get_cookie_server(uri->host, uri->hostlen));
393 if (!cookie) return;
394 /* @cookie now owns @path and @domain. */
396 #if 0
397 /* We don't actually set ->accept at the moment. But I have kept it
398 * since it will maybe help to fix bug 77 - Support for more
399 * finegrained control upon accepting of cookies. */
400 if (!cookie->server->accept) {
401 #ifdef DEBUG_COOKIES
402 DBG("Dropped.");
403 #endif
404 done_cookie(cookie);
405 return;
407 #endif
409 /* Set cookie expiration if needed.
410 * Cookie expires at end of session by default,
411 * set to 0 by calloc().
413 * max_age:
414 * -1 is use cookie's expiration date if any
415 * 0 is force expiration at the end of session,
416 * ignoring cookie's expiration date
417 * 1+ is use cookie's expiration date,
418 * but limit age to the given number of days.
421 max_age = get_cookies_max_age();
422 if (max_age) {
423 unsigned char *date;
424 time_t expires;
426 switch (parse_header_param(str, "expires", &date)) {
427 case HEADER_PARAM_FOUND:
428 expires = parse_date(&date, NULL, 0, 1); /* Convert date to seconds. */
430 mem_free(date);
432 if (expires) {
433 if (max_age > 0) {
434 time_t seconds = ((time_t) max_age)*24*3600;
435 time_t deadline = time(NULL) + seconds;
437 if (expires > deadline) /* Over-aged cookie ? */
438 expires = deadline;
441 cookie->expires = expires;
443 break;
445 case HEADER_PARAM_NOT_FOUND:
446 break;
448 default: /* error */
449 done_cookie(cookie);
450 return;
454 cookie->secure = (parse_header_param(str, "secure", NULL)
455 == HEADER_PARAM_FOUND);
457 #ifdef DEBUG_COOKIES
459 DBG("Got cookie %s = %s from %s, domain %s, "
460 "expires at %"TIME_PRINT_FORMAT", secure %d", cookie->name,
461 cookie->value, cookie->server->host, cookie->domain,
462 (time_print_T) cookie->expires, cookie->secure);
464 #endif
466 if (!is_domain_security_ok(cookie->domain, uri->host, uri->hostlen)) {
467 #ifdef DEBUG_COOKIES
468 DBG("Domain security violated: %s vs %.*s", cookie->domain,
469 uri->hostlen, uri->host);
470 #endif
471 mem_free(cookie->domain);
472 cookie->domain = memacpy(uri->host, uri->hostlen);
475 /* We have already check COOKIES_ACCEPT_NONE */
476 if (get_cookies_accept_policy() == COOKIES_ACCEPT_ASK) {
477 add_to_list(cookie_queries, cookie);
478 add_questions_entry(accept_cookie_dialog, cookie);
479 return;
482 accept_cookie(cookie);
485 void
486 accept_cookie(struct cookie *cookie)
488 struct c_domain *cd;
489 struct listbox_item *root = cookie->server->box_item;
490 int domain_len;
492 if (root)
493 cookie->box_item = add_listbox_leaf(&cookie_browser, root, cookie);
495 /* Do not weed out duplicates when loading the cookie file. It doesn't
496 * scale at all, being O(N^2) and taking about 2s with my 500 cookies
497 * (so if you don't notice that 100ms with your 100 cookies, that's
498 * not an argument). --pasky */
499 if (!cookies_nosave) {
500 struct cookie *c, *next;
502 foreachsafe (c, next, cookies) {
503 if (c_strcasecmp(c->name, cookie->name)
504 || c_strcasecmp(c->domain, cookie->domain))
505 continue;
507 delete_cookie(c);
508 /* @set_cookies_dirty will be called below. */
512 add_to_list(cookies, cookie);
513 set_cookies_dirty();
515 /* XXX: This crunches CPU too. --pasky */
516 foreach (cd, c_domains)
517 if (!c_strcasecmp(cd->domain, cookie->domain))
518 return;
520 domain_len = strlen(cookie->domain);
521 /* One byte is reserved for domain in struct c_domain. */
522 cd = mem_alloc(sizeof(*cd) + domain_len);
523 if (!cd) return;
525 memcpy(cd->domain, cookie->domain, domain_len + 1);
526 add_to_list(c_domains, cd);
529 #if 0
530 static unsigned int cookie_id = 0;
532 static void
533 delete_cookie(struct cookie *c)
535 struct c_domain *cd;
536 struct cookie *d;
538 foreach (d, cookies)
539 if (!c_strcasecmp(d->domain, c->domain))
540 goto end;
542 foreach (cd, c_domains) {
543 if (!c_strcasecmp(cd->domain, c->domain)) {
544 del_from_list(cd);
545 mem_free(cd);
546 break;
550 end:
551 del_from_list(c);
552 done_cookie(c);
556 static struct
557 cookie *find_cookie_id(void *idp)
559 int id = (int) idp;
560 struct cookie *c;
562 foreach (c, cookies)
563 if (c->id == id)
564 return c;
566 return NULL;
570 static void
571 reject_cookie(void *idp)
573 struct cookie *c = find_cookie_id(idp);
575 if (!c) return;
577 delete_cookie(c);
578 set_cookies_dirty(); /* @find_cookie_id doesn't use @cookie_queries */
582 static void
583 cookie_default(void *idp, int a)
585 struct cookie *c = find_cookie_id(idp);
587 if (c) c->server->accept = a;
591 static void
592 accept_cookie_always(void *idp)
594 cookie_default(idp, 1);
598 static void
599 accept_cookie_never(void *idp)
601 cookie_default(idp, 0);
602 reject_cookie(idp);
604 #endif
607 static inline int
608 is_path_prefix(unsigned char *d, unsigned char *s)
610 int dl = strlen(d);
612 /* TODO: strlcmp()? --pasky */
614 if (dl > strlen(s)) return 0;
616 return !memcmp(d, s, dl);
620 struct string *
621 send_cookies(struct uri *uri)
623 struct c_domain *cd;
624 struct cookie *c, *next;
625 unsigned char *path = NULL;
626 static struct string header;
627 time_t now;
629 if (!uri->host || !uri->data)
630 return NULL;
632 foreach (cd, c_domains)
633 if (is_in_domain(cd->domain, uri->host, uri->hostlen)) {
634 path = get_uri_string(uri, URI_PATH);
635 break;
638 if (!path) return NULL;
640 init_string(&header);
642 now = time(NULL);
643 foreachsafe (c, next, cookies) {
644 if (!is_in_domain(c->domain, uri->host, uri->hostlen)
645 || !is_path_prefix(c->path, path))
646 continue;
648 if (c->expires && c->expires <= now) {
649 #ifdef DEBUG_COOKIES
650 DBG("Cookie %s=%s (exp %"TIME_PRINT_FORMAT") expired.",
651 c->name, c->value, (time_print_T) c->expires);
652 #endif
653 delete_cookie(c);
655 set_cookies_dirty();
656 continue;
659 /* Not sure if this is 100% right..? --pasky */
660 if (c->secure && uri->protocol != PROTOCOL_HTTPS)
661 continue;
663 if (header.length)
664 add_to_string(&header, "; ");
666 add_to_string(&header, c->name);
667 add_char_to_string(&header, '=');
668 add_to_string(&header, c->value);
669 #ifdef DEBUG_COOKIES
670 DBG("Cookie: %s=%s", c->name, c->value);
671 #endif
674 mem_free(path);
676 if (!header.length) {
677 done_string(&header);
678 return NULL;
681 return &header;
684 static void done_cookies(struct module *module);
687 void
688 load_cookies(void) {
689 /* Buffer size is set to be enough to read long lines that
690 * save_cookies may write. 6 is choosen after the fprintf(..) call
691 * in save_cookies(). --Zas */
692 unsigned char in_buffer[6 * MAX_STR_LEN];
693 unsigned char *cookfile = COOKIES_FILENAME;
694 FILE *fp;
695 time_t now;
697 if (elinks_home) {
698 cookfile = straconcat(elinks_home, cookfile,
699 (unsigned char *) NULL);
700 if (!cookfile) return;
703 /* Do it here, as we will delete whole cookies list if the file was
704 * removed */
705 cookies_nosave = 1;
706 done_cookies(&cookies_module);
707 cookies_nosave = 0;
709 fp = fopen(cookfile, "rb");
710 if (elinks_home) mem_free(cookfile);
711 if (!fp) return;
713 /* XXX: We don't want to overwrite the cookies file
714 * periodically to our death. */
715 cookies_nosave = 1;
717 now = time(NULL);
718 while (fgets(in_buffer, 6 * MAX_STR_LEN, fp)) {
719 struct cookie *cookie;
720 unsigned char *p, *q = in_buffer;
721 enum { NAME = 0, VALUE, SERVER, PATH, DOMAIN, EXPIRES, SECURE, MEMBERS } member;
722 struct {
723 unsigned char *pos;
724 int len;
725 } members[MEMBERS];
726 time_t expires;
728 /* First find all members. */
729 for (member = NAME; member < MEMBERS; member++, q = ++p) {
730 p = strchr(q, '\t');
731 if (!p) {
732 if (member + 1 != MEMBERS) break; /* last field ? */
733 p = strchr(q, '\n');
734 if (!p) break;
737 members[member].pos = q;
738 members[member].len = p - q;
741 if (member != MEMBERS) continue; /* Invalid line. */
743 /* Skip expired cookies if any. */
744 expires = str_to_time_t(members[EXPIRES].pos);
745 if (!expires || expires <= now) {
746 set_cookies_dirty();
747 continue;
750 /* Prepare cookie if all members and fields was read. */
751 cookie = mem_calloc(1, sizeof(*cookie));
752 if (!cookie) continue;
754 cookie->server = get_cookie_server(members[SERVER].pos, members[SERVER].len);
755 cookie->name = memacpy(members[NAME].pos, members[NAME].len);
756 cookie->value = memacpy(members[VALUE].pos, members[VALUE].len);
757 cookie->path = memacpy(members[PATH].pos, members[PATH].len);
758 cookie->domain = memacpy(members[DOMAIN].pos, members[DOMAIN].len);
760 /* Check whether all fields were correctly allocated. */
761 if (!cookie->server || !cookie->name || !cookie->value
762 || !cookie->path || !cookie->domain) {
763 done_cookie(cookie);
764 continue;
767 cookie->expires = expires;
768 cookie->secure = !!atoi(members[SECURE].pos);
770 accept_cookie(cookie);
773 cookies_nosave = 0;
774 fclose(fp);
777 static void
778 resave_cookies_bottom_half(void *always_null)
780 if (get_cookies_save() && get_cookies_resave())
781 save_cookies(NULL); /* checks cookies_dirty */
784 /* Note that the cookies have been modified, and register a bottom
785 * half for saving them if appropriate. We use a bottom half so that
786 * if something makes multiple changes and calls this for each change,
787 * the cookies get saved only once at the end. */
788 void
789 set_cookies_dirty(void)
791 /* Do not check @cookies_dirty here. If the previous attempt
792 * to save cookies failed, @cookies_dirty can still be nonzero
793 * even though @resave_cookies_bottom_half is no longer in the
794 * queue. */
795 cookies_dirty = 1;
796 /* If @resave_cookies_bottom_half is already in the queue,
797 * @register_bottom_half does nothing. */
798 register_bottom_half(resave_cookies_bottom_half, NULL);
801 /* @term is non-NULL if the user told ELinks to save cookies, or NULL
802 * if ELinks decided that on its own. In the former case, this
803 * function reports errors to @term, unless CONFIG_SMALL is defined.
804 * In the latter case, this function does not save the cookies if it
805 * thinks the file is already up to date. */
806 void
807 save_cookies(struct terminal *term) {
808 struct cookie *c;
809 unsigned char *cookfile;
810 struct secure_save_info *ssi;
811 time_t now;
813 #ifdef CONFIG_SMALL
814 # define CANNOT_SAVE_COOKIES(flags, message)
815 #else
816 # define CANNOT_SAVE_COOKIES(flags, message) \
817 do { \
818 if (term) \
819 info_box(term, flags, N_("Cannot save cookies"),\
820 ALIGN_LEFT, message); \
821 } while (0)
822 #endif
824 if (cookies_nosave) {
825 assert(term == NULL);
826 if_assert_failed {}
827 return;
829 if (!elinks_home) {
830 CANNOT_SAVE_COOKIES(0, N_("ELinks was started without a home directory."));
831 return;
833 if (!cookies_dirty && !term)
834 return;
835 if (get_cmd_opt_bool("anonymous")) {
836 CANNOT_SAVE_COOKIES(0, N_("ELinks was started with the -anonymous option."));
837 return;
840 cookfile = straconcat(elinks_home, COOKIES_FILENAME,
841 (unsigned char *) NULL);
842 if (!cookfile) {
843 CANNOT_SAVE_COOKIES(0, N_("Out of memory"));
844 return;
847 ssi = secure_open(cookfile);
848 mem_free(cookfile);
849 if (!ssi) {
850 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
851 secsave_strerror(secsave_errno, term));
852 return;
855 now = time(NULL);
856 foreach (c, cookies) {
857 if (!c->expires || c->expires <= now) continue;
858 if (secure_fprintf(ssi, "%s\t%s\t%s\t%s\t%s\t%"TIME_PRINT_FORMAT"\t%d\n",
859 c->name, c->value,
860 c->server->host,
861 empty_string_or_(c->path),
862 empty_string_or_(c->domain),
863 (time_print_T) c->expires, c->secure) < 0)
864 break;
867 secsave_errno = SS_ERR_OTHER; /* @secure_close doesn't always set it */
868 if (!secure_close(ssi)) cookies_dirty = 0;
869 else {
870 CANNOT_SAVE_COOKIES(MSGBOX_NO_TEXT_INTL,
871 secsave_strerror(secsave_errno, term));
873 #undef CANNOT_SAVE_COOKIES
876 static void
877 init_cookies(struct module *module)
879 if (get_cookies_save())
880 load_cookies();
883 /* Like @delete_cookie, this function does not set @cookies_dirty.
884 * The caller must do that if appropriate. */
885 static void
886 free_cookies_list(LIST_OF(struct cookie) *list)
888 while (!list_empty(*list)) {
889 struct cookie *cookie = list->next;
891 delete_cookie(cookie);
895 static void
896 done_cookies(struct module *module)
898 free_list(c_domains);
900 if (!cookies_nosave && get_cookies_save())
901 save_cookies(NULL);
903 free_cookies_list(&cookies);
904 free_cookies_list(&cookie_queries);
905 /* If @save_cookies failed above, @cookies_dirty can still be
906 * nonzero. Now if @resave_cookies_bottom_half were in the
907 * queue, it could save the empty @cookies list to the file.
908 * Prevent that. */
909 cookies_dirty = 0;
912 struct module cookies_module = struct_module(
913 /* name: */ N_("Cookies"),
914 /* options: */ cookies_options,
915 /* events: */ NULL,
916 /* submodules: */ NULL,
917 /* data: */ NULL,
918 /* init: */ init_cookies,
919 /* done: */ done_cookies