bug 153, 1066: Convert bookmarks to/from UTF-8 when searching.
[elinks.git] / src / formhist / formhist.c
blob621533ac9716e48de01d9767cac8d2434b2dd75a
1 /* Implementation of a login manager for HTML forms */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <string.h>
9 #include "elinks.h"
11 #include "bfu/dialog.h"
12 #include "config/home.h"
13 #include "document/forms.h"
14 #include "formhist/dialogs.h"
15 #include "formhist/formhist.h"
16 #include "intl/gettext/libintl.h"
17 #include "main/module.h"
18 #include "main/object.h"
19 #include "session/session.h"
20 #include "terminal/window.h"
21 #include "util/base64.h"
22 #include "util/file.h"
23 #include "util/lists.h"
24 #include "util/secsave.h"
25 #include "util/string.h"
26 #include "viewer/text/form.h"
28 #define FORMS_HISTORY_FILENAME "formhist"
31 /* TODO: Remember multiple login for the same form.
32 * TODO: Password manager GUI (here?) (in dialogs.c, of course --pasky). */
35 static struct option_info forms_history_options[] = {
36 INIT_OPT_BOOL("document.browse.forms", N_("Show form history dialog"),
37 "show_formhist", 0, 0,
38 N_("Ask if a login form should be saved to file or not.\n"
39 "This option only disables the dialog, already saved login\n"
40 "forms are unaffected.")),
42 NULL_OPTION_INFO,
45 INIT_LIST_OF(struct formhist_data, saved_forms);
47 static struct formhist_data *
48 new_formhist_item(unsigned char *url)
50 struct formhist_data *form;
51 int url_len = strlen(url);
53 form = mem_calloc(1, sizeof(*form) + url_len);
54 if (!form) return NULL;
56 memcpy(form->url, url, url_len);
57 form->submit = mem_alloc(sizeof(*form->submit));
58 if (!form->submit) { mem_free(form); return NULL; }
60 object_nolock(form, "formhist");
61 init_list(*form->submit);
62 form->box_item = add_listbox_leaf(&formhist_browser, NULL, form);
63 if (!form->box_item) {
64 mem_free(form->submit);
65 mem_free(form);
66 return NULL;
69 return form;
72 static void
73 done_formhist_item(struct formhist_data *form)
75 done_listbox_item(&formhist_browser, form->box_item);
76 done_submitted_value_list(form->submit);
77 mem_free(form->submit);
78 mem_free(form);
81 void
82 delete_formhist_item(struct formhist_data *form)
84 del_from_list(form);
85 done_formhist_item(form);
88 static int loaded = 0;
90 int
91 load_formhist_from_file(void)
93 struct formhist_data *form;
94 unsigned char tmp[MAX_STR_LEN];
95 unsigned char *file;
96 FILE *f;
98 if (loaded) return 1;
100 if (!elinks_home) return 0;
102 file = straconcat(elinks_home, FORMS_HISTORY_FILENAME,
103 (unsigned char *) NULL);
104 if (!file) return 0;
106 f = fopen(file, "rb");
107 mem_free(file);
108 if (!f) return 0;
110 while (fgets(tmp, MAX_STR_LEN, f)) {
111 unsigned char *p;
112 int dontsave = 0;
114 if (tmp[0] == '\n' && !tmp[1]) continue;
116 p = strchr(tmp, '\t');
117 if (p) {
118 *p = '\0';
119 ++p;
120 if (!strcmp(tmp, "dontsave"))
121 dontsave = 1;
122 } else {
123 /* Compat. with older file formats. Remove it at some
124 * time. --Zas */
125 if (!strncmp(tmp, "dontsave,", 9)) {
126 dontsave = 1;
127 p = tmp + 9;
128 } else {
129 p = tmp;
133 /* URL */
134 p[strlen(p) - 1] = '\0';
136 form = new_formhist_item(p);
137 if (!form) continue;
138 if (dontsave) form->dontsave = 1;
140 /* Fields type, name, value */
141 while (fgets(tmp, MAX_STR_LEN, f)) {
142 struct submitted_value *sv;
143 unsigned char *type, *name, *value;
144 unsigned char *enc_value;
145 enum form_type ftype;
146 int ret;
148 if (tmp[0] == '\n' && !tmp[1]) break;
150 /* Type */
151 type = tmp;
152 p = strchr(type, '\t');
153 if (!p) goto fail;
154 *p = '\0';
156 /* Name */
157 name = ++p;
158 p = strchr(name, '\t');
159 if (!p) {
160 /* Compatibility with previous file formats.
161 * REMOVE AT SOME TIME --Zas */
162 value = name;
163 name = type;
165 if (*name == '*') {
166 name++;
167 type = "password";
168 } else {
169 type = "text";
172 goto cont;
174 *p = '\0';
176 /* Value */
177 value = ++p;
178 cont:
179 p = strchr(value, '\n');
180 if (!p) goto fail;
181 *p = '\0';
183 ret = str2form_type(type);
184 if (ret == -1) goto fail;
185 ftype = ret;
187 if (form->dontsave) continue;
189 enc_value = *value ? base64_decode(value)
190 : stracpy(value);
191 if (!enc_value) goto fail;
193 sv = init_submitted_value(name, enc_value,
194 ftype, NULL, 0);
196 mem_free(enc_value);
197 if (!sv) goto fail;
199 add_to_list(*form->submit, sv);
202 add_to_list(saved_forms, form);
205 fclose(f);
206 loaded = 1;
208 return 1;
210 fail:
211 done_formhist_item(form);
212 return 0;
216 save_formhist_to_file(void)
218 struct secure_save_info *ssi;
219 unsigned char *file;
220 struct formhist_data *form;
221 int r;
223 if (!elinks_home || get_cmd_opt_bool("anonymous"))
224 return 0;
226 file = straconcat(elinks_home, FORMS_HISTORY_FILENAME,
227 (unsigned char *) NULL);
228 if (!file) return 0;
230 ssi = secure_open(file);
231 mem_free(file);
232 if (!ssi) return 0;
234 /* Write the list to password file ($ELINKS_HOME/formhist) */
236 foreach (form, saved_forms) {
237 struct submitted_value *sv;
239 if (form->dontsave) {
240 secure_fprintf(ssi, "dontsave\t%s\n\n", form->url);
241 continue;
244 secure_fprintf(ssi, "%s\n", form->url);
246 foreach (sv, *form->submit) {
247 unsigned char *encvalue;
249 if (sv->value && *sv->value) {
250 /* Obfuscate the value. If we do
251 * $ cat ~/.elinks/formhist
252 * we don't want someone behind our back to read our
253 * password (androids don't count). */
254 encvalue = base64_encode(sv->value);
255 } else {
256 encvalue = stracpy("");
259 if (!encvalue) return 0;
260 /* Format is : type[TAB]name[TAB]value[CR] */
261 secure_fprintf(ssi, "%s\t%s\t%s\n", form_type2str(sv->type),
262 sv->name, encvalue);
264 mem_free(encvalue);
267 secure_fputc(ssi, '\n');
270 r = secure_close(ssi);
271 if (r == 0) loaded = 1;
273 return r;
276 /* Check whether the form (chain of @submit submitted_values at @url document)
277 * is already present in the form history. */
278 static int
279 form_exists(struct formhist_data *form1)
281 struct formhist_data *form;
283 if (!load_formhist_from_file()) return 0;
285 foreach (form, saved_forms) {
286 int count = 0;
287 int exact = 0;
288 struct submitted_value *sv;
290 if (strcmp(form->url, form1->url)) continue;
291 if (form->dontsave) return 1;
293 /* Iterate through submitted entries. */
294 foreach (sv, *form1->submit) {
295 struct submitted_value *sv2;
296 unsigned char *value = NULL;
298 count++;
299 foreach (sv2, *form->submit) {
300 if (sv->type != sv2->type) continue;
301 if (!strcmp(sv->name, sv2->name)) {
302 exact++;
303 value = sv2->value;
304 break;
307 /* If we found a value for that name, check if value
308 * has changed or not. */
309 if (value && strcmp(sv->value, value)) return 0;
312 /* Check if submitted values have changed or not. */
313 if (count && exact && count == exact) return 1;
316 return 0;
319 static int
320 forget_forms_with_url(unsigned char *url)
322 struct formhist_data *form, *next;
323 int count = 0;
325 foreachsafe (form, next, saved_forms) {
326 if (strcmp(form->url, url)) continue;
328 delete_formhist_item(form);
329 count++;
332 return count;
335 /* Appends form data @form_ (url and submitted_value(s)) to the password file. */
336 static void
337 remember_form(void *form_)
339 struct formhist_data *form = form_;
341 forget_forms_with_url(form->url);
342 add_to_list(saved_forms, form);
344 save_formhist_to_file();
347 static void
348 dont_remember_form(void *form_)
350 struct formhist_data *form = form_;
352 done_formhist_item(form);
355 static void
356 never_for_this_site(void *form_)
358 struct formhist_data *form = form_;
360 form->dontsave = 1;
361 remember_form(form);
364 unsigned char *
365 get_form_history_value(unsigned char *url, unsigned char *name)
367 struct formhist_data *form;
369 if (!url || !*url || !name || !*name) return NULL;
371 if (!load_formhist_from_file()) return NULL;
373 foreach (form, saved_forms) {
374 if (form->dontsave) continue;
376 if (!strcmp(form->url, url)) {
377 struct submitted_value *sv;
379 foreach (sv, *form->submit)
380 if (!strcmp(sv->name, name))
381 return sv->value;
385 return NULL;
388 void
389 memorize_form(struct session *ses, LIST_OF(struct submitted_value) *submit,
390 struct form *forminfo)
392 /* [gettext_accelerator_context(memorize_form)] */
393 struct formhist_data *form;
394 struct submitted_value *sv;
395 int save = 0;
397 /* XXX: For now, we only save these types of form fields. */
398 foreach (sv, *submit) {
399 if (sv->type == FC_PASSWORD && sv->value && *sv->value) {
400 save = 1;
401 break;
405 if (!save) return;
407 /* Create a temporary form. */
408 form = new_formhist_item(forminfo->action);
409 if (!form) return;
411 foreach (sv, *submit) {
412 if ((sv->type == FC_TEXT) || (sv->type == FC_PASSWORD)) {
413 struct submitted_value *sv2;
415 sv2 = init_submitted_value(sv->name, sv->value,
416 sv->type, NULL, 0);
417 if (!sv2) goto fail;
419 add_to_list(*form->submit, sv2);
423 if (form_exists(form)) goto fail;
425 msg_box(ses->tab->term, NULL, 0,
426 N_("Form history"), ALIGN_CENTER,
427 N_("Should this login be remembered?\n\n"
428 "Please note that the password will be stored "
429 "obscured (but unencrypted) in a file on your disk.\n\n"
430 "If you are using a valuable password, answer NO."),
431 form, 3,
432 MSG_BOX_BUTTON(N_("~Yes"), remember_form, B_ENTER),
433 MSG_BOX_BUTTON(N_("~No"), dont_remember_form, B_ESC),
434 MSG_BOX_BUTTON(N_("Ne~ver for this site"), never_for_this_site, 0));
436 return;
438 fail:
439 done_formhist_item(form);
442 static void
443 done_form_history(struct module *module)
445 struct formhist_data *form, *next;
447 foreachsafe (form, next, saved_forms) {
448 delete_formhist_item(form);
452 struct module forms_history_module = struct_module(
453 /* name: */ N_("Form History"),
454 /* options: */ forms_history_options,
455 /* events: */ NULL,
456 /* submodules: */ NULL,
457 /* data: */ NULL,
458 /* init: */ NULL,
459 /* done: */ done_form_history