1008: Do not check boundaries, use random ones.
[elinks.git] / src / viewer / text / form.c
blobb8adf10438fc304559ff5b19bfac557386a437c7
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/view.h"
33 #include "intl/gettext/libintl.h"
34 #include "formhist/formhist.h"
35 #include "mime/mime.h"
36 #include "osdep/ascii.h"
37 #include "osdep/osdep.h"
38 #include "protocol/uri.h"
39 #include "session/session.h"
40 #include "session/task.h"
41 #include "terminal/kbd.h"
42 #include "terminal/terminal.h"
43 #include "terminal/window.h"
44 #include "util/conv.h"
45 #include "util/error.h"
46 #include "util/file.h"
47 #include "util/memory.h"
48 #include "util/string.h"
49 #include "viewer/action.h"
50 #include "viewer/text/draw.h"
51 #include "viewer/text/form.h"
52 #include "viewer/text/link.h"
53 #include "viewer/text/textarea.h"
54 #include "viewer/text/view.h"
55 #include "viewer/text/vs.h"
58 /* TODO: Some of these (particulary those encoding routines) would feel better
59 * in viewer/common/. --pasky */
61 struct files_offset {
62 LIST_HEAD(struct files_offset);
63 /* offset of the filename in the data generated by encode_multipart.
64 * data[begin] is the FILE_CHAR, data + begin + 1 is the filename. */
65 int begin;
66 /* end of filename. data[end] is the FILE_CHAR. In normal strings
67 * it would be a nul char. */
68 int end;
72 /** @relates submitted_value */
74 struct submitted_value *
75 init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
76 struct form_control *fc, int position)
78 struct submitted_value *sv;
80 sv = mem_alloc(sizeof(*sv));
81 if (!sv) return NULL;
83 sv->value = stracpy(value);
84 if (!sv->value) { mem_free(sv); return NULL; }
86 sv->name = stracpy(name);
87 if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
89 sv->type = type;
90 sv->form_control = fc;
91 sv->position = position;
93 return sv;
96 /** @relates submitted_value */
97 void
98 done_submitted_value(struct submitted_value *sv)
100 if (!sv) return;
101 mem_free_if(sv->value);
102 mem_free_if(sv->name);
103 mem_free(sv);
106 static void
107 fixup_select_state(struct form_control *fc, struct form_state *fs)
109 int i;
111 assert(fc && fs);
112 if_assert_failed return;
114 if (fs->state >= 0
115 && fs->state < fc->nvalues
116 && !strcmp(fc->values[fs->state], fs->value))
117 return;
119 for (i = 0; i < fc->nvalues; i++)
120 if (!strcmp(fc->values[i], fs->value)) {
121 fs->state = i;
122 return;
125 fs->state = 0;
127 mem_free_set(&fs->value, stracpy(fc->nvalues
128 ? fc->values[0]
129 : (unsigned char *) ""));
132 /* menu_func_T */
133 void
134 selected_item(struct terminal *term, void *item_, void *ses_)
136 struct session *ses = ses_;
137 int item = (long) item_;
138 struct document_view *doc_view;
139 struct link *link;
140 struct form_state *fs;
141 struct form_control *fc;
143 assert(term && ses);
144 if_assert_failed return;
145 doc_view = current_frame(ses);
147 assert(doc_view && doc_view->vs && doc_view->document);
148 if_assert_failed return;
150 link = get_current_link(doc_view);
151 if (!link || link->type != LINK_SELECT) return;
153 fc = get_link_form_control(link);
154 fs = find_form_state(doc_view, fc);
155 if (fs) {
156 if (item >= 0 && item < fc->nvalues) {
157 fs->state = item;
158 mem_free_set(&fs->value, stracpy(fc->values[item]));
160 fixup_select_state(fc, fs);
163 refresh_view(ses, doc_view, 0);
166 static void
167 init_form_state(struct document_view *doc_view,
168 struct form_control *fc, struct form_state *fs)
170 struct terminal *term;
171 int doc_cp, viewer_cp;
173 assert(fc && fs);
174 if_assert_failed return;
176 doc_cp = doc_view->document->cp;
177 term = doc_view->session->tab->term;
178 viewer_cp = get_opt_codepage_tree(term->spec, "charset", NULL);
180 mem_free_set(&fs->value, NULL);
182 switch (fc->type) {
183 case FC_TEXT:
184 case FC_PASSWORD:
185 #ifdef CONFIG_FORMHIST
186 fs->value = null_or_stracpy(
187 get_form_history_value(
188 fc->form->action, fc->name));
189 #endif /* CONFIG_FORMHIST */
190 /* fall through */
191 case FC_TEXTAREA:
192 if (fs->value == NULL) {
193 fs->value = convert_string(
194 get_translation_table(doc_cp, viewer_cp),
195 fc->default_value,
196 strlen(fc->default_value),
197 viewer_cp, CSM_FORM,
198 &fs->state, NULL, NULL);
200 fs->state = fs->value ? strlen(fs->value) : 0;
201 #ifdef CONFIG_UTF8
202 if (fc->type == FC_TEXTAREA)
203 fs->state_cell = 0;
204 #endif /* CONFIG_UTF8 */
205 fs->vpos = 0;
206 break;
207 case FC_FILE:
208 fs->value = stracpy("");
209 fs->state = 0;
210 fs->vpos = 0;
211 break;
212 case FC_SELECT:
213 fs->value = convert_string(
214 get_translation_table(doc_cp, viewer_cp),
215 fc->default_value,
216 strlen(fc->default_value),
217 viewer_cp, CSM_FORM,
218 &fs->state, NULL, NULL);
219 fs->state = fc->default_state;
220 fixup_select_state(fc, fs);
221 break;
222 case FC_CHECKBOX:
223 case FC_RADIO:
224 fs->state = fc->default_state;
225 /* Fall-through */
226 case FC_SUBMIT:
227 case FC_IMAGE:
228 case FC_RESET:
229 case FC_BUTTON:
230 case FC_HIDDEN:
231 /* We don't want to recode hidden fields. */
232 fs->value = stracpy(fc->default_value);
233 break;
238 struct form_state *
239 find_form_state(struct document_view *doc_view, struct form_control *fc)
241 struct view_state *vs;
242 struct form_state *fs;
243 int n;
245 assert(doc_view && doc_view->vs && fc);
246 if_assert_failed return NULL;
248 vs = doc_view->vs;
249 n = fc->g_ctrl_num;
251 if (n >= vs->form_info_len) {
252 int nn = n + 1;
254 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
255 if (!fs) return NULL;
256 vs->form_info = fs;
257 vs->form_info_len = nn;
259 fs = &vs->form_info[n];
261 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
262 && fs->g_ctrl_num == fc->g_ctrl_num
263 && fs->position == fc->position
264 && fs->type == fc->type)
265 return fs;
267 mem_free_if(fs->value);
268 memset(fs, 0, sizeof(*fs));
269 fs->form_view = find_form_view(doc_view, fc->form);
270 fs->g_ctrl_num = fc->g_ctrl_num;
271 fs->position = fc->position;
272 fs->type = fc->type;
273 init_form_state(doc_view, fc, fs);
275 return fs;
278 struct form_control *
279 find_form_control(struct document *document, struct form_state *fs)
281 struct form *form = find_form_by_form_view(document, fs->form_view);
282 struct form_control *fc;
284 foreach (fc, form->items) {
285 if (fs->g_ctrl_num == fc->g_ctrl_num
286 && fs->position == fc->position
287 && fs->type == fc->type)
288 return fc;
291 return NULL;
294 struct form_view *
295 find_form_view_in_vs(struct view_state *vs, int form_num)
297 struct form_view *fv;
299 assert(vs);
301 foreach (fv, vs->forms)
302 if (fv->form_num == form_num)
303 return fv;
305 fv = mem_calloc(1, sizeof(*fv));
306 fv->form_num = form_num;
307 add_to_list(vs->forms, fv);
308 return fv;
311 struct form_view *
312 find_form_view(struct document_view *doc_view, struct form *form)
314 return find_form_view_in_vs(doc_view->vs, form->form_num);
317 struct form *
318 find_form_by_form_view(struct document *document, struct form_view *fv)
320 struct form *form;
322 foreach (form, document->forms) {
323 if (form->form_num == fv->form_num)
324 return form;
326 return NULL;
331 get_current_state(struct session *ses)
333 struct document_view *doc_view;
334 struct link *link;
335 struct form_state *fs;
337 assert(ses);
338 if_assert_failed return -1;
339 doc_view = current_frame(ses);
341 assert(doc_view && doc_view->vs && doc_view->document);
342 if_assert_failed return -1;
344 link = get_current_link(doc_view);
345 if (!link || link->type != LINK_SELECT) return -1;
347 fs = find_form_state(doc_view, get_link_form_control(link));
348 if (fs) return fs->state;
349 return -1;
352 void
353 draw_form_entry(struct terminal *term, struct document_view *doc_view,
354 struct link *link)
356 struct form_state *fs;
357 struct form_control *fc;
358 struct view_state *vs;
359 struct box *box;
360 int dx, dy;
362 assert(term && doc_view && doc_view->document && doc_view->vs && link);
363 if_assert_failed return;
365 fc = get_link_form_control(link);
366 assertm(fc != NULL, "link %d has no form control", (int) (link - doc_view->document->links));
367 if_assert_failed return;
369 fs = find_form_state(doc_view, fc);
370 if (!fs) return;
372 box = &doc_view->box;
373 vs = doc_view->vs;
374 dx = box->x - vs->x;
375 dy = box->y - vs->y;
376 switch (fc->type) {
377 unsigned char *s;
378 #ifdef CONFIG_UTF8
379 unsigned char *text, *end, *last_in_view;
380 int retried;
381 #endif /* CONFIG_UTF8 */
382 int len;
383 int i, x, y;
385 case FC_TEXT:
386 case FC_PASSWORD:
387 case FC_FILE:
388 if (!link->npoints) break;
390 y = link->points[0].y + dy;
391 if (!row_is_in_box(box, y))
392 break;
394 x = link->points[0].x + dx;
395 #ifdef CONFIG_UTF8
396 if (term->utf8_cp) goto utf8;
397 #endif /* CONFIG_UTF8 */
398 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
399 len = strlen(fs->value) - fs->vpos;
401 for (i = 0; i < fc->size; i++, x++) {
402 unsigned char data;
404 if (!col_is_in_box(box, x)) continue;
406 if (fs->value && i >= -fs->vpos && i < len)
407 data = fc->type != FC_PASSWORD
408 ? fs->value[i + fs->vpos] : '*';
409 else
410 data = '_';
412 draw_char_data(term, x, y, data);
414 break;
415 #ifdef CONFIG_UTF8
416 utf8:
417 retried = 0;
419 retry_after_scroll:
420 text = fs->value;
421 if (!text) text = "";
422 len = strlen(text);
423 int_bounds(&fs->state, 0, len);
424 int_bounds(&fs->vpos, 0, fs->state);
425 end = text + len;
426 text += fs->vpos;
427 last_in_view = NULL;
429 for (i = 0; i < fc->size; ) {
430 unicode_val_T data;
431 int cells, cell;
432 unsigned char *maybe_in_view = text;
434 data = utf8_to_unicode(&text, end);
435 if (data == UCS_NO_CHAR) /* end of string */
436 data = '_';
437 else if (fc->type == FC_PASSWORD)
438 data = '*';
440 cells = unicode_to_cell(data);
441 if (i + cells <= fc->size) {
442 last_in_view = maybe_in_view;
443 if (colspan_is_in_box(box, x + i, cells)) {
444 /* The character fits completely.
445 * Draw the character, and mark any
446 * further cells with UCS_NO_CHAR. */
447 draw_char_data(term, x + i, y, data);
448 for (cell = 1; cell < cells; cell++)
449 draw_char_data(term, x + i + cell,
450 y, UCS_NO_CHAR);
451 goto drew_char;
455 /* The character does not fit completely.
456 * Write UCS_ORPHAN_CELL to the cells that
457 * do fit. */
458 for (cell = 0; cell < cells; cell++) {
459 if (col_is_in_box(box, x + i + cell)
460 && i + cell < fc->size)
461 draw_char_data(term,
462 x + i + cell, y,
463 UCS_ORPHAN_CELL);
466 drew_char:
467 i += cells;
470 /* The int_bounds calls above ensured that the
471 * insertion point cannot be at the left side
472 * of the scrolled-visible part of the text.
473 * However it can still be at the right side.
474 * Check whether we need to change fs->vpos.
476 * This implementation attempts to follow
477 * these rules:
478 * - If the insertion point is at the end of
479 * the string, leave at least one empty cell
480 * so that there is a place for the cursor.
481 * - If a character follows the insertion
482 * point, make that character fully visible;
483 * note the character may be double-width.
484 * - If fc->size < 2, it is not possible to
485 * make a double-width character fully
486 * visible. In this case, it is OK if the
487 * output is ugly, but ELinks must not fall
488 * into an infinite loop or crash.
489 * - The length of the string should not affect
490 * how long this function takes. The width
491 * of the widget naturally will.
492 * - Optimize the case where fields are drawn
493 * several times without being modified.
495 * It follows that:
496 * - If the "for i" loop above hit UCS_NO_CHAR,
497 * then there is no need to scroll.
498 * - When the string ends with a double-width
499 * character that fits in only partially,
500 * then text==end, but the field may have
501 * to be scrolled. */
502 if (fs->value && last_in_view
503 && last_in_view < fs->value + fs->state) {
504 unsigned char *ptr = fs->value + fs->state;
505 int cells = fc->size;
506 enum utf8_step how = (fc->type == FC_PASSWORD)
507 ? UTF8_STEP_CHARACTERS
508 : UTF8_STEP_CELLS_FEWER;
510 /* The insertion point is at the right
511 * side of the scrolled-visible part
512 * of the text. Decide a new fs->vpos
513 * by counting cells backwards from
514 * @ptr. But first advance @ptr past
515 * the character that follows the
516 * insertion point, so that it will be
517 * fully displayed. If there is no
518 * such character, reserve one cell
519 * for the cursor anyway. */
520 if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
521 --cells;
522 ptr = utf8_step_backward(ptr, fs->value,
523 cells, how, NULL);
525 if (fs->vpos != ptr - fs->value) {
526 fs->vpos = ptr - fs->value;
527 retried = 1;
528 goto retry_after_scroll;
531 break;
532 #endif /* CONFIG_UTF8 */
533 case FC_TEXTAREA:
534 draw_textarea(term, fs, doc_view, link);
535 break;
536 case FC_CHECKBOX:
537 case FC_RADIO:
538 if (link->npoints < 2) break;
539 x = link->points[1].x + dx;
540 y = link->points[1].y + dy;
541 if (is_in_box(box, x, y))
542 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
543 break;
544 case FC_SELECT:
545 fixup_select_state(fc, fs);
546 if (fs->state < fc->nvalues)
547 s = fc->labels[fs->state];
548 else
549 /* XXX: when can this happen? --pasky */
550 s = "";
551 #ifdef CONFIG_UTF8
552 if (term->utf8_cp) goto utf8_select;
553 #endif /* CONFIG_UTF8 */
554 len = s ? strlen(s) : 0;
555 for (i = 0; i < link->npoints; i++) {
556 x = link->points[i].x + dx;
557 y = link->points[i].y + dy;
558 if (is_in_box(box, x, y))
559 draw_char_data(term, x, y, i < len ? s[i] : '_');
561 break;
562 #ifdef CONFIG_UTF8
563 utf8_select:
564 text = s;
565 end = strchr(s, '\0');
566 len = utf8_ptr2cells(text, end);
567 for (i = 0; i < link->npoints; i++) {
568 x = link->points[i].x + dx;
569 y = link->points[i].y + dy;
570 if (is_in_box(box, x, y)) {
571 unicode_val_T data;
572 if (i < len) {
573 int cell;
575 data = utf8_to_unicode(&s, end);
576 cell = unicode_to_cell(data);
577 if (i + 1 < len && cell == 2) {
578 draw_char_data(term, x++, y, data);
580 data = UCS_NO_CHAR;
581 i++;
582 } else if (cell == 2) {
583 data = UCS_ORPHAN_CELL;
585 } else
586 data = '_';
587 draw_char_data(term, x, y, data);
590 break;
591 #endif /* CONFIG_UTF8 */
592 case FC_SUBMIT:
593 case FC_IMAGE:
594 case FC_RESET:
595 case FC_BUTTON:
596 case FC_HIDDEN:
597 break;
601 void
602 draw_forms(struct terminal *term, struct document_view *doc_view)
604 struct link *l1, *l2;
606 assert(term && doc_view);
607 if_assert_failed return;
609 l1 = get_first_link(doc_view);
610 l2 = get_last_link(doc_view);
612 if (!l1 || !l2) {
613 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
614 /* Return path :-). */
615 return;
617 do {
618 struct form_control *fc = get_link_form_control(l1);
620 if (!fc) continue;
621 draw_form_entry(term, doc_view, l1);
623 } while (l1++ < l2);
627 /** @relates submitted_value */
628 void
629 done_submitted_value_list(LIST_OF(struct submitted_value) *list)
631 struct submitted_value *sv, *svtmp;
633 assert(list);
634 if_assert_failed return;
636 foreach (sv, *list) {
637 svtmp = sv;
638 sv = sv->prev;
639 del_from_list(svtmp);
640 done_submitted_value(svtmp);
644 static void
645 add_submitted_value_to_list(struct form_control *fc,
646 struct form_state *fs,
647 LIST_OF(struct submitted_value) *list)
649 struct submitted_value *sub;
650 unsigned char *name;
651 enum form_type type;
652 int position;
654 assert(fc && fs && list);
656 name = fc->name;
657 position = fc->position;
658 type = fc->type;
660 switch (fc->type) {
661 case FC_TEXT:
662 case FC_PASSWORD:
663 case FC_FILE:
664 case FC_TEXTAREA:
665 sub = init_submitted_value(name, fs->value, type, fc, position);
666 if (sub) add_to_list(*list, sub);
667 break;
669 case FC_CHECKBOX:
670 case FC_RADIO:
671 if (!fs->state) break;
672 /* fall through */
674 case FC_SUBMIT:
675 case FC_HIDDEN:
676 case FC_RESET:
677 case FC_BUTTON:
678 sub = init_submitted_value(name, fs->value, type, fc,
679 position);
680 if (sub) add_to_list(*list, sub);
681 break;
683 case FC_SELECT:
684 if (!fc->nvalues) break;
686 fixup_select_state(fc, fs);
687 sub = init_submitted_value(name, fs->value, type, fc, position);
688 if (sub) add_to_list(*list, sub);
689 break;
691 case FC_IMAGE:
692 name = straconcat(fc->name, ".x", (unsigned char *) NULL);
693 if (!name) break;
694 sub = init_submitted_value(name, "0", type, fc, position);
695 mem_free(name);
696 if (sub) add_to_list(*list, sub);
698 name = straconcat(fc->name, ".y", (unsigned char *) NULL);
699 if (!name) break;
700 sub = init_submitted_value(name, "0", type, fc, position);
701 mem_free(name);
702 if (sub) add_to_list(*list, sub);
704 break;
708 static void
709 sort_submitted_values(LIST_OF(struct submitted_value) *list)
711 while (1) {
712 struct submitted_value *sub;
713 int changed = 0;
715 foreach (sub, *list) if (list_has_next(*list, sub))
716 if (sub->next->position < sub->position) {
717 struct submitted_value *next = sub->next;
719 del_from_list(sub);
720 add_at_pos(next, sub);
721 sub = next;
722 changed = 1;
725 foreachback (sub, *list) if (list_has_next(*list, sub))
726 if (sub->next->position < sub->position) {
727 struct submitted_value *next = sub->next;
729 del_from_list(sub);
730 add_at_pos(next, sub);
731 sub = next;
732 changed = 1;
735 if (!changed) break;
739 static void
740 get_successful_controls(struct document_view *doc_view,
741 struct form_control *fc,
742 LIST_OF(struct submitted_value) *list)
744 struct form_control *fc2;
746 assert(doc_view && fc && fc->form && list);
747 if_assert_failed return;
749 foreach (fc2, fc->form->items) {
750 if (((fc2->type != FC_SUBMIT &&
751 fc2->type != FC_IMAGE &&
752 fc2->type != FC_RESET &&
753 fc2->type != FC_BUTTON) || fc2 == fc)
754 && fc2->name && fc2->name[0]) {
755 struct form_state *fs = find_form_state(doc_view, fc2);
757 if (!fs) continue;
759 add_submitted_value_to_list(fc2, fs, list);
763 sort_submitted_values(list);
766 static void
767 encode_controls(LIST_OF(struct submitted_value) *l, struct string *data,
768 int cp_from, int cp_to)
770 struct submitted_value *sv;
771 struct conv_table *convert_table = NULL;
772 int lst = 0;
774 assert(l && data);
775 if_assert_failed return;
777 foreach (sv, *l) {
778 unsigned char *p2 = NULL;
780 if (lst)
781 add_char_to_string(data, '&');
782 else
783 lst = 1;
785 encode_uri_string(data, sv->name, strlen(sv->name), 1);
786 add_char_to_string(data, '=');
788 /* Convert back to original encoding (see html_form_control()
789 * for the original recoding). */
790 if (sv->type == FC_TEXTAREA) {
791 unsigned char *p;
793 p = encode_textarea(sv);
794 if (p) {
795 if (!convert_table)
796 convert_table = get_translation_table(cp_from, cp_to);
798 p2 = convert_string(convert_table, p,
799 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
800 mem_free(p);
802 } else if (sv->type == FC_TEXT ||
803 sv->type == FC_PASSWORD) {
804 if (!convert_table)
805 convert_table = get_translation_table(cp_from, cp_to);
807 p2 = convert_string(convert_table, sv->value,
808 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
809 } else {
810 p2 = stracpy(sv->value);
813 if (p2) {
814 encode_uri_string(data, p2, strlen(p2), 1);
815 mem_free(p2);
822 #define BOUNDARY_LENGTH 32
823 #define realloc_bound_ptrs(bptrs, bptrs_size) \
824 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
826 struct boundary_info {
827 int count;
828 int *offsets;
829 unsigned char string[BOUNDARY_LENGTH];
832 /** @relates boundary_info */
833 static void
834 randomize_boundary(unsigned char *data, int length)
836 int i;
837 FILE *f = fopen("/dev/urandom", "rb");
839 if (!f) f = fopen("/dev/prandom", "rb"); /* OpenBSD */
840 if (f) {
841 fread(data, 1, length, f);
842 fclose(f);
844 /* FIXME. What if both fails */
845 for (i = 0; i < length; i++) {
846 /* Only [0-9A-Za-z]. */
847 data[i] = data[i] & 63;
848 if (data[i] < 10) data[i] += '0';
849 else if (data[i] < 36) data[i] = data[i] - 10 + 'A';
850 else if (data[i] < 62) data[i] = data[i] - 36 + 'a';
851 else data[i] = '0';
855 /** @relates boundary_info */
856 static inline void
857 init_boundary(struct boundary_info *boundary)
859 memset(boundary, 0, sizeof(*boundary));
860 randomize_boundary(boundary->string, BOUNDARY_LENGTH);
863 /** Add boundary to string and save the offset
864 * @relates boundary_info */
865 static inline void
866 add_boundary(struct string *data, struct boundary_info *boundary)
868 add_to_string(data, "--");
870 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
871 boundary->offsets[boundary->count++] = data->length;
873 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
877 /** @todo FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
878 static void
879 encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
880 struct string *data, struct boundary_info *boundary,
881 LIST_OF(struct files_offset) *bfs, int cp_from, int cp_to)
883 struct conv_table *convert_table = NULL;
884 struct submitted_value *sv;
886 assert(ses && l && data && boundary);
887 if_assert_failed return;
889 init_boundary(boundary);
891 foreach (sv, *l) {
892 add_boundary(data, boundary);
893 add_crlf_to_string(data);
895 /** @bug FIXME: name is not encoded.
896 * from RFC 1867:
897 * multipart/form-data contains a series of parts.
898 * Each part is expected to contain a content-disposition
899 * header where the value is "form-data" and a name attribute
900 * specifies the field name within the form,
901 * e.g., 'content-disposition: form-data; name="xxxxx"',
902 * where xxxxx is the field name corresponding to that field.
903 * Field names originally in non-ASCII character sets may be
904 * encoded using the method outlined in RFC 1522. */
905 add_to_string(data, "Content-Disposition: form-data; name=\"");
906 add_to_string(data, sv->name);
907 add_char_to_string(data, '"');
909 if (sv->type == FC_FILE) {
910 unsigned char *extension;
912 add_to_string(data, "; filename=\"");
913 add_to_string(data, get_filename_position(sv->value));
914 /* It sends bad data if the file name contains ", but
915 Netscape does the same */
916 /* FIXME: We should follow RFCs 1522, 1867,
917 * 2047 (updated by rfc 2231), to provide correct support
918 * for non-ASCII and special characters in values. --Zas */
919 add_char_to_string(data, '"');
921 /* Add a Content-Type header if the type is configured */
922 extension = strrchr(sv->value, '.');
923 if (extension) {
924 unsigned char *type = get_extension_content_type(extension);
926 if (type) {
927 add_crlf_to_string(data);
928 add_to_string(data, "Content-Type: ");
929 add_to_string(data, type);
930 mem_free(type);
934 add_crlf_to_string(data);
935 add_crlf_to_string(data);
937 if (*sv->value) {
938 unsigned char *filename;
939 struct files_offset *bfs_new;
941 if (get_cmd_opt_bool("anonymous")) {
942 errno = EPERM;
943 goto encode_error;
946 filename = expand_tilde(sv->value);
947 if (!filename) goto encode_error;
949 if (access(filename, R_OK)) {
950 mem_free(filename);
951 goto encode_error;
953 bfs_new = mem_calloc(1, sizeof(*bfs_new));
954 if (!bfs_new) {
955 mem_free(filename);
956 goto encode_error;
958 bfs_new->begin = data->length;
959 add_char_to_string(data, FILE_CHAR);
960 add_to_string(data, filename);
961 add_char_to_string(data, FILE_CHAR);
962 bfs_new->end = data->length;
963 add_to_list_end(*bfs, bfs_new);
964 mem_free(filename);
966 } else {
967 add_crlf_to_string(data);
968 add_crlf_to_string(data);
970 /* Convert back to original encoding (see
971 * html_special_form_control() for the original
972 * recoding). */
973 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
974 sv->type == FC_TEXTAREA) {
975 unsigned char *p;
977 if (!convert_table)
978 convert_table = get_translation_table(cp_from,
979 cp_to);
981 p = convert_string(convert_table, sv->value,
982 strlen(sv->value), -1, CSM_FORM, NULL,
983 NULL, NULL);
984 if (p) {
985 add_to_string(data, p);
986 mem_free(p);
988 } else {
989 add_to_string(data, sv->value);
993 add_crlf_to_string(data);
996 /* End-boundary */
997 add_boundary(data, boundary);
998 add_to_string(data, "--\r\n");
1000 mem_free_if(boundary->offsets);
1001 return;
1003 encode_error:
1004 free_list(*bfs);
1005 mem_free_if(boundary->offsets);
1006 done_string(data);
1008 /* XXX: This error message should move elsewhere. --Zas */
1009 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1010 N_("Error while posting form"), ALIGN_CENTER,
1011 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1012 sv->value, strerror(errno)));
1015 static void
1016 encode_newlines(struct string *string, unsigned char *data)
1018 for (; *data; data++) {
1019 if (*data == '\n' || *data == '\r') {
1020 unsigned char buffer[3];
1022 /* Hex it. */
1023 buffer[0] = '%';
1024 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1025 buffer[2] = hx(((int) *data) & 0xF);
1026 add_bytes_to_string(string, buffer, 3);
1027 } else {
1028 add_char_to_string(string, *data);
1033 static void
1034 encode_text_plain(LIST_OF(struct submitted_value) *l, struct string *data,
1035 int cp_from, int cp_to)
1037 struct submitted_value *sv;
1038 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1040 assert(l && data);
1041 if_assert_failed return;
1043 foreach (sv, *l) {
1044 unsigned char *area51 = NULL;
1045 unsigned char *value = sv->value;
1047 add_to_string(data, sv->name);
1048 add_char_to_string(data, '=');
1050 switch (sv->type) {
1051 case FC_TEXTAREA:
1052 value = area51 = encode_textarea(sv);
1053 if (!area51) break;
1054 /* Fall through */
1055 case FC_TEXT:
1056 case FC_PASSWORD:
1057 /* Convert back to original encoding (see
1058 * html_form_control() for the original recoding). */
1059 value = convert_string(convert_table, value,
1060 strlen(value), -1, CSM_FORM,
1061 NULL, NULL, NULL);
1062 default:
1063 /* Falling right through to free that textarea stuff */
1064 mem_free_if(area51);
1066 /* Did the conversion fail? */
1067 if (!value) break;
1069 encode_newlines(data, value);
1071 /* Free if we did convert something */
1072 if (value != sv->value) mem_free(value);
1075 add_crlf_to_string(data);
1079 void
1080 do_reset_form(struct document_view *doc_view, struct form *form)
1082 struct form_control *fc;
1084 assert(doc_view && doc_view->document);
1085 if_assert_failed return;
1087 foreach (fc, form->items) {
1088 struct form_state *fs = find_form_state(doc_view, fc);
1090 if (fs) init_form_state(doc_view, fc, fs);
1094 enum frame_event_status
1095 reset_form(struct session *ses, struct document_view *doc_view, int a)
1097 struct link *link = get_current_link(doc_view);
1099 if (!link) return FRAME_EVENT_OK;
1101 do_reset_form(doc_view, get_link_form_control(link)->form);
1102 draw_forms(ses->tab->term, doc_view);
1104 /* Could be the refresh return value and then ditch the draw_forms()
1105 * call. */
1106 return FRAME_EVENT_OK;
1109 struct uri *
1110 get_form_uri(struct session *ses, struct document_view *doc_view,
1111 struct form_control *fc)
1113 struct boundary_info boundary;
1114 INIT_LIST_OF(struct submitted_value, submit);
1115 INIT_LIST_OF(struct files_offset, bfs);
1116 struct string data;
1117 struct string go;
1118 int cp_from, cp_to;
1119 struct uri *uri;
1120 struct form *form;
1122 assert(ses && ses->tab && ses->tab->term);
1123 if_assert_failed return NULL;
1124 assert(doc_view && doc_view->document && fc && fc->form);
1125 if_assert_failed return NULL;
1127 form = fc->form;
1129 if (fc->type == FC_RESET) {
1130 do_reset_form(doc_view, form);
1131 return NULL;
1134 if (!form->action
1135 || !init_string(&data))
1136 return NULL;
1138 get_successful_controls(doc_view, fc, &submit);
1140 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset", NULL);
1141 cp_to = doc_view->document->cp;
1142 switch (form->method) {
1143 case FORM_METHOD_GET:
1144 case FORM_METHOD_POST:
1145 encode_controls(&submit, &data, cp_from, cp_to);
1146 break;
1148 case FORM_METHOD_POST_MP:
1149 encode_multipart(ses, &submit, &data, &boundary,
1150 &bfs, cp_from, cp_to);
1151 break;
1153 case FORM_METHOD_POST_TEXT_PLAIN:
1154 encode_text_plain(&submit, &data, cp_from, cp_to);
1157 #ifdef CONFIG_FORMHIST
1158 /* XXX: We check data.source here because a NULL value can indicate
1159 * not only a memory allocation failure, but also an error reading
1160 * a file that is to be uploaded. TODO: Distinguish between
1161 * these two classes of errors (is it worth it?). -- Miciah */
1162 if (data.source
1163 && get_opt_bool("document.browse.forms.show_formhist", ses))
1164 memorize_form(ses, &submit, form);
1165 #endif
1167 done_submitted_value_list(&submit);
1169 if (!data.source
1170 || !init_string(&go)) {
1171 done_string(&data);
1172 return NULL;
1175 switch (form->method) {
1176 case FORM_METHOD_GET:
1178 unsigned char *pos = strchr(form->action, '#');
1180 if (pos) {
1181 add_bytes_to_string(&go, form->action, pos - form->action);
1182 } else {
1183 add_to_string(&go, form->action);
1186 if (strchr(go.source, '?'))
1187 add_char_to_string(&go, '&');
1188 else
1189 add_char_to_string(&go, '?');
1191 add_string_to_string(&go, &data);
1193 if (pos) add_to_string(&go, pos);
1194 break;
1196 case FORM_METHOD_POST:
1197 case FORM_METHOD_POST_MP:
1198 case FORM_METHOD_POST_TEXT_PLAIN:
1200 /* Note that we end content type here by a simple '\n',
1201 * replaced later by correct '\r\n' in http_send_header(). */
1203 add_to_string(&go, form->action);
1204 add_char_to_string(&go, POST_CHAR);
1205 if (form->method == FORM_METHOD_POST) {
1206 add_to_string(&go, "application/x-www-form-urlencoded\n");
1208 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1209 /* Dunno about this one but we don't want the full
1210 * hextcat thingy. --jonas */
1211 add_to_string(&go, "text/plain\n");
1212 add_to_string(&go, data.source);
1213 break;
1215 } else {
1216 add_to_string(&go, "multipart/form-data; boundary=");
1217 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1218 add_char_to_string(&go, '\n');
1221 if (list_empty(bfs)) {
1222 int i;
1224 for (i = 0; i < data.length; i++) {
1225 unsigned char p[3];
1227 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1228 add_to_string(&go, p);
1230 } else {
1231 struct files_offset *b;
1232 int i = 0;
1234 foreach (b, bfs) {
1235 for (; i < b->begin; i++) {
1236 unsigned char p[3];
1238 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1239 add_to_string(&go, p);
1241 add_bytes_to_string(&go, data.source + i, b->end - b->begin);
1242 i = b->end;
1244 for (; i < data.length; i++) {
1245 unsigned char p[3];
1247 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1248 add_to_string(&go, p);
1254 done_string(&data);
1256 uri = get_uri(go.source, 0);
1257 done_string(&go);
1258 if (uri) {
1259 uri->form = 1;
1261 free_list(bfs);
1263 return uri;
1266 #undef BOUNDARY_LENGTH
1269 enum frame_event_status
1270 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1272 goto_current_link(ses, doc_view, do_reload);
1273 return FRAME_EVENT_OK;
1276 void
1277 submit_given_form(struct session *ses, struct document_view *doc_view,
1278 struct form *form, int do_reload)
1280 /* Added support for submitting forms in hidden
1281 * links in 1.285, commented code can safely be removed once we have made sure the new
1282 * code does the right thing. */
1283 #if 0
1285 struct document *document = doc_view->document;
1286 int link;
1288 for (link = 0; link < document->nlinks; link++) {
1289 struct form_control *fc = get_link_form_control(&document->links[link]);
1291 if (fc && fc->form == form) {
1292 doc_view->vs->current_link = link;
1293 submit_form(ses, doc_view, 0);
1294 return;
1297 #endif
1298 if (!list_empty(form->items)) {
1299 struct form_control *fc = (struct form_control *)form->items.next;
1300 struct uri *uri;
1301 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1303 if (!fc) return;
1304 uri = get_form_uri(ses, doc_view, fc);
1305 if (!uri) return;
1306 goto_uri_frame(ses, uri, form->target, mode);
1307 done_uri(uri);
1311 void
1312 auto_submit_form(struct session *ses)
1314 struct document *document = ses->doc_view->document;
1316 if (!list_empty(document->forms))
1317 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1321 /* menu_func_T */
1322 static void
1323 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1325 unsigned char *filename = filename_;
1326 struct form_state *fs = fs_;
1328 /* The menu code doesn't free the filename data */
1329 mem_free_set(&fs->value, filename);
1330 fs->state = strlen(filename);
1331 redraw_terminal(term);
1334 /* menu_func_T */
1335 static void
1336 file_form_menu(struct terminal *term, void *path_, void *fs_)
1338 unsigned char *path = path_;
1339 struct form_state *fs = fs_;
1341 /* FIXME: It doesn't work for ../../ */
1342 #if 0
1343 int valuelen = strlen(fs->value);
1344 int pathlen = strlen(path);
1345 int no_elevator = 0;
1347 /* Don't add elevators for subdirs menus */
1348 /* It is not perfect at all because fs->value is not updated for each
1349 * newly opened file menu. Maybe it should be dropped. */
1350 for (; valuelen < pathlen; valuelen++) {
1351 if (dir_sep(path[valuelen - 1])) {
1352 no_elevator = 1;
1353 break;
1356 #endif
1358 auto_complete_file(term, 0 /* no_elevator */, path,
1359 set_file_form_state,
1360 file_form_menu, fs);
1364 enum frame_event_status
1365 field_op(struct session *ses, struct document_view *doc_view,
1366 struct link *link, struct term_event *ev)
1368 struct form_control *fc;
1369 struct form_state *fs;
1370 enum edit_action action_id;
1371 unsigned char *text;
1372 int length;
1373 enum frame_event_status status = FRAME_EVENT_REFRESH;
1374 #ifdef CONFIG_UTF8
1375 const unsigned char *ctext;
1376 int utf8 = ses->tab->term->utf8_cp;
1377 #endif /* CONFIG_UTF8 */
1379 assert(ses && doc_view && link && ev);
1380 if_assert_failed return FRAME_EVENT_OK;
1382 fc = get_link_form_control(link);
1383 assertm(fc != NULL, "link has no form control");
1384 if_assert_failed return FRAME_EVENT_OK;
1386 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1387 || (ses->insert_mode == INSERT_MODE_OFF
1388 && !(get_kbd_modifier(ev) & KBD_MOD_PASTE)))
1389 return FRAME_EVENT_IGNORED;
1391 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1393 fs = find_form_state(doc_view, fc);
1394 if (!fs || !fs->value) return FRAME_EVENT_OK;
1396 switch (action_id) {
1397 case ACT_EDIT_LEFT:
1398 #ifdef CONFIG_UTF8
1399 if (fc->type == FC_TEXTAREA) {
1400 status = textarea_op_left(fs, fc, utf8);
1401 break;
1403 if (utf8) {
1404 unsigned char *new_value;
1406 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1407 fs->state = new_value - fs->value;
1408 } else
1409 #endif /* CONFIG_UTF8 */
1410 fs->state = int_max(fs->state - 1, 0);
1411 break;
1412 case ACT_EDIT_RIGHT:
1413 #ifdef CONFIG_UTF8
1414 if (fc->type == FC_TEXTAREA) {
1415 status = textarea_op_right(fs, fc, utf8);
1416 break;
1418 if (utf8) {
1419 unsigned char *text = fs->value + fs->state;
1420 unsigned char *end = strchr(text, '\0');
1422 utf8_to_unicode(&text, end);
1423 fs->state = (int)(text - fs->value);
1424 } else
1425 #endif /* CONFIG_UTF8 */
1426 fs->state = int_min(fs->state + 1, strlen(fs->value));
1427 break;
1428 case ACT_EDIT_HOME:
1429 #ifdef CONFIG_UTF8
1430 if (fc->type == FC_TEXTAREA) {
1431 status = textarea_op_home(fs, fc, utf8);
1432 } else {
1433 fs->state = 0;
1435 #else
1436 if (fc->type == FC_TEXTAREA) {
1437 status = textarea_op_home(fs, fc);
1438 } else {
1439 fs->state = 0;
1442 #endif /* CONFIG_UTF8 */
1443 break;
1444 case ACT_EDIT_UP:
1445 if (fc->type != FC_TEXTAREA)
1446 status = FRAME_EVENT_IGNORED;
1447 else
1448 #ifdef CONFIG_UTF8
1449 status = textarea_op_up(fs, fc, utf8);
1450 #else
1451 status = textarea_op_up(fs, fc);
1452 #endif /* CONFIG_UTF8 */
1453 break;
1454 case ACT_EDIT_DOWN:
1455 if (fc->type != FC_TEXTAREA)
1456 status = FRAME_EVENT_IGNORED;
1457 else
1458 #ifdef CONFIG_UTF8
1459 status = textarea_op_down(fs, fc, utf8);
1460 #else
1461 status = textarea_op_down(fs, fc);
1462 #endif /* CONFIG_UTF8 */
1463 break;
1464 case ACT_EDIT_END:
1465 if (fc->type == FC_TEXTAREA) {
1466 #ifdef CONFIG_UTF8
1467 status = textarea_op_end(fs, fc, utf8);
1468 #else
1469 status = textarea_op_end(fs, fc);
1470 #endif /* CONFIG_UTF8 */
1471 } else {
1472 fs->state = strlen(fs->value);
1474 break;
1475 case ACT_EDIT_BEGINNING_OF_BUFFER:
1476 if (fc->type == FC_TEXTAREA) {
1477 #ifdef CONFIG_UTF8
1478 status = textarea_op_bob(fs, fc, utf8);
1479 fs->state_cell = 0;
1480 #else
1481 status = textarea_op_bob(fs, fc);
1482 #endif /* CONFIG_UTF8 */
1483 } else {
1484 fs->state = 0;
1486 break;
1487 case ACT_EDIT_END_OF_BUFFER:
1488 if (fc->type == FC_TEXTAREA) {
1489 #ifdef CONFIG_UTF8
1490 status = textarea_op_eob(fs, fc, utf8);
1491 #else
1492 status = textarea_op_eob(fs, fc);
1493 #endif /* CONFIG_UTF8 */
1494 } else {
1495 fs->state = strlen(fs->value);
1497 break;
1498 case ACT_EDIT_OPEN_EXTERNAL:
1499 if (form_field_is_readonly(fc))
1500 status = FRAME_EVENT_IGNORED;
1501 else if (fc->type == FC_TEXTAREA)
1502 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1503 break;
1504 case ACT_EDIT_COPY_CLIPBOARD:
1505 set_clipboard_text(fs->value);
1506 status = FRAME_EVENT_OK;
1507 break;
1508 case ACT_EDIT_CUT_CLIPBOARD:
1509 set_clipboard_text(fs->value);
1510 if (!form_field_is_readonly(fc))
1511 fs->value[0] = 0;
1512 fs->state = 0;
1513 #ifdef CONFIG_UTF8
1514 if (fc->type == FC_TEXTAREA)
1515 fs->state_cell = 0;
1516 #endif /* CONFIG_UTF8 */
1517 break;
1518 case ACT_EDIT_PASTE_CLIPBOARD:
1519 if (form_field_is_readonly(fc)) break;
1521 text = get_clipboard_text();
1522 if (!text) break;
1524 length = strlen(text);
1525 if (length <= fc->maxlength) {
1526 unsigned char *v = mem_realloc(fs->value, length + 1);
1528 if (v) {
1529 fs->value = v;
1530 memmove(v, text, length + 1);
1531 fs->state = strlen(fs->value);
1532 #ifdef CONFIG_UTF8
1533 if (utf8 && fc->type == FC_TEXTAREA)
1534 fs->state_cell = 0;
1535 #endif /* CONFIG_UTF8 */
1538 mem_free(text);
1539 break;
1540 case ACT_EDIT_ENTER:
1541 if (fc->type == FC_TEXTAREA) {
1542 #ifdef CONFIG_UTF8
1543 status = textarea_op_enter(fs, fc, utf8);
1544 #else
1545 status = textarea_op_enter(fs, fc);
1546 #endif /* CONFIG_UTF8 */
1547 break;
1550 /* Set status to ok if either it is not possible to
1551 * submit the form or the posting fails. */
1552 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1553 if ((has_form_submit(fc->form)
1554 && !get_opt_bool("document.browse.forms.auto_submit", ses))
1555 || goto_current_link(ses, doc_view, 0)) {
1556 if (ses->insert_mode == INSERT_MODE_ON)
1557 ses->insert_mode = INSERT_MODE_OFF;
1558 status = FRAME_EVENT_OK;
1560 break;
1561 case ACT_EDIT_BACKSPACE:
1562 if (form_field_is_readonly(fc)) {
1563 status = FRAME_EVENT_IGNORED;
1564 break;
1567 if (!fs->state) {
1568 status = FRAME_EVENT_OK;
1569 break;
1571 #ifdef CONFIG_UTF8
1572 if (utf8) {
1573 int old_state = fs->state;
1574 unsigned char *new_value;
1576 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1577 fs->state = new_value - fs->value;
1579 if (old_state != fs->state) {
1580 if (fc->type == FC_TEXTAREA)
1581 fs->state_cell = 0;
1582 length = strlen(fs->value + old_state) + 1;
1583 memmove(new_value, fs->value + old_state, length);
1585 } else
1586 #endif /* CONFIG_UTF8 */
1588 length = strlen(fs->value + fs->state) + 1;
1589 text = fs->value + fs->state;
1591 memmove(text - 1, text, length);
1592 fs->state--;
1594 break;
1595 case ACT_EDIT_DELETE:
1596 if (form_field_is_readonly(fc)) {
1597 status = FRAME_EVENT_IGNORED;
1598 break;
1601 length = strlen(fs->value);
1602 if (fs->state >= length) {
1603 status = FRAME_EVENT_OK;
1604 break;
1606 #ifdef CONFIG_UTF8
1607 if (utf8) {
1608 unsigned char *end = fs->value + length;
1609 unsigned char *text = fs->value + fs->state;
1610 unsigned char *old = text;
1612 utf8_to_unicode(&text, end);
1613 if (old != text) {
1614 memmove(old, text,
1615 (int)(end - text) + 1);
1617 break;
1619 #endif /* CONFIG_UTF8 */
1620 text = fs->value + fs->state;
1622 memmove(text, text + 1, length - fs->state);
1623 break;
1624 case ACT_EDIT_KILL_TO_BOL:
1625 if (form_field_is_readonly(fc)) {
1626 status = FRAME_EVENT_IGNORED;
1627 break;
1630 if (fs->state <= 0) {
1631 status = FRAME_EVENT_OK;
1632 break;
1635 text = memrchr(fs->value, ASCII_LF, fs->state);
1636 if (text) {
1637 /* Leave the new-line character if it does not
1638 * immediately precede the cursor. */
1639 if (text != &fs->value[fs->state - 1])
1640 text++;
1641 } else {
1642 text = fs->value;
1645 length = strlen(fs->value + fs->state) + 1;
1646 memmove(text, fs->value + fs->state, length);
1648 fs->state = (int) (text - fs->value);
1649 #ifdef CONFIG_UTF8
1650 if (utf8) {
1651 if (fc->type == FC_TEXTAREA)
1652 fs->state_cell = 0;
1654 #endif /* CONFIG_UTF8 */
1655 break;
1656 case ACT_EDIT_KILL_TO_EOL:
1657 if (form_field_is_readonly(fc)) {
1658 status = FRAME_EVENT_IGNORED;
1659 break;
1662 if (!fs->value[fs->state]) {
1663 status = FRAME_EVENT_OK;
1664 break;
1667 text = strchr(fs->value + fs->state, ASCII_LF);
1668 if (!text) {
1669 fs->value[fs->state] = '\0';
1670 break;
1673 if (fs->value[fs->state] == ASCII_LF)
1674 ++text;
1676 memmove(fs->value + fs->state, text, strlen(text) + 1);
1677 break;
1679 case ACT_EDIT_KILL_WORD_BACK:
1680 if (form_field_is_readonly(fc)) {
1681 status = FRAME_EVENT_IGNORED;
1682 break;
1685 if (fs->state <= 0) {
1686 status = FRAME_EVENT_OK;
1687 break;
1690 text = &fs->value[fs->state];
1691 while (text > fs->value && isspace(*(text - 1)))
1692 --text;
1693 while (text > fs->value && !isspace(*(text - 1)))
1694 --text;
1695 if (*text == ASCII_LF
1696 && text != &fs->value[fs->state - 1])
1697 text++;
1699 length = strlen(fs->value + fs->state) + 1;
1700 memmove(text, fs->value + fs->state, length);
1702 fs->state = (int) (text - fs->value);
1703 break;
1705 case ACT_EDIT_MOVE_BACKWARD_WORD:
1706 while (fs->state > 0
1707 && isspace(fs->value[fs->state - 1]))
1708 --fs->state;
1709 while (fs->state > 0
1710 && !isspace(fs->value[fs->state - 1]))
1711 --fs->state;
1712 break;
1714 case ACT_EDIT_MOVE_FORWARD_WORD:
1715 while (isspace(fs->value[fs->state]))
1716 ++fs->state;
1717 while (fs->value[fs->state]
1718 && !isspace(fs->value[fs->state]))
1719 ++fs->state;
1720 while (isspace(fs->value[fs->state]))
1721 ++fs->state;
1722 break;
1724 case ACT_EDIT_AUTO_COMPLETE:
1725 if (fc->type != FC_FILE
1726 || form_field_is_readonly(fc)) {
1727 status = FRAME_EVENT_IGNORED;
1728 break;
1731 file_form_menu(ses->tab->term, fs->value, fs);
1732 break;
1734 case ACT_EDIT_CANCEL:
1735 if (ses->insert_mode == INSERT_MODE_ON)
1736 ses->insert_mode = INSERT_MODE_OFF;
1737 else
1738 status = FRAME_EVENT_IGNORED;
1739 break;
1741 case ACT_EDIT_REDRAW:
1742 redraw_terminal_cls(ses->tab->term);
1743 status = FRAME_EVENT_OK;
1744 break;
1746 default:
1747 if (!check_kbd_textinput_key(ev)) {
1748 status = FRAME_EVENT_IGNORED;
1749 break;
1752 if (form_field_is_readonly(fc)
1753 #ifndef CONFIG_UTF8
1754 || strlen(fs->value) >= fc->maxlength
1755 || !insert_in_string(&fs->value, fs->state, "?", 1)
1756 #endif /* CONFIG_UTF8 */
1759 status = FRAME_EVENT_OK;
1760 break;
1763 #ifdef CONFIG_UTF8
1764 /* fs->value is in the charset of the terminal. */
1765 ctext = u2cp_no_nbsp(get_kbd_key(ev),
1766 get_opt_codepage_tree(ses->tab->term->spec,
1767 "charset",
1768 NULL));
1769 length = strlen(ctext);
1771 if (strlen(fs->value) + length > fc->maxlength
1772 || !insert_in_string(&fs->value, fs->state, ctext, length)) {
1773 status = FRAME_EVENT_OK;
1774 break;
1777 fs->state += length;
1778 if (fc->type == FC_TEXTAREA)
1779 fs->state_cell = 0;
1780 #else
1781 fs->value[fs->state++] = get_kbd_key(ev);
1782 #endif /* CONFIG_UTF8 */
1783 break;
1786 return status;
1789 static unsigned char *
1790 get_form_label(struct form_control *fc)
1792 assert(fc->form);
1793 switch (fc->type) {
1794 case FC_RESET:
1795 return N_("Reset form");
1796 case FC_BUTTON:
1797 return N_("Harmless button");
1798 case FC_HIDDEN:
1799 return NULL;
1800 case FC_SUBMIT:
1801 case FC_IMAGE:
1802 if (!fc->form->action) return NULL;
1804 if (fc->form->method == FORM_METHOD_GET)
1805 return N_("Submit form to");
1806 return N_("Post form to");
1807 case FC_RADIO:
1808 return N_("Radio button");
1809 case FC_CHECKBOX:
1810 return N_("Checkbox");
1811 case FC_SELECT:
1812 return N_("Select field");
1813 case FC_TEXT:
1814 return N_("Text field");
1815 case FC_TEXTAREA:
1816 return N_("Text area");
1817 case FC_FILE:
1818 return N_("File upload");
1819 case FC_PASSWORD:
1820 return N_("Password field");
1823 return NULL;
1826 static inline void
1827 add_form_attr_to_string(struct string *string, struct terminal *term,
1828 unsigned char *name, unsigned char *value)
1830 add_to_string(string, ", ");
1831 add_to_string(string, _(name, term));
1832 if (value) {
1833 add_char_to_string(string, ' ');
1834 add_to_string(string, value);
1838 unsigned char *
1839 get_form_info(struct session *ses, struct document_view *doc_view)
1841 struct terminal *term = ses->tab->term;
1842 struct link *link = get_current_link(doc_view);
1843 struct form_control *fc;
1844 unsigned char *label, *key;
1845 struct string str;
1847 assert(link);
1849 fc = get_link_form_control(link);
1850 label = get_form_label(fc);
1851 if (!label) return NULL;
1853 if (!init_string(&str)) return NULL;
1855 add_to_string(&str, _(label, term));
1857 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1858 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1861 switch (fc->type) {
1862 case FC_CHECKBOX:
1863 case FC_RADIO:
1865 struct form_state *fs = find_form_state(doc_view, fc);
1867 if (!fs->value || !fs->value[0])
1868 break;
1870 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1871 break;
1874 case FC_TEXT:
1875 case FC_PASSWORD:
1876 case FC_FILE:
1877 case FC_TEXTAREA:
1879 struct uri *uri;
1880 unsigned char *uristring;
1882 if (form_field_is_readonly(fc)) {
1883 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1886 /* Should we add info about entering insert mode or add info
1887 * about submitting the form? */
1888 if (ses->insert_mode == INSERT_MODE_OFF) {
1889 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1891 if (!key) break;
1893 if (form_field_is_readonly(fc))
1894 label = N_("press %s to navigate");
1895 else
1896 label = N_("press %s to edit");
1898 add_to_string(&str, " (");
1899 add_format_to_string(&str, _(label, term), key);
1900 add_char_to_string(&str, ')');
1901 mem_free(key);
1902 break;
1906 if (fc->type == FC_TEXTAREA)
1907 break;
1909 assert(fc->form);
1911 if (!fc->form->action
1912 || (has_form_submit(fc->form)
1913 && !get_opt_bool("document.browse.forms.auto_submit",
1914 ses)))
1915 break;
1917 uri = get_uri(fc->form->action, 0);
1918 if (!uri) break;
1920 /* Add the uri with password and post info stripped */
1921 uristring = get_uri_string(uri, URI_PUBLIC);
1922 done_uri(uri);
1924 if (!uristring) break;
1926 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1927 if (!key) {
1928 mem_free(uristring);
1929 break;
1932 if (fc->form->method == FORM_METHOD_GET)
1933 label = N_("press %s to submit to %s");
1934 else
1935 label = N_("press %s to post to %s");
1937 add_to_string(&str, " (");
1938 add_format_to_string(&str, _(label, term), key, uristring);
1939 mem_free(uristring);
1940 mem_free(key);
1942 add_char_to_string(&str, ')');
1943 break;
1945 case FC_SUBMIT:
1946 case FC_IMAGE:
1947 add_char_to_string(&str, ' ');
1949 assert(fc->form);
1950 /* Add the uri with password and post info stripped */
1951 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
1952 break;
1954 case FC_HIDDEN:
1955 case FC_RESET:
1956 case FC_BUTTON:
1957 case FC_SELECT:
1958 break;
1961 if (link->accesskey
1962 && get_opt_bool("document.browse.accesskey.display", ses)) {
1963 add_to_string(&str, " (");
1964 add_accesskey_to_string(&str, link->accesskey);
1965 add_char_to_string(&str, ')');
1968 return str.source;
1971 static void
1972 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
1974 struct session *ses = ses_;
1975 struct document_view *doc_view;
1976 int link_number = *(int *) link_number_;
1978 mem_free(link_number_);
1980 assert(term && ses);
1981 if_assert_failed return;
1983 doc_view = current_frame(ses);
1984 if (!doc_view) return;
1986 assert(doc_view->vs && doc_view->document);
1987 if_assert_failed return;
1989 jump_to_link_number(ses, doc_view, link_number);
1990 refresh_view(ses, doc_view, 0);
1993 void
1994 link_form_menu(struct session *ses)
1996 struct document_view *doc_view;
1997 struct link *link;
1998 struct menu_item *mi;
1999 struct form_control *fc;
2000 struct form *form;
2002 assert(ses);
2003 if_assert_failed return;
2005 doc_view = current_frame(ses);
2006 if (!doc_view) return;
2008 assert(doc_view->vs && doc_view->document);
2009 if_assert_failed return;
2011 link = get_current_link(doc_view);
2012 if (!link) return;
2014 assert(link_is_form(link));
2016 fc = get_link_form_control(link);
2017 if (!fc) return;
2019 form = fc->form;
2021 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2022 if (!mi) return;
2024 foreach (fc, form->items) {
2025 unsigned char *text;
2026 unsigned char *rtext;
2027 int link_number;
2028 struct string str;
2030 switch (fc->type) {
2031 case FC_HIDDEN:
2032 continue;
2034 case FC_SUBMIT:
2035 case FC_IMAGE:
2036 if (!form->action)
2037 text = N_("Useless button");
2038 else
2039 text = N_("Submit button");
2040 break;
2042 default:
2043 text = get_form_label(fc);
2046 link_number = get_form_control_link(doc_view->document, fc);
2047 if (link_number < 0
2048 || !init_string(&str))
2049 continue;
2051 assert(text);
2052 add_to_string(&str, _(text, ses->tab->term));
2054 rtext = fc->name;
2055 if (!rtext) rtext = fc->alt;
2057 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2058 link_form_menu_func, intdup(link_number),
2059 FREE_DATA);
2062 do_menu(ses->tab->term, mi, ses, 1);