Use @current_tab for current tab.
[elinks.git] / src / formhist / formhist.c
blobc1227f0abab7c6bbb5b35138ff3a22022355643f
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_HEAD(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, NULL);
103 if (!file) return 0;
105 f = fopen(file, "rb");
106 mem_free(file);
107 if (!f) return 0;
109 while (fgets(tmp, MAX_STR_LEN, f)) {
110 unsigned char *p;
111 int dontsave = 0;
113 if (tmp[0] == '\n' && !tmp[1]) continue;
115 p = strchr(tmp, '\t');
116 if (p) {
117 *p = '\0';
118 ++p;
119 if (!strcmp(tmp, "dontsave"))
120 dontsave = 1;
121 } else {
122 /* Compat. with older file formats. Remove it at some
123 * time. --Zas */
124 if (!strncmp(tmp, "dontsave,", 9)) {
125 dontsave = 1;
126 p = tmp + 9;
127 } else {
128 p = tmp;
132 /* URL */
133 p[strlen(p) - 1] = '\0';
135 form = new_formhist_item(p);
136 if (!form) continue;
137 if (dontsave) form->dontsave = 1;
139 /* Fields type, name, value */
140 while (fgets(tmp, MAX_STR_LEN, f)) {
141 struct submitted_value *sv;
142 unsigned char *type, *name, *value;
143 unsigned char *enc_value;
144 enum form_type ftype;
145 int ret;
147 if (tmp[0] == '\n' && !tmp[1]) break;
149 /* Type */
150 type = tmp;
151 p = strchr(type, '\t');
152 if (!p) goto fail;
153 *p = '\0';
155 /* Name */
156 name = ++p;
157 p = strchr(name, '\t');
158 if (!p) {
159 /* Compatibility with previous file formats.
160 * REMOVE AT SOME TIME --Zas */
161 value = name;
162 name = type;
164 if (*name == '*') {
165 name++;
166 type = "password";
167 } else {
168 type = "text";
171 goto cont;
173 *p = '\0';
175 /* Value */
176 value = ++p;
177 cont:
178 p = strchr(value, '\n');
179 if (!p) goto fail;
180 *p = '\0';
182 ret = str2form_type(type);
183 if (ret == -1) goto fail;
184 ftype = ret;
186 if (form->dontsave) continue;
188 enc_value = *value ? base64_decode(value)
189 : stracpy(value);
190 if (!enc_value) goto fail;
192 sv = init_submitted_value(name, enc_value,
193 ftype, NULL, 0);
195 mem_free(enc_value);
196 if (!sv) goto fail;
198 add_to_list(*form->submit, sv);
201 add_to_list(saved_forms, form);
204 fclose(f);
205 loaded = 1;
207 return 1;
209 fail:
210 done_formhist_item(form);
211 return 0;
215 save_formhist_to_file(void)
217 struct secure_save_info *ssi;
218 unsigned char *file;
219 struct formhist_data *form;
220 int r;
222 if (!elinks_home || get_cmd_opt_bool("anonymous"))
223 return 0;
225 file = straconcat(elinks_home, FORMS_HISTORY_FILENAME, NULL);
226 if (!file) return 0;
228 ssi = secure_open(file);
229 mem_free(file);
230 if (!ssi) return 0;
232 /* Write the list to password file ($ELINKS_HOME/formhist) */
234 foreach (form, saved_forms) {
235 struct submitted_value *sv;
237 if (form->dontsave) {
238 secure_fprintf(ssi, "dontsave\t%s\n\n", form->url);
239 continue;
242 secure_fprintf(ssi, "%s\n", form->url);
244 foreach (sv, *form->submit) {
245 unsigned char *encvalue;
247 if (sv->value && *sv->value) {
248 /* Obfuscate the value. If we do
249 * $ cat ~/.elinks/formhist
250 * we don't want someone behind our back to read our
251 * password (androids don't count). */
252 encvalue = base64_encode(sv->value);
253 } else {
254 encvalue = stracpy("");
257 if (!encvalue) return 0;
258 /* Format is : type[TAB]name[TAB]value[CR] */
259 secure_fprintf(ssi, "%s\t%s\t%s\n", form_type2str(sv->type),
260 sv->name, encvalue);
262 mem_free(encvalue);
265 secure_fputc(ssi, '\n');
268 r = secure_close(ssi);
269 if (r == 0) loaded = 1;
271 return r;
274 /* Check whether the form (chain of @submit submitted_values at @url document)
275 * is already present in the form history. */
276 static int
277 form_exists(struct formhist_data *form1)
279 struct formhist_data *form;
281 if (!load_formhist_from_file()) return 0;
283 foreach (form, saved_forms) {
284 int count = 0;
285 int exact = 0;
286 struct submitted_value *sv;
288 if (strcmp(form->url, form1->url)) continue;
289 if (form->dontsave) return 1;
291 /* Iterate through submitted entries. */
292 foreach (sv, *form1->submit) {
293 struct submitted_value *sv2;
294 unsigned char *value = NULL;
296 count++;
297 foreach (sv2, *form->submit) {
298 if (sv->type != sv2->type) continue;
299 if (!strcmp(sv->name, sv2->name)) {
300 exact++;
301 value = sv2->value;
302 break;
305 /* If we found a value for that name, check if value
306 * has changed or not. */
307 if (value && strcmp(sv->value, value)) return 0;
310 /* Check if submitted values have changed or not. */
311 if (count && exact && count == exact) return 1;
314 return 0;
317 static int
318 forget_forms_with_url(unsigned char *url)
320 struct formhist_data *form, *next;
321 int count = 0;
323 foreachsafe (form, next, saved_forms) {
324 if (strcmp(form->url, url)) continue;
326 delete_formhist_item(form);
327 count++;
330 return count;
333 /* Appends form data @form1 (url and submitted_value(s)) to the password file.
334 * Returns 1 on success, 0 otherwise. */
335 static int
336 remember_form(struct formhist_data *form)
338 forget_forms_with_url(form->url);
339 add_to_list(saved_forms, form);
341 return save_formhist_to_file();
344 static int
345 never_for_this_site(struct formhist_data *form)
347 form->dontsave = 1;
348 return remember_form(form);
351 unsigned char *
352 get_form_history_value(unsigned char *url, unsigned char *name)
354 struct formhist_data *form;
356 if (!url || !*url || !name || !*name) return NULL;
358 if (!load_formhist_from_file()) return NULL;
360 foreach (form, saved_forms) {
361 if (form->dontsave) continue;
363 if (!strcmp(form->url, url)) {
364 struct submitted_value *sv;
366 foreach (sv, *form->submit)
367 if (!strcmp(sv->name, name))
368 return sv->value;
372 return NULL;
375 void
376 memorize_form(struct session *ses, struct list_head *submit,
377 struct form *forminfo)
379 /* [gettext_accelerator_context(memorize_form)] */
380 struct formhist_data *form;
381 struct submitted_value *sv;
382 int save = 0;
384 /* XXX: For now, we only save these types of form fields. */
385 foreach (sv, *submit) {
386 if (sv->type == FC_PASSWORD && sv->value && *sv->value) {
387 save = 1;
388 break;
392 if (!save) return;
394 /* Create a temporary form. */
395 form = new_formhist_item(forminfo->action);
396 if (!form) return;
398 foreach (sv, *submit) {
399 if ((sv->type == FC_TEXT) || (sv->type == FC_PASSWORD)) {
400 struct submitted_value *sv2;
402 sv2 = init_submitted_value(sv->name, sv->value,
403 sv->type, NULL, 0);
404 if (!sv2) goto fail;
406 add_to_list(*form->submit, sv2);
410 if (form_exists(form)) goto fail;
412 msg_box(ses->tab->term, NULL, 0,
413 N_("Form history"), ALIGN_CENTER,
414 N_("Should this login be remembered?\n\n"
415 "Please note that the password will be stored "
416 "obscured (but unencrypted) in a file on your disk.\n\n"
417 "If you are using a valuable password, answer NO."),
418 form, 3,
419 N_("~Yes"), remember_form, B_ENTER,
420 N_("~No"), done_formhist_item, B_ESC,
421 N_("Ne~ver for this site"), never_for_this_site, NULL);
423 return;
425 fail:
426 done_formhist_item(form);
429 static void
430 done_form_history(struct module *module)
432 struct formhist_data *form, *next;
434 foreachsafe (form, next, saved_forms) {
435 delete_formhist_item(form);
439 struct module forms_history_module = struct_module(
440 /* name: */ N_("Form History"),
441 /* options: */ forms_history_options,
442 /* events: */ NULL,
443 /* submodules: */ NULL,
444 /* data: */ NULL,
445 /* init: */ NULL,
446 /* done: */ done_form_history