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 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.")),
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
);
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
, NULL
);
105 f
= fopen(file
, "rb");
109 while (fgets(tmp
, MAX_STR_LEN
, f
)) {
113 if (tmp
[0] == '\n' && !tmp
[1]) continue;
115 p
= strchr(tmp
, '\t');
119 if (!strcmp(tmp
, "dontsave"))
122 /* Compat. with older file formats. Remove it at some
124 if (!strncmp(tmp
, "dontsave,", 9)) {
133 p
[strlen(p
) - 1] = '\0';
135 form
= new_formhist_item(p
);
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
;
147 if (tmp
[0] == '\n' && !tmp
[1]) break;
151 p
= strchr(type
, '\t');
157 p
= strchr(name
, '\t');
159 /* Compatibility with previous file formats.
160 * REMOVE AT SOME TIME --Zas */
178 p
= strchr(value
, '\n');
182 ret
= str2form_type(type
);
183 if (ret
== -1) goto fail
;
186 if (form
->dontsave
) continue;
188 enc_value
= *value
? base64_decode(value
)
190 if (!enc_value
) goto fail
;
192 sv
= init_submitted_value(name
, enc_value
,
198 add_to_list(*form
->submit
, sv
);
201 add_to_list(saved_forms
, form
);
210 done_formhist_item(form
);
215 save_formhist_to_file(void)
217 struct secure_save_info
*ssi
;
219 struct formhist_data
*form
;
222 if (!elinks_home
|| get_cmd_opt_bool("anonymous"))
225 file
= straconcat(elinks_home
, FORMS_HISTORY_FILENAME
, NULL
);
228 ssi
= secure_open(file
);
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
);
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
);
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
),
265 secure_fputc(ssi
, '\n');
268 r
= secure_close(ssi
);
269 if (r
== 0) loaded
= 1;
274 /* Check whether the form (chain of @submit submitted_values at @url document)
275 * is already present in the form history. */
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
) {
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
;
297 foreach (sv2
, *form
->submit
) {
298 if (sv
->type
!= sv2
->type
) continue;
299 if (!strcmp(sv
->name
, sv2
->name
)) {
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;
318 forget_forms_with_url(unsigned char *url
)
320 struct formhist_data
*form
, *next
;
323 foreachsafe (form
, next
, saved_forms
) {
324 if (strcmp(form
->url
, url
)) continue;
326 delete_formhist_item(form
);
333 /* Appends form data @form1 (url and submitted_value(s)) to the password file.
334 * Returns 1 on success, 0 otherwise. */
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();
345 never_for_this_site(struct formhist_data
*form
)
348 return remember_form(form
);
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
))
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
;
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
) {
394 /* Create a temporary form. */
395 form
= new_formhist_item(forminfo
->action
);
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
,
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."),
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
);
426 done_formhist_item(form
);
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
,
443 /* submodules: */ NULL
,
446 /* done: */ done_form_history