Add get_terminal_codepage().
[elinks.git] / src / viewer / text / form.c
blob775e1d04f65e11e35be793de7277fd8bb6c0ec37
1 /** Forms viewing/manipulation handling
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #ifndef _GNU_SOURCE
9 #define _GNU_SOURCE /* XXX: we want memrchr() ! */
10 #endif
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #ifdef HAVE_FCNTL_H
21 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
22 #endif
24 #include "elinks.h"
26 #include "bfu/listmenu.h"
27 #include "bfu/dialog.h"
28 #include "config/kbdbind.h"
29 #include "dialogs/menu.h"
30 #include "document/document.h"
31 #include "document/forms.h"
32 #include "document/html/parser.h"
33 #include "document/view.h"
34 #include "ecmascript/ecmascript.h"
35 #include "intl/gettext/libintl.h"
36 #include "formhist/formhist.h"
37 #include "mime/mime.h"
38 #include "osdep/ascii.h"
39 #include "osdep/osdep.h"
40 #include "protocol/uri.h"
41 #include "session/session.h"
42 #include "session/task.h"
43 #include "terminal/kbd.h"
44 #include "terminal/terminal.h"
45 #include "terminal/window.h"
46 #include "util/conv.h"
47 #include "util/error.h"
48 #include "util/file.h"
49 #include "util/memory.h"
50 #include "util/string.h"
51 #include "viewer/action.h"
52 #include "viewer/text/draw.h"
53 #include "viewer/text/form.h"
54 #include "viewer/text/link.h"
55 #include "viewer/text/textarea.h"
56 #include "viewer/text/view.h"
57 #include "viewer/text/vs.h"
60 /* TODO: Some of these (particulary those encoding routines) would feel better
61 * in viewer/common/. --pasky */
63 /** @relates submitted_value */
64 struct submitted_value *
65 init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
66 struct form_control *fc, int position)
68 struct submitted_value *sv;
70 sv = mem_alloc(sizeof(*sv));
71 if (!sv) return NULL;
73 sv->value = stracpy(value);
74 if (!sv->value) { mem_free(sv); return NULL; }
76 sv->name = stracpy(name);
77 if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
79 sv->type = type;
80 sv->form_control = fc;
81 sv->position = position;
83 return sv;
86 /** @relates submitted_value */
87 void
88 done_submitted_value(struct submitted_value *sv)
90 if (!sv) return;
91 mem_free_if(sv->value);
92 mem_free_if(sv->name);
93 mem_free(sv);
96 static void
97 fixup_select_state(struct form_control *fc, struct form_state *fs)
99 int i;
101 assert(fc && fs);
102 if_assert_failed return;
104 if (fs->state >= 0
105 && fs->state < fc->nvalues
106 && !strcmp(fc->values[fs->state], fs->value))
107 return;
109 for (i = 0; i < fc->nvalues; i++)
110 if (!strcmp(fc->values[i], fs->value)) {
111 fs->state = i;
112 return;
115 fs->state = 0;
117 mem_free_set(&fs->value, stracpy(fc->nvalues
118 ? fc->values[0]
119 : (unsigned char *) ""));
122 /* menu_func_T */
123 void
124 selected_item(struct terminal *term, void *item_, void *ses_)
126 struct session *ses = ses_;
127 int item = (long) item_;
128 struct document_view *doc_view;
129 struct link *link;
130 struct form_state *fs;
131 struct form_control *fc;
133 assert(term && ses);
134 if_assert_failed return;
135 doc_view = current_frame(ses);
137 assert(doc_view && doc_view->vs && doc_view->document);
138 if_assert_failed return;
140 link = get_current_link(doc_view);
141 if (!link || link->type != LINK_SELECT) return;
143 fc = get_link_form_control(link);
144 fs = find_form_state(doc_view, fc);
145 if (fs) {
146 if (item >= 0 && item < fc->nvalues) {
147 fs->state = item;
148 mem_free_set(&fs->value, stracpy(fc->values[item]));
150 fixup_select_state(fc, fs);
153 refresh_view(ses, doc_view, 0);
156 static void
157 init_form_state(struct document_view *doc_view,
158 struct form_control *fc, struct form_state *fs)
160 struct terminal *term;
161 int doc_cp, viewer_cp;
163 assert(fc && fs);
164 if_assert_failed return;
166 doc_cp = doc_view->document->cp;
167 term = doc_view->session->tab->term;
168 viewer_cp = get_terminal_codepage(term);
170 mem_free_set(&fs->value, NULL);
172 switch (fc->type) {
173 case FC_TEXT:
174 case FC_PASSWORD:
175 #ifdef CONFIG_FORMHIST
176 fs->value = null_or_stracpy(
177 get_form_history_value(
178 fc->form->action, fc->name));
179 #endif /* CONFIG_FORMHIST */
180 /* fall through */
181 case FC_TEXTAREA:
182 if (fs->value == NULL) {
183 fs->value = convert_string(
184 get_translation_table(doc_cp, viewer_cp),
185 fc->default_value,
186 strlen(fc->default_value),
187 viewer_cp, CSM_FORM,
188 &fs->state, NULL, NULL);
190 fs->state = fs->value ? strlen(fs->value) : 0;
191 #ifdef CONFIG_UTF8
192 if (fc->type == FC_TEXTAREA)
193 fs->state_cell = 0;
194 #endif /* CONFIG_UTF8 */
195 fs->vpos = 0;
196 break;
197 case FC_FILE:
198 fs->value = stracpy("");
199 fs->state = 0;
200 fs->vpos = 0;
201 break;
202 case FC_SELECT:
203 fs->value = convert_string(
204 get_translation_table(doc_cp, viewer_cp),
205 fc->default_value,
206 strlen(fc->default_value),
207 viewer_cp, CSM_FORM,
208 &fs->state, NULL, NULL);
209 fs->state = fc->default_state;
210 fixup_select_state(fc, fs);
211 break;
212 case FC_CHECKBOX:
213 case FC_RADIO:
214 fs->state = fc->default_state;
215 /* Fall-through */
216 case FC_SUBMIT:
217 case FC_IMAGE:
218 case FC_RESET:
219 case FC_BUTTON:
220 case FC_HIDDEN:
221 /* We don't want to recode hidden fields. */
222 fs->value = stracpy(fc->default_value);
223 break;
228 struct form_state *
229 find_form_state(struct document_view *doc_view, struct form_control *fc)
231 struct view_state *vs;
232 struct form_state *fs;
233 int n;
235 assert(doc_view && doc_view->vs && fc);
236 if_assert_failed return NULL;
238 vs = doc_view->vs;
239 n = fc->g_ctrl_num;
241 if (n >= vs->form_info_len) {
242 int nn = n + 1;
243 #ifdef CONFIG_ECMASCRIPT
244 const struct form_state *const old_form_info = vs->form_info;
245 #endif
247 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
248 if (!fs) return NULL;
249 vs->form_info = fs;
250 vs->form_info_len = nn;
252 #ifdef CONFIG_ECMASCRIPT
253 /* TODO: Standard C does not allow this comparison;
254 * if the memory to which old_form_info pointed has
255 * been freed, then the value of the pointer itself is
256 * indeterminate. Fixing this would require changing
257 * mem_align_alloc to tell the caller whether it did
258 * realloc or not. */
259 if (vs->form_info != old_form_info) {
260 /* vs->form_info[] was moved to a different address.
261 * Update all the ECMAScript objects that have
262 * pointers to its elements. */
263 for (nn = 0; nn < vs->form_info_len; nn++)
264 ecmascript_moved_form_state(&vs->form_info[nn]);
266 #endif /* CONFIG_ECMASCRIPT */
268 fs = &vs->form_info[n];
270 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
271 && fs->g_ctrl_num == fc->g_ctrl_num
272 && fs->position == fc->position
273 && fs->type == fc->type)
274 return fs;
276 mem_free_if(fs->value);
277 memset(fs, 0, sizeof(*fs));
278 fs->form_view = find_form_view(doc_view, fc->form);
279 fs->g_ctrl_num = fc->g_ctrl_num;
280 fs->position = fc->position;
281 fs->type = fc->type;
282 init_form_state(doc_view, fc, fs);
284 return fs;
287 struct form_control *
288 find_form_control(struct document *document, struct form_state *fs)
290 struct form *form = find_form_by_form_view(document, fs->form_view);
291 struct form_control *fc;
293 foreach (fc, form->items) {
294 if (fs->g_ctrl_num == fc->g_ctrl_num
295 && fs->position == fc->position
296 && fs->type == fc->type)
297 return fc;
300 return NULL;
303 struct form_view *
304 find_form_view_in_vs(struct view_state *vs, int form_num)
306 struct form_view *fv;
308 assert(vs);
310 foreach (fv, vs->forms)
311 if (fv->form_num == form_num)
312 return fv;
314 fv = mem_calloc(1, sizeof(*fv));
315 fv->form_num = form_num;
316 add_to_list(vs->forms, fv);
317 return fv;
320 struct form_view *
321 find_form_view(struct document_view *doc_view, struct form *form)
323 return find_form_view_in_vs(doc_view->vs, form->form_num);
326 struct form *
327 find_form_by_form_view(struct document *document, struct form_view *fv)
329 struct form *form;
331 foreach (form, document->forms) {
332 if (form->form_num == fv->form_num)
333 return form;
335 return NULL;
338 /** Free any data owned by @a fs, but not the struct form_state
339 * itself, because that is normally allocated as part of an array.
340 * @relates form_state */
341 void
342 done_form_state(struct form_state *fs)
344 #ifdef CONFIG_ECMASCRIPT
345 ecmascript_detach_form_state(fs);
346 #endif
347 mem_free_if(fs->value);
350 /** Free @a fv and any data owned by it. This does not call
351 * del_from_list(fv), so the caller must usually do that first.
352 * @relates form_view */
353 void
354 done_form_view(struct form_view *fv)
356 #ifdef CONFIG_ECMASCRIPT
357 ecmascript_detach_form_view(fv);
358 #endif
359 mem_free(fv);
363 get_current_state(struct session *ses)
365 struct document_view *doc_view;
366 struct link *link;
367 struct form_state *fs;
369 assert(ses);
370 if_assert_failed return -1;
371 doc_view = current_frame(ses);
373 assert(doc_view && doc_view->vs && doc_view->document);
374 if_assert_failed return -1;
376 link = get_current_link(doc_view);
377 if (!link || link->type != LINK_SELECT) return -1;
379 fs = find_form_state(doc_view, get_link_form_control(link));
380 if (fs) return fs->state;
381 return -1;
384 void
385 draw_form_entry(struct terminal *term, struct document_view *doc_view,
386 struct link *link)
388 struct form_state *fs;
389 struct form_control *fc;
390 struct view_state *vs;
391 struct box *box;
392 int dx, dy;
394 assert(term && doc_view && doc_view->document && doc_view->vs && link);
395 if_assert_failed return;
397 fc = get_link_form_control(link);
398 assertm(fc != NULL, "link %d has no form control", (int) (link - doc_view->document->links));
399 if_assert_failed return;
401 fs = find_form_state(doc_view, fc);
402 if (!fs) return;
404 box = &doc_view->box;
405 vs = doc_view->vs;
406 dx = box->x - vs->x;
407 dy = box->y - vs->y;
408 switch (fc->type) {
409 unsigned char *s;
410 #ifdef CONFIG_UTF8
411 unsigned char *text, *end, *last_in_view;
412 int retried;
413 #endif /* CONFIG_UTF8 */
414 int len;
415 int i, x, y;
417 case FC_TEXT:
418 case FC_PASSWORD:
419 case FC_FILE:
420 if (!link->npoints) break;
422 y = link->points[0].y + dy;
423 if (!row_is_in_box(box, y))
424 break;
426 x = link->points[0].x + dx;
427 #ifdef CONFIG_UTF8
428 if (term->utf8_cp) goto utf8;
429 #endif /* CONFIG_UTF8 */
430 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
431 len = strlen(fs->value) - fs->vpos;
433 for (i = 0; i < fc->size; i++, x++) {
434 unsigned char data;
436 if (!col_is_in_box(box, x)) continue;
438 if (fs->value && i >= -fs->vpos && i < len)
439 data = fc->type != FC_PASSWORD
440 ? fs->value[i + fs->vpos] : '*';
441 else
442 data = '_';
444 draw_char_data(term, x, y, data);
446 break;
447 #ifdef CONFIG_UTF8
448 utf8:
449 retried = 0;
451 retry_after_scroll:
452 text = fs->value;
453 if (!text) text = "";
454 len = strlen(text);
455 int_bounds(&fs->state, 0, len);
456 int_bounds(&fs->vpos, 0, fs->state);
457 end = text + len;
458 text += fs->vpos;
459 last_in_view = NULL;
461 for (i = 0; i < fc->size; ) {
462 unicode_val_T data;
463 int cells, cell;
464 unsigned char *maybe_in_view = text;
466 data = utf8_to_unicode(&text, end);
467 if (data == UCS_NO_CHAR) /* end of string */
468 data = '_';
469 else if (fc->type == FC_PASSWORD)
470 data = '*';
472 cells = unicode_to_cell(data);
473 if (i + cells <= fc->size) {
474 last_in_view = maybe_in_view;
475 if (colspan_is_in_box(box, x + i, cells)) {
476 /* The character fits completely.
477 * Draw the character, and mark any
478 * further cells with UCS_NO_CHAR. */
479 draw_char_data(term, x + i, y, data);
480 for (cell = 1; cell < cells; cell++)
481 draw_char_data(term, x + i + cell,
482 y, UCS_NO_CHAR);
483 goto drew_char;
487 /* The character does not fit completely.
488 * Write UCS_ORPHAN_CELL to the cells that
489 * do fit. */
490 for (cell = 0; cell < cells; cell++) {
491 if (col_is_in_box(box, x + i + cell)
492 && i + cell < fc->size)
493 draw_char_data(term,
494 x + i + cell, y,
495 UCS_ORPHAN_CELL);
498 drew_char:
499 i += cells;
502 /* The int_bounds calls above ensured that the
503 * insertion point cannot be at the left side
504 * of the scrolled-visible part of the text.
505 * However it can still be at the right side.
506 * Check whether we need to change fs->vpos.
508 * This implementation attempts to follow
509 * these rules:
510 * - If the insertion point is at the end of
511 * the string, leave at least one empty cell
512 * so that there is a place for the cursor.
513 * - If a character follows the insertion
514 * point, make that character fully visible;
515 * note the character may be double-width.
516 * - If fc->size < 2, it is not possible to
517 * make a double-width character fully
518 * visible. In this case, it is OK if the
519 * output is ugly, but ELinks must not fall
520 * into an infinite loop or crash.
521 * - The length of the string should not affect
522 * how long this function takes. The width
523 * of the widget naturally will.
524 * - Optimize the case where fields are drawn
525 * several times without being modified.
527 * It follows that:
528 * - If the "for i" loop above hit UCS_NO_CHAR,
529 * then there is no need to scroll.
530 * - When the string ends with a double-width
531 * character that fits in only partially,
532 * then text==end, but the field may have
533 * to be scrolled. */
534 if (fs->value && last_in_view
535 && last_in_view < fs->value + fs->state) {
536 unsigned char *ptr = fs->value + fs->state;
537 int cells = fc->size;
538 enum utf8_step how = (fc->type == FC_PASSWORD)
539 ? UTF8_STEP_CHARACTERS
540 : UTF8_STEP_CELLS_FEWER;
542 /* The insertion point is at the right
543 * side of the scrolled-visible part
544 * of the text. Decide a new fs->vpos
545 * by counting cells backwards from
546 * @ptr. But first advance @ptr past
547 * the character that follows the
548 * insertion point, so that it will be
549 * fully displayed. If there is no
550 * such character, reserve one cell
551 * for the cursor anyway. */
552 if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
553 --cells;
554 ptr = utf8_step_backward(ptr, fs->value,
555 cells, how, NULL);
557 if (fs->vpos != ptr - fs->value) {
558 fs->vpos = ptr - fs->value;
559 retried = 1;
560 goto retry_after_scroll;
563 break;
564 #endif /* CONFIG_UTF8 */
565 case FC_TEXTAREA:
566 draw_textarea(term, fs, doc_view, link);
567 break;
568 case FC_CHECKBOX:
569 case FC_RADIO:
570 if (link->npoints < 2) break;
571 x = link->points[1].x + dx;
572 y = link->points[1].y + dy;
573 if (is_in_box(box, x, y))
574 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
575 break;
576 case FC_SELECT:
577 fixup_select_state(fc, fs);
578 if (fs->state < fc->nvalues)
579 s = fc->labels[fs->state];
580 else
581 /* XXX: when can this happen? --pasky */
582 s = "";
583 #ifdef CONFIG_UTF8
584 if (term->utf8_cp) goto utf8_select;
585 #endif /* CONFIG_UTF8 */
586 len = s ? strlen(s) : 0;
587 for (i = 0; i < link->npoints; i++) {
588 x = link->points[i].x + dx;
589 y = link->points[i].y + dy;
590 if (is_in_box(box, x, y))
591 draw_char_data(term, x, y, i < len ? s[i] : '_');
593 break;
594 #ifdef CONFIG_UTF8
595 utf8_select:
596 text = s;
597 end = strchr(s, '\0');
598 len = utf8_ptr2cells(text, end);
599 for (i = 0; i < link->npoints; i++) {
600 x = link->points[i].x + dx;
601 y = link->points[i].y + dy;
602 if (is_in_box(box, x, y)) {
603 unicode_val_T data;
604 if (i < len) {
605 int cell;
607 data = utf8_to_unicode(&s, end);
608 cell = unicode_to_cell(data);
609 if (i + 1 < len && cell == 2) {
610 draw_char_data(term, x++, y, data);
612 data = UCS_NO_CHAR;
613 i++;
614 } else if (cell == 2) {
615 data = UCS_ORPHAN_CELL;
617 } else
618 data = '_';
619 draw_char_data(term, x, y, data);
622 break;
623 #endif /* CONFIG_UTF8 */
624 case FC_SUBMIT:
625 case FC_IMAGE:
626 case FC_RESET:
627 case FC_BUTTON:
628 case FC_HIDDEN:
629 break;
633 void
634 draw_forms(struct terminal *term, struct document_view *doc_view)
636 struct link *l1, *l2;
638 assert(term && doc_view);
639 if_assert_failed return;
641 l1 = get_first_link(doc_view);
642 l2 = get_last_link(doc_view);
644 if (!l1 || !l2) {
645 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
646 /* Return path :-). */
647 return;
649 do {
650 struct form_control *fc = get_link_form_control(l1);
652 if (!fc) continue;
653 draw_form_entry(term, doc_view, l1);
655 } while (l1++ < l2);
659 /** @relates submitted_value */
660 void
661 done_submitted_value_list(LIST_OF(struct submitted_value) *list)
663 struct submitted_value *sv, *svtmp;
665 assert(list);
666 if_assert_failed return;
668 foreach (sv, *list) {
669 svtmp = sv;
670 sv = sv->prev;
671 del_from_list(svtmp);
672 done_submitted_value(svtmp);
676 static void
677 add_submitted_value_to_list(struct form_control *fc,
678 struct form_state *fs,
679 LIST_OF(struct submitted_value) *list)
681 struct submitted_value *sub;
682 unsigned char *name;
683 enum form_type type;
684 int position;
686 assert(fc && fs && list);
688 name = fc->name;
689 position = fc->position;
690 type = fc->type;
692 switch (fc->type) {
693 case FC_TEXT:
694 case FC_PASSWORD:
695 case FC_FILE:
696 case FC_TEXTAREA:
697 sub = init_submitted_value(name, fs->value, type, fc, position);
698 if (sub) add_to_list(*list, sub);
699 break;
701 case FC_CHECKBOX:
702 case FC_RADIO:
703 if (!fs->state) break;
704 /* fall through */
706 case FC_SUBMIT:
707 case FC_HIDDEN:
708 case FC_RESET:
709 case FC_BUTTON:
710 sub = init_submitted_value(name, fs->value, type, fc,
711 position);
712 if (sub) add_to_list(*list, sub);
713 break;
715 case FC_SELECT:
716 if (!fc->nvalues) break;
718 fixup_select_state(fc, fs);
719 sub = init_submitted_value(name, fs->value, type, fc, position);
720 if (sub) add_to_list(*list, sub);
721 break;
723 case FC_IMAGE:
724 name = straconcat(fc->name, ".x", (unsigned char *) NULL);
725 if (!name) break;
726 sub = init_submitted_value(name, "0", type, fc, position);
727 mem_free(name);
728 if (sub) add_to_list(*list, sub);
730 name = straconcat(fc->name, ".y", (unsigned char *) NULL);
731 if (!name) break;
732 sub = init_submitted_value(name, "0", type, fc, position);
733 mem_free(name);
734 if (sub) add_to_list(*list, sub);
736 break;
740 static void
741 sort_submitted_values(LIST_OF(struct submitted_value) *list)
743 while (1) {
744 struct submitted_value *sub;
745 int changed = 0;
747 foreach (sub, *list) if (list_has_next(*list, sub))
748 if (sub->next->position < sub->position) {
749 struct submitted_value *next = sub->next;
751 del_from_list(sub);
752 add_at_pos(next, sub);
753 sub = next;
754 changed = 1;
757 foreachback (sub, *list) if (list_has_next(*list, sub))
758 if (sub->next->position < sub->position) {
759 struct submitted_value *next = sub->next;
761 del_from_list(sub);
762 add_at_pos(next, sub);
763 sub = next;
764 changed = 1;
767 if (!changed) break;
771 static void
772 get_successful_controls(struct document_view *doc_view,
773 struct form_control *fc,
774 LIST_OF(struct submitted_value) *list)
776 struct form_control *fc2;
778 assert(doc_view && fc && fc->form && list);
779 if_assert_failed return;
781 foreach (fc2, fc->form->items) {
782 if (((fc2->type != FC_SUBMIT &&
783 fc2->type != FC_IMAGE &&
784 fc2->type != FC_RESET &&
785 fc2->type != FC_BUTTON) || fc2 == fc)
786 && fc2->name && fc2->name[0]) {
787 struct form_state *fs = find_form_state(doc_view, fc2);
789 if (!fs) continue;
791 add_submitted_value_to_list(fc2, fs, list);
795 sort_submitted_values(list);
798 unsigned char *
799 encode_crlf(struct submitted_value *sv)
801 struct string newtext;
802 int i;
804 assert(sv && sv->value);
805 if_assert_failed return NULL;
807 if (!init_string(&newtext)) return NULL;
809 for (i = 0; sv->value[i]; i++) {
810 if (sv->value[i] == '\r') {
811 if (sv->value[i+1] != '\n')
812 add_crlf_to_string(&newtext);
813 } else if (sv->value[i] == '\n')
814 add_crlf_to_string(&newtext);
815 else
816 add_char_to_string(&newtext, sv->value[i]);
819 return newtext.source;
822 static void
823 encode_controls(LIST_OF(struct submitted_value) *l, struct string *data,
824 int cp_from, int cp_to)
826 struct submitted_value *sv;
827 struct conv_table *convert_table = NULL;
828 int lst = 0;
830 assert(l && data);
831 if_assert_failed return;
833 foreach (sv, *l) {
834 unsigned char *p2 = NULL;
836 if (lst)
837 add_char_to_string(data, '&');
838 else
839 lst = 1;
841 encode_uri_string(data, sv->name, strlen(sv->name), 1);
842 add_char_to_string(data, '=');
844 /* Convert back to original encoding (see html_form_control()
845 * for the original recoding). */
846 if (sv->type == FC_TEXTAREA) {
847 unsigned char *p;
849 p = encode_textarea(sv);
850 if (p) {
851 if (!convert_table)
852 convert_table = get_translation_table(cp_from, cp_to);
854 p2 = convert_string(convert_table, p,
855 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
856 mem_free(p);
858 } else if (sv->type == FC_TEXT ||
859 sv->type == FC_PASSWORD) {
860 if (!convert_table)
861 convert_table = get_translation_table(cp_from, cp_to);
863 p2 = convert_string(convert_table, sv->value,
864 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
865 } else if (sv->type == FC_HIDDEN) {
866 p2 = encode_crlf(sv);
867 } else {
868 p2 = stracpy(sv->value);
871 if (p2) {
872 encode_uri_string(data, p2, strlen(p2), 1);
873 mem_free(p2);
880 #define BOUNDARY_LENGTH 32
881 #define realloc_bound_ptrs(bptrs, bptrs_size) \
882 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
884 struct boundary_info {
885 int count;
886 int *offsets;
887 unsigned char string[BOUNDARY_LENGTH];
890 /** @relates boundary_info */
891 static inline void
892 init_boundary(struct boundary_info *boundary)
894 memset(boundary, 0, sizeof(*boundary));
895 memset(boundary->string, '0', BOUNDARY_LENGTH);
898 /** Add boundary to string and save the offset
899 * @relates boundary_info */
900 static inline void
901 add_boundary(struct string *data, struct boundary_info *boundary)
903 add_to_string(data, "--");
905 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
906 boundary->offsets[boundary->count++] = data->length;
908 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
911 /** @relates boundary_info */
912 static inline unsigned char *
913 increment_boundary_counter(struct boundary_info *boundary)
915 int j;
917 /* This is just a decimal string incrementation */
918 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
919 if (boundary->string[j]++ < '9')
920 return boundary->string;
922 boundary->string[j] = '0';
925 INTERNAL("Form data boundary counter overflow");
927 return NULL;
930 /** @relates boundary_info */
931 static inline void
932 check_boundary(struct string *data, struct boundary_info *boundary)
934 unsigned char *bound = boundary->string;
935 int i;
937 /* Search between all boundaries. There is a starting and an ending
938 * boundary so only check the range of chars after the current offset
939 * and before the next offset. If some string in the form data matches
940 * the boundary string it is changed. */
941 for (i = 0; i < boundary->count - 1; i++) {
942 /* Start after the boundary string and also jump past the
943 * "\r\nContent-Disposition: form-data; name=\"" string added
944 * before any form data. */
945 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
947 /* End so that there is atleast BOUNDARY_LENGTH chars to
948 * compare. Subtract 2 char because there is no need to also
949 * compare the '--' prefix that is part of the boundary. */
950 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
951 unsigned char *pos = data->source + start_offset;
952 unsigned char *end = data->source + end_offset;
954 for (; pos <= end; pos++) {
955 if (memcmp(pos, bound, BOUNDARY_LENGTH))
956 continue;
958 /* If incrementing causes overflow bail out. There is
959 * no need to reset the boundary string with '0' since
960 * that is already done when incrementing. */
961 if (!increment_boundary_counter(boundary))
962 return;
964 /* Else start checking all boundaries using the new
965 * boundary string */
966 i = 0;
967 break;
971 /* Now update all the boundaries with the unique boundary string */
972 for (i = 0; i < boundary->count; i++)
973 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
976 /** @todo FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
977 static void
978 encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
979 struct string *data,
980 struct boundary_info *boundary, int cp_from, int cp_to)
982 struct conv_table *convert_table = NULL;
983 struct submitted_value *sv;
985 assert(ses && l && data && boundary);
986 if_assert_failed return;
988 init_boundary(boundary);
990 foreach (sv, *l) {
991 add_boundary(data, boundary);
992 add_crlf_to_string(data);
994 /** @bug FIXME: name is not encoded.
995 * from RFC 1867:
996 * multipart/form-data contains a series of parts.
997 * Each part is expected to contain a content-disposition
998 * header where the value is "form-data" and a name attribute
999 * specifies the field name within the form,
1000 * e.g., 'content-disposition: form-data; name="xxxxx"',
1001 * where xxxxx is the field name corresponding to that field.
1002 * Field names originally in non-ASCII character sets may be
1003 * encoded using the method outlined in RFC 1522. */
1004 add_to_string(data, "Content-Disposition: form-data; name=\"");
1005 add_to_string(data, sv->name);
1006 add_char_to_string(data, '"');
1008 if (sv->type == FC_FILE) {
1009 #define F_BUFLEN 1024
1010 int fh;
1011 unsigned char buffer[F_BUFLEN];
1012 unsigned char *extension;
1014 add_to_string(data, "; filename=\"");
1015 add_to_string(data, get_filename_position(sv->value));
1016 /* It sends bad data if the file name contains ", but
1017 Netscape does the same */
1018 /* FIXME: We should follow RFCs 1522, 1867,
1019 * 2047 (updated by rfc 2231), to provide correct support
1020 * for non-ASCII and special characters in values. --Zas */
1021 add_char_to_string(data, '"');
1023 /* Add a Content-Type header if the type is configured */
1024 extension = strrchr(sv->value, '.');
1025 if (extension) {
1026 unsigned char *type = get_extension_content_type(extension);
1028 if (type) {
1029 add_crlf_to_string(data);
1030 add_to_string(data, "Content-Type: ");
1031 add_to_string(data, type);
1032 mem_free(type);
1036 add_crlf_to_string(data);
1037 add_crlf_to_string(data);
1039 if (*sv->value) {
1040 unsigned char *filename;
1042 if (get_cmd_opt_bool("anonymous")) {
1043 errno = EPERM;
1044 goto encode_error;
1047 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
1048 filename = expand_tilde(sv->value);
1049 if (!filename) goto encode_error;
1051 fh = open(filename, O_RDONLY);
1052 mem_free(filename);
1054 if (fh == -1) goto encode_error;
1055 set_bin(fh);
1056 while (1) {
1057 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
1059 if (rd) {
1060 if (rd == -1) {
1061 close(fh);
1062 goto encode_error;
1065 add_bytes_to_string(data, buffer, rd);
1067 } else {
1068 break;
1071 close(fh);
1073 #undef F_BUFLEN
1074 } else {
1075 add_crlf_to_string(data);
1076 add_crlf_to_string(data);
1078 /* Convert back to original encoding (see
1079 * html_form_control() for the original recoding). */
1080 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
1081 sv->type == FC_TEXTAREA) {
1082 unsigned char *p;
1084 if (!convert_table)
1085 convert_table = get_translation_table(cp_from,
1086 cp_to);
1088 p = convert_string(convert_table, sv->value,
1089 strlen(sv->value), -1, CSM_FORM, NULL,
1090 NULL, NULL);
1091 if (p) {
1092 add_to_string(data, p);
1093 mem_free(p);
1095 } else {
1096 add_to_string(data, sv->value);
1100 add_crlf_to_string(data);
1103 /* End-boundary */
1104 add_boundary(data, boundary);
1105 add_to_string(data, "--\r\n");
1107 check_boundary(data, boundary);
1109 mem_free_if(boundary->offsets);
1110 return;
1112 encode_error:
1113 mem_free_if(boundary->offsets);
1114 done_string(data);
1116 /* XXX: This error message should move elsewhere. --Zas */
1117 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1118 N_("Error while posting form"), ALIGN_CENTER,
1119 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1120 sv->value, strerror(errno)));
1123 static void
1124 encode_newlines(struct string *string, unsigned char *data)
1126 for (; *data; data++) {
1127 if (*data == '\n' || *data == '\r') {
1128 unsigned char buffer[3];
1130 /* Hex it. */
1131 buffer[0] = '%';
1132 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1133 buffer[2] = hx(((int) *data) & 0xF);
1134 add_bytes_to_string(string, buffer, 3);
1135 } else {
1136 add_char_to_string(string, *data);
1141 static void
1142 encode_text_plain(LIST_OF(struct submitted_value) *l, struct string *data,
1143 int cp_from, int cp_to)
1145 struct submitted_value *sv;
1146 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1148 assert(l && data);
1149 if_assert_failed return;
1151 foreach (sv, *l) {
1152 unsigned char *area51 = NULL;
1153 unsigned char *value = sv->value;
1155 add_to_string(data, sv->name);
1156 add_char_to_string(data, '=');
1158 switch (sv->type) {
1159 case FC_TEXTAREA:
1160 value = area51 = encode_textarea(sv);
1161 if (!area51) break;
1162 /* Fall through */
1163 case FC_HIDDEN:
1164 if (!area51) value = area51 = encode_crlf(sv);
1165 if (!area51) break;
1166 /* Fall through */
1167 case FC_TEXT:
1168 case FC_PASSWORD:
1169 /* Convert back to original encoding (see
1170 * html_form_control() for the original recoding). */
1171 value = convert_string(convert_table, value,
1172 strlen(value), -1, CSM_FORM,
1173 NULL, NULL, NULL);
1174 default:
1175 /* Falling right through to free that textarea stuff */
1176 mem_free_if(area51);
1178 /* Did the conversion fail? */
1179 if (!value) break;
1181 encode_newlines(data, value);
1183 /* Free if we did convert something */
1184 if (value != sv->value) mem_free(value);
1187 add_crlf_to_string(data);
1191 void
1192 do_reset_form(struct document_view *doc_view, struct form *form)
1194 struct form_control *fc;
1196 assert(doc_view && doc_view->document);
1197 if_assert_failed return;
1199 foreach (fc, form->items) {
1200 struct form_state *fs = find_form_state(doc_view, fc);
1202 if (fs) init_form_state(doc_view, fc, fs);
1206 enum frame_event_status
1207 reset_form(struct session *ses, struct document_view *doc_view, int a)
1209 struct link *link = get_current_link(doc_view);
1211 if (!link) return FRAME_EVENT_OK;
1213 do_reset_form(doc_view, get_link_form_control(link)->form);
1214 draw_forms(ses->tab->term, doc_view);
1216 /* Could be the refresh return value and then ditch the draw_forms()
1217 * call. */
1218 return FRAME_EVENT_OK;
1221 struct uri *
1222 get_form_uri(struct session *ses, struct document_view *doc_view,
1223 struct form_control *fc)
1225 struct boundary_info boundary;
1226 INIT_LIST_OF(struct submitted_value, submit);
1227 struct string data;
1228 struct string go;
1229 int cp_from, cp_to;
1230 struct uri *uri;
1231 struct form *form;
1233 assert(ses && ses->tab && ses->tab->term);
1234 if_assert_failed return NULL;
1235 assert(doc_view && doc_view->document && fc && fc->form);
1236 if_assert_failed return NULL;
1238 form = fc->form;
1240 if (fc->type == FC_RESET) {
1241 do_reset_form(doc_view, form);
1242 return NULL;
1245 if (!form->action
1246 || !init_string(&data))
1247 return NULL;
1249 get_successful_controls(doc_view, fc, &submit);
1251 cp_from = get_terminal_codepage(ses->tab->term);
1252 cp_to = doc_view->document->cp;
1253 switch (form->method) {
1254 case FORM_METHOD_GET:
1255 case FORM_METHOD_POST:
1256 encode_controls(&submit, &data, cp_from, cp_to);
1257 break;
1259 case FORM_METHOD_POST_MP:
1260 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1261 break;
1263 case FORM_METHOD_POST_TEXT_PLAIN:
1264 encode_text_plain(&submit, &data, cp_from, cp_to);
1267 #ifdef CONFIG_FORMHIST
1268 /* XXX: We check data.source here because a NULL value can indicate
1269 * not only a memory allocation failure, but also an error reading
1270 * a file that is to be uploaded. TODO: Distinguish between
1271 * these two classes of errors (is it worth it?). -- Miciah */
1272 if (data.source
1273 && get_opt_bool("document.browse.forms.show_formhist"))
1274 memorize_form(ses, &submit, form);
1275 #endif
1277 done_submitted_value_list(&submit);
1279 if (!data.source
1280 || !init_string(&go)) {
1281 done_string(&data);
1282 return NULL;
1285 switch (form->method) {
1286 case FORM_METHOD_GET:
1288 unsigned char *pos = strchr(form->action, '#');
1290 if (pos) {
1291 add_bytes_to_string(&go, form->action, pos - form->action);
1292 } else {
1293 add_to_string(&go, form->action);
1296 if (strchr(go.source, '?'))
1297 add_char_to_string(&go, '&');
1298 else
1299 add_char_to_string(&go, '?');
1301 add_string_to_string(&go, &data);
1303 if (pos) add_to_string(&go, pos);
1304 break;
1306 case FORM_METHOD_POST:
1307 case FORM_METHOD_POST_MP:
1308 case FORM_METHOD_POST_TEXT_PLAIN:
1310 /* Note that we end content type here by a simple '\n',
1311 * replaced later by correct '\r\n' in http_send_header(). */
1312 int i;
1314 add_to_string(&go, form->action);
1315 add_char_to_string(&go, POST_CHAR);
1316 if (form->method == FORM_METHOD_POST) {
1317 add_to_string(&go, "application/x-www-form-urlencoded\n");
1319 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1320 /* Dunno about this one but we don't want the full
1321 * hextcat thingy. --jonas */
1322 add_to_string(&go, "text/plain\n");
1323 add_to_string(&go, data.source);
1324 break;
1326 } else {
1327 add_to_string(&go, "multipart/form-data; boundary=");
1328 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1329 add_char_to_string(&go, '\n');
1332 for (i = 0; i < data.length; i++) {
1333 unsigned char p[3];
1335 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1336 add_to_string(&go, p);
1341 done_string(&data);
1343 uri = get_uri(go.source, 0);
1344 done_string(&go);
1345 if (uri) uri->form = 1;
1347 return uri;
1350 #undef BOUNDARY_LENGTH
1353 enum frame_event_status
1354 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1356 goto_current_link(ses, doc_view, do_reload);
1357 return FRAME_EVENT_OK;
1360 void
1361 submit_given_form(struct session *ses, struct document_view *doc_view,
1362 struct form *form, int do_reload)
1364 /* Added support for submitting forms in hidden
1365 * links in 1.285, commented code can safely be removed once we have made sure the new
1366 * code does the right thing. */
1367 #if 0
1369 struct document *document = doc_view->document;
1370 int link;
1372 for (link = 0; link < document->nlinks; link++) {
1373 struct form_control *fc = get_link_form_control(&document->links[link]);
1375 if (fc && fc->form == form) {
1376 doc_view->vs->current_link = link;
1377 submit_form(ses, doc_view, 0);
1378 return;
1381 #endif
1382 if (!list_empty(form->items)) {
1383 struct form_control *fc = (struct form_control *)form->items.next;
1384 struct uri *uri;
1385 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1387 if (!fc) return;
1388 uri = get_form_uri(ses, doc_view, fc);
1389 if (!uri) return;
1390 goto_uri_frame(ses, uri, form->target, mode);
1391 done_uri(uri);
1395 void
1396 auto_submit_form(struct session *ses)
1398 struct document *document = ses->doc_view->document;
1400 if (!list_empty(document->forms))
1401 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1405 /* menu_func_T */
1406 static void
1407 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1409 unsigned char *filename = filename_;
1410 struct form_state *fs = fs_;
1412 /* The menu code doesn't free the filename data */
1413 mem_free_set(&fs->value, filename);
1414 fs->state = strlen(filename);
1415 redraw_terminal(term);
1418 /* menu_func_T */
1419 static void
1420 file_form_menu(struct terminal *term, void *path_, void *fs_)
1422 unsigned char *path = path_;
1423 struct form_state *fs = fs_;
1425 /* FIXME: It doesn't work for ../../ */
1426 #if 0
1427 int valuelen = strlen(fs->value);
1428 int pathlen = strlen(path);
1429 int no_elevator = 0;
1431 /* Don't add elevators for subdirs menus */
1432 /* It is not perfect at all because fs->value is not updated for each
1433 * newly opened file menu. Maybe it should be dropped. */
1434 for (; valuelen < pathlen; valuelen++) {
1435 if (dir_sep(path[valuelen - 1])) {
1436 no_elevator = 1;
1437 break;
1440 #endif
1442 auto_complete_file(term, 0 /* no_elevator */, path,
1443 set_file_form_state,
1444 file_form_menu, fs);
1448 enum frame_event_status
1449 field_op(struct session *ses, struct document_view *doc_view,
1450 struct link *link, struct term_event *ev)
1452 struct form_control *fc;
1453 struct form_state *fs;
1454 enum edit_action action_id;
1455 unsigned char *text;
1456 int length;
1457 enum frame_event_status status = FRAME_EVENT_REFRESH;
1458 #ifdef CONFIG_UTF8
1459 const unsigned char *ctext;
1460 int utf8 = ses->tab->term->utf8_cp;
1461 #endif /* CONFIG_UTF8 */
1463 assert(ses && doc_view && link && ev);
1464 if_assert_failed return FRAME_EVENT_OK;
1466 fc = get_link_form_control(link);
1467 assertm(fc != NULL, "link has no form control");
1468 if_assert_failed return FRAME_EVENT_OK;
1470 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1471 || ses->insert_mode == INSERT_MODE_OFF)
1472 return FRAME_EVENT_IGNORED;
1474 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1476 fs = find_form_state(doc_view, fc);
1477 if (!fs || !fs->value) return FRAME_EVENT_OK;
1479 switch (action_id) {
1480 case ACT_EDIT_LEFT:
1481 #ifdef CONFIG_UTF8
1482 if (fc->type == FC_TEXTAREA) {
1483 status = textarea_op_left(fs, fc, utf8);
1484 break;
1486 if (utf8) {
1487 unsigned char *new_value;
1489 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1490 fs->state = new_value - fs->value;
1491 } else
1492 #endif /* CONFIG_UTF8 */
1493 fs->state = int_max(fs->state - 1, 0);
1494 break;
1495 case ACT_EDIT_RIGHT:
1496 #ifdef CONFIG_UTF8
1497 if (fc->type == FC_TEXTAREA) {
1498 status = textarea_op_right(fs, fc, utf8);
1499 break;
1501 if (utf8) {
1502 unsigned char *text = fs->value + fs->state;
1503 unsigned char *end = strchr(text, '\0');
1505 utf8_to_unicode(&text, end);
1506 fs->state = (int)(text - fs->value);
1507 } else
1508 #endif /* CONFIG_UTF8 */
1509 fs->state = int_min(fs->state + 1, strlen(fs->value));
1510 break;
1511 case ACT_EDIT_HOME:
1512 #ifdef CONFIG_UTF8
1513 if (fc->type == FC_TEXTAREA) {
1514 status = textarea_op_home(fs, fc, utf8);
1515 } else {
1516 fs->state = 0;
1518 #else
1519 if (fc->type == FC_TEXTAREA) {
1520 status = textarea_op_home(fs, fc);
1521 } else {
1522 fs->state = 0;
1525 #endif /* CONFIG_UTF8 */
1526 break;
1527 case ACT_EDIT_UP:
1528 if (fc->type != FC_TEXTAREA)
1529 status = FRAME_EVENT_IGNORED;
1530 else
1531 #ifdef CONFIG_UTF8
1532 status = textarea_op_up(fs, fc, utf8);
1533 #else
1534 status = textarea_op_up(fs, fc);
1535 #endif /* CONFIG_UTF8 */
1536 break;
1537 case ACT_EDIT_DOWN:
1538 if (fc->type != FC_TEXTAREA)
1539 status = FRAME_EVENT_IGNORED;
1540 else
1541 #ifdef CONFIG_UTF8
1542 status = textarea_op_down(fs, fc, utf8);
1543 #else
1544 status = textarea_op_down(fs, fc);
1545 #endif /* CONFIG_UTF8 */
1546 break;
1547 case ACT_EDIT_END:
1548 if (fc->type == FC_TEXTAREA) {
1549 #ifdef CONFIG_UTF8
1550 status = textarea_op_end(fs, fc, utf8);
1551 #else
1552 status = textarea_op_end(fs, fc);
1553 #endif /* CONFIG_UTF8 */
1554 } else {
1555 fs->state = strlen(fs->value);
1557 break;
1558 case ACT_EDIT_BEGINNING_OF_BUFFER:
1559 if (fc->type == FC_TEXTAREA) {
1560 #ifdef CONFIG_UTF8
1561 status = textarea_op_bob(fs, fc, utf8);
1562 fs->state_cell = 0;
1563 #else
1564 status = textarea_op_bob(fs, fc);
1565 #endif /* CONFIG_UTF8 */
1566 } else {
1567 fs->state = 0;
1569 break;
1570 case ACT_EDIT_END_OF_BUFFER:
1571 if (fc->type == FC_TEXTAREA) {
1572 #ifdef CONFIG_UTF8
1573 status = textarea_op_eob(fs, fc, utf8);
1574 #else
1575 status = textarea_op_eob(fs, fc);
1576 #endif /* CONFIG_UTF8 */
1577 } else {
1578 fs->state = strlen(fs->value);
1580 break;
1581 case ACT_EDIT_OPEN_EXTERNAL:
1582 if (form_field_is_readonly(fc))
1583 status = FRAME_EVENT_IGNORED;
1584 else if (fc->type == FC_TEXTAREA)
1585 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1586 break;
1587 case ACT_EDIT_COPY_CLIPBOARD:
1588 set_clipboard_text(fs->value);
1589 status = FRAME_EVENT_OK;
1590 break;
1591 case ACT_EDIT_CUT_CLIPBOARD:
1592 set_clipboard_text(fs->value);
1593 if (!form_field_is_readonly(fc))
1594 fs->value[0] = 0;
1595 fs->state = 0;
1596 #ifdef CONFIG_UTF8
1597 if (fc->type == FC_TEXTAREA)
1598 fs->state_cell = 0;
1599 #endif /* CONFIG_UTF8 */
1600 break;
1601 case ACT_EDIT_PASTE_CLIPBOARD:
1602 if (form_field_is_readonly(fc)) break;
1604 text = get_clipboard_text();
1605 if (!text) break;
1607 length = strlen(text);
1608 if (length <= fc->maxlength) {
1609 unsigned char *v = mem_realloc(fs->value, length + 1);
1611 if (v) {
1612 fs->value = v;
1613 memmove(v, text, length + 1);
1614 fs->state = strlen(fs->value);
1615 #ifdef CONFIG_UTF8
1616 if (utf8 && fc->type == FC_TEXTAREA)
1617 fs->state_cell = 0;
1618 #endif /* CONFIG_UTF8 */
1621 mem_free(text);
1622 break;
1623 case ACT_EDIT_ENTER:
1624 if (fc->type == FC_TEXTAREA) {
1625 #ifdef CONFIG_UTF8
1626 status = textarea_op_enter(fs, fc, utf8);
1627 #else
1628 status = textarea_op_enter(fs, fc);
1629 #endif /* CONFIG_UTF8 */
1630 break;
1633 /* Set status to ok if either it is not possible to
1634 * submit the form or the posting fails. */
1635 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1636 if ((has_form_submit(fc->form)
1637 && !get_opt_bool("document.browse.forms.auto_submit"))
1638 || goto_current_link(ses, doc_view, 0)) {
1639 if (ses->insert_mode == INSERT_MODE_ON)
1640 ses->insert_mode = INSERT_MODE_OFF;
1641 status = FRAME_EVENT_OK;
1643 break;
1644 case ACT_EDIT_BACKSPACE:
1645 if (form_field_is_readonly(fc)) {
1646 status = FRAME_EVENT_IGNORED;
1647 break;
1650 if (!fs->state) {
1651 status = FRAME_EVENT_OK;
1652 break;
1654 #ifdef CONFIG_UTF8
1655 if (utf8) {
1656 int old_state = fs->state;
1657 unsigned char *new_value;
1659 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1660 fs->state = new_value - fs->value;
1662 if (old_state != fs->state) {
1663 if (fc->type == FC_TEXTAREA)
1664 fs->state_cell = 0;
1665 length = strlen(fs->value + old_state) + 1;
1666 memmove(new_value, fs->value + old_state, length);
1668 } else
1669 #endif /* CONFIG_UTF8 */
1671 length = strlen(fs->value + fs->state) + 1;
1672 text = fs->value + fs->state;
1674 memmove(text - 1, text, length);
1675 fs->state--;
1677 break;
1678 case ACT_EDIT_DELETE:
1679 if (form_field_is_readonly(fc)) {
1680 status = FRAME_EVENT_IGNORED;
1681 break;
1684 length = strlen(fs->value);
1685 if (fs->state >= length) {
1686 status = FRAME_EVENT_OK;
1687 break;
1689 #ifdef CONFIG_UTF8
1690 if (utf8) {
1691 unsigned char *end = fs->value + length;
1692 unsigned char *text = fs->value + fs->state;
1693 unsigned char *old = text;
1695 utf8_to_unicode(&text, end);
1696 if (old != text) {
1697 memmove(old, text,
1698 (int)(end - text) + 1);
1700 break;
1702 #endif /* CONFIG_UTF8 */
1703 text = fs->value + fs->state;
1705 memmove(text, text + 1, length - fs->state);
1706 break;
1707 case ACT_EDIT_KILL_TO_BOL:
1708 if (form_field_is_readonly(fc)) {
1709 status = FRAME_EVENT_IGNORED;
1710 break;
1713 if (fs->state <= 0) {
1714 status = FRAME_EVENT_OK;
1715 break;
1718 text = memrchr(fs->value, ASCII_LF, fs->state);
1719 if (text) {
1720 /* Leave the new-line character if it does not
1721 * immediately precede the cursor. */
1722 if (text != &fs->value[fs->state - 1])
1723 text++;
1724 } else {
1725 text = fs->value;
1728 length = strlen(fs->value + fs->state) + 1;
1729 memmove(text, fs->value + fs->state, length);
1731 fs->state = (int) (text - fs->value);
1732 #ifdef CONFIG_UTF8
1733 if (utf8) {
1734 if (fc->type == FC_TEXTAREA)
1735 fs->state_cell = 0;
1737 #endif /* CONFIG_UTF8 */
1738 break;
1739 case ACT_EDIT_KILL_TO_EOL:
1740 if (form_field_is_readonly(fc)) {
1741 status = FRAME_EVENT_IGNORED;
1742 break;
1745 if (!fs->value[fs->state]) {
1746 status = FRAME_EVENT_OK;
1747 break;
1750 text = strchr(fs->value + fs->state, ASCII_LF);
1751 if (!text) {
1752 fs->value[fs->state] = '\0';
1753 break;
1756 if (fs->value[fs->state] == ASCII_LF)
1757 ++text;
1759 memmove(fs->value + fs->state, text, strlen(text) + 1);
1760 break;
1762 case ACT_EDIT_KILL_WORD_BACK:
1763 if (form_field_is_readonly(fc)) {
1764 status = FRAME_EVENT_IGNORED;
1765 break;
1768 if (fs->state <= 0) {
1769 status = FRAME_EVENT_OK;
1770 break;
1773 text = &fs->value[fs->state];
1774 while (text > fs->value && isspace(*(text - 1)))
1775 --text;
1776 while (text > fs->value && !isspace(*(text - 1)))
1777 --text;
1778 if (*text == ASCII_LF
1779 && text != &fs->value[fs->state - 1])
1780 text++;
1782 length = strlen(fs->value + fs->state) + 1;
1783 memmove(text, fs->value + fs->state, length);
1785 fs->state = (int) (text - fs->value);
1786 break;
1788 case ACT_EDIT_MOVE_BACKWARD_WORD:
1789 while (fs->state > 0
1790 && isspace(fs->value[fs->state - 1]))
1791 --fs->state;
1792 while (fs->state > 0
1793 && !isspace(fs->value[fs->state - 1]))
1794 --fs->state;
1795 break;
1797 case ACT_EDIT_MOVE_FORWARD_WORD:
1798 while (isspace(fs->value[fs->state]))
1799 ++fs->state;
1800 while (fs->value[fs->state]
1801 && !isspace(fs->value[fs->state]))
1802 ++fs->state;
1803 while (isspace(fs->value[fs->state]))
1804 ++fs->state;
1805 break;
1807 case ACT_EDIT_AUTO_COMPLETE:
1808 if (fc->type != FC_FILE
1809 || form_field_is_readonly(fc)) {
1810 status = FRAME_EVENT_IGNORED;
1811 break;
1814 file_form_menu(ses->tab->term, fs->value, fs);
1815 break;
1817 case ACT_EDIT_CANCEL:
1818 if (ses->insert_mode == INSERT_MODE_ON)
1819 ses->insert_mode = INSERT_MODE_OFF;
1820 else
1821 status = FRAME_EVENT_IGNORED;
1822 break;
1824 case ACT_EDIT_REDRAW:
1825 redraw_terminal_cls(ses->tab->term);
1826 status = FRAME_EVENT_OK;
1827 break;
1829 default:
1830 if (!check_kbd_textinput_key(ev)) {
1831 status = FRAME_EVENT_IGNORED;
1832 break;
1835 if (form_field_is_readonly(fc)
1836 #ifndef CONFIG_UTF8
1837 || strlen(fs->value) >= fc->maxlength
1838 || !insert_in_string(&fs->value, fs->state, "?", 1)
1839 #endif /* CONFIG_UTF8 */
1842 status = FRAME_EVENT_OK;
1843 break;
1846 #ifdef CONFIG_UTF8
1847 /* fs->value is in the charset of the terminal. */
1848 ctext = u2cp_no_nbsp(get_kbd_key(ev),
1849 get_terminal_codepage(ses->tab->term));
1850 length = strlen(ctext);
1852 if (strlen(fs->value) + length > fc->maxlength
1853 || !insert_in_string(&fs->value, fs->state, ctext, length)) {
1854 status = FRAME_EVENT_OK;
1855 break;
1858 fs->state += length;
1859 if (fc->type == FC_TEXTAREA)
1860 fs->state_cell = 0;
1861 #else
1862 fs->value[fs->state++] = get_kbd_key(ev);
1863 #endif /* CONFIG_UTF8 */
1864 break;
1867 return status;
1870 static unsigned char *
1871 get_form_label(struct form_control *fc)
1873 assert(fc->form);
1874 switch (fc->type) {
1875 case FC_RESET:
1876 return N_("Reset form");
1877 case FC_BUTTON:
1878 return N_("Harmless button");
1879 case FC_HIDDEN:
1880 return NULL;
1881 case FC_SUBMIT:
1882 case FC_IMAGE:
1883 if (!fc->form->action) return NULL;
1885 if (fc->form->method == FORM_METHOD_GET)
1886 return N_("Submit form to");
1887 return N_("Post form to");
1888 case FC_RADIO:
1889 return N_("Radio button");
1890 case FC_CHECKBOX:
1891 return N_("Checkbox");
1892 case FC_SELECT:
1893 return N_("Select field");
1894 case FC_TEXT:
1895 return N_("Text field");
1896 case FC_TEXTAREA:
1897 return N_("Text area");
1898 case FC_FILE:
1899 return N_("File upload");
1900 case FC_PASSWORD:
1901 return N_("Password field");
1904 return NULL;
1907 static inline void
1908 add_form_attr_to_string(struct string *string, struct terminal *term,
1909 unsigned char *name, unsigned char *value)
1911 add_to_string(string, ", ");
1912 add_to_string(string, _(name, term));
1913 if (value) {
1914 add_char_to_string(string, ' ');
1915 add_to_string(string, value);
1919 unsigned char *
1920 get_form_info(struct session *ses, struct document_view *doc_view)
1922 struct terminal *term = ses->tab->term;
1923 struct link *link = get_current_link(doc_view);
1924 struct form_control *fc;
1925 unsigned char *label, *key;
1926 struct string str;
1928 assert(link);
1930 fc = get_link_form_control(link);
1931 label = get_form_label(fc);
1932 if (!label) return NULL;
1934 if (!init_string(&str)) return NULL;
1936 add_to_string(&str, _(label, term));
1938 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1939 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1942 switch (fc->type) {
1943 case FC_CHECKBOX:
1944 case FC_RADIO:
1946 struct form_state *fs = find_form_state(doc_view, fc);
1948 if (!fs->value || !fs->value[0])
1949 break;
1951 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1952 break;
1955 case FC_TEXT:
1956 case FC_PASSWORD:
1957 case FC_FILE:
1958 case FC_TEXTAREA:
1960 struct uri *uri;
1961 unsigned char *uristring;
1963 if (form_field_is_readonly(fc)) {
1964 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1967 /* Should we add info about entering insert mode or add info
1968 * about submitting the form? */
1969 if (ses->insert_mode == INSERT_MODE_OFF) {
1970 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1972 if (!key) break;
1974 if (form_field_is_readonly(fc))
1975 label = N_("press %s to navigate");
1976 else
1977 label = N_("press %s to edit");
1979 add_to_string(&str, " (");
1980 add_format_to_string(&str, _(label, term), key);
1981 add_char_to_string(&str, ')');
1982 mem_free(key);
1983 break;
1987 if (fc->type == FC_TEXTAREA)
1988 break;
1990 assert(fc->form);
1992 if (!fc->form->action
1993 || (has_form_submit(fc->form)
1994 && !get_opt_bool("document.browse.forms.auto_submit")))
1995 break;
1997 uri = get_uri(fc->form->action, 0);
1998 if (!uri) break;
2000 /* Add the uri with password and post info stripped */
2001 uristring = get_uri_string(uri, URI_PUBLIC);
2002 done_uri(uri);
2004 if (!uristring) break;
2006 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
2007 if (!key) {
2008 mem_free(uristring);
2009 break;
2012 if (fc->form->method == FORM_METHOD_GET)
2013 label = N_("press %s to submit to %s");
2014 else
2015 label = N_("press %s to post to %s");
2017 add_to_string(&str, " (");
2018 add_format_to_string(&str, _(label, term), key, uristring);
2019 mem_free(uristring);
2020 mem_free(key);
2022 add_char_to_string(&str, ')');
2023 break;
2025 case FC_SUBMIT:
2026 case FC_IMAGE:
2027 add_char_to_string(&str, ' ');
2029 assert(fc->form);
2030 /* Add the uri with password and post info stripped */
2031 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
2032 break;
2034 case FC_HIDDEN:
2035 case FC_RESET:
2036 case FC_BUTTON:
2037 case FC_SELECT:
2038 break;
2041 if (link->accesskey
2042 && get_opt_bool("document.browse.accesskey.display")) {
2043 add_to_string(&str, " (");
2044 add_accesskey_to_string(&str, link->accesskey);
2045 add_char_to_string(&str, ')');
2048 return str.source;
2051 static void
2052 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
2054 struct session *ses = ses_;
2055 struct document_view *doc_view;
2056 int link_number = *(int *) link_number_;
2058 mem_free(link_number_);
2060 assert(term && ses);
2061 if_assert_failed return;
2063 doc_view = current_frame(ses);
2064 if (!doc_view) return;
2066 assert(doc_view->vs && doc_view->document);
2067 if_assert_failed return;
2069 jump_to_link_number(ses, doc_view, link_number);
2070 refresh_view(ses, doc_view, 0);
2073 void
2074 link_form_menu(struct session *ses)
2076 struct document_view *doc_view;
2077 struct link *link;
2078 struct menu_item *mi;
2079 struct form_control *fc;
2080 struct form *form;
2082 assert(ses);
2083 if_assert_failed return;
2085 doc_view = current_frame(ses);
2086 if (!doc_view) return;
2088 assert(doc_view->vs && doc_view->document);
2089 if_assert_failed return;
2091 link = get_current_link(doc_view);
2092 if (!link) return;
2094 assert(link_is_form(link));
2096 fc = get_link_form_control(link);
2097 if (!fc) return;
2099 form = fc->form;
2101 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2102 if (!mi) return;
2104 foreach (fc, form->items) {
2105 unsigned char *text;
2106 unsigned char *rtext;
2107 int link_number;
2108 struct string str;
2110 switch (fc->type) {
2111 case FC_HIDDEN:
2112 continue;
2114 case FC_SUBMIT:
2115 case FC_IMAGE:
2116 if (!form->action)
2117 text = N_("Useless button");
2118 else
2119 text = N_("Submit button");
2120 break;
2122 default:
2123 text = get_form_label(fc);
2126 link_number = get_form_control_link(doc_view->document, fc);
2127 if (link_number < 0
2128 || !init_string(&str))
2129 continue;
2131 assert(text);
2132 add_to_string(&str, _(text, ses->tab->term));
2134 rtext = fc->name;
2135 if (!rtext) rtext = fc->alt;
2137 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2138 link_form_menu_func, intdup(link_number),
2139 FREE_DATA);
2142 do_menu(ses->tab->term, mi, ses, 1);