1 /* Implementation of a login manager for HTML forms */
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 union 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. "
39 "This option only disables the dialog, already saved login "
40 "forms are unaffected.")),
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
);
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
);
82 delete_formhist_item(struct formhist_data
*form
)
85 done_formhist_item(form
);
88 static int loaded
= 0;
91 load_formhist_from_file(void)
93 struct formhist_data
*form
;
94 unsigned char tmp
[MAX_STR_LEN
];
100 if (!elinks_home
) return 0;
102 file
= straconcat(elinks_home
, FORMS_HISTORY_FILENAME
,
103 (unsigned char *) NULL
);
106 f
= fopen(file
, "rb");
110 while (fgets(tmp
, MAX_STR_LEN
, f
)) {
114 if (tmp
[0] == '\n' && !tmp
[1]) continue;
116 p
= strchr(tmp
, '\t');
120 if (!strcmp(tmp
, "dontsave"))
123 /* Compat. with older file formats. Remove it at some
125 if (!strncmp(tmp
, "dontsave,", 9)) {
134 p
[strlen(p
) - 1] = '\0';
136 form
= new_formhist_item(p
);
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
;
148 if (tmp
[0] == '\n' && !tmp
[1]) break;
152 p
= strchr(type
, '\t');
158 p
= strchr(name
, '\t');
160 /* Compatibility with previous file formats.
161 * REMOVE AT SOME TIME --Zas */
179 p
= strchr(value
, '\n');
183 ret
= str2form_type(type
);
184 if (ret
== -1) goto fail
;
187 if (form
->dontsave
) continue;
189 enc_value
= *value
? base64_decode(value
)
191 if (!enc_value
) goto fail
;
193 sv
= init_submitted_value(name
, enc_value
,
199 add_to_list(*form
->submit
, sv
);
202 add_to_list(saved_forms
, form
);
211 done_formhist_item(form
);
216 save_formhist_to_file(void)
218 struct secure_save_info
*ssi
;
220 struct formhist_data
*form
;
223 if (!elinks_home
|| get_cmd_opt_bool("anonymous"))
226 file
= straconcat(elinks_home
, FORMS_HISTORY_FILENAME
,
227 (unsigned char *) NULL
);
230 ssi
= secure_open(file
);
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
);
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
);
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
),
267 secure_fputc(ssi
, '\n');
270 r
= secure_close(ssi
);
271 if (r
== 0) loaded
= 1;
276 /* Check whether the form (chain of @submit submitted_values at @url document)
277 * is already present in the form history. */
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
) {
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
;
299 foreach (sv2
, *form
->submit
) {
300 if (sv
->type
!= sv2
->type
) continue;
301 if (!strcmp(sv
->name
, sv2
->name
)) {
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;
320 forget_forms_with_url(unsigned char *url
)
322 struct formhist_data
*form
, *next
;
325 foreachsafe (form
, next
, saved_forms
) {
326 if (strcmp(form
->url
, url
)) continue;
328 delete_formhist_item(form
);
335 /* Appends form data @form_ (url and submitted_value(s)) to the password file. */
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();
348 dont_remember_form(void *form_
)
350 struct formhist_data
*form
= form_
;
352 done_formhist_item(form
);
356 never_for_this_site(void *form_
)
358 struct formhist_data
*form
= form_
;
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
))
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
;
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
) {
407 /* Create a temporary form. */
408 form
= new_formhist_item(forminfo
->action
);
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
,
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."),
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));
439 done_formhist_item(form
);
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
,
456 /* submodules: */ NULL
,
459 /* done: */ done_form_history