Strings corrections from Malcolm Parsons
[elinks.git] / src / viewer / text / form.c
blobbc8ef21270f9075880817bc049fdc64f0a0a34d3
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 "intl/gettext/libintl.h"
35 #include "formhist/formhist.h"
36 #include "mime/mime.h"
37 #include "osdep/ascii.h"
38 #include "osdep/osdep.h"
39 #include "protocol/uri.h"
40 #include "session/session.h"
41 #include "session/task.h"
42 #include "terminal/kbd.h"
43 #include "terminal/terminal.h"
44 #include "terminal/window.h"
45 #include "util/conv.h"
46 #include "util/error.h"
47 #include "util/file.h"
48 #include "util/memory.h"
49 #include "util/string.h"
50 #include "viewer/action.h"
51 #include "viewer/text/draw.h"
52 #include "viewer/text/form.h"
53 #include "viewer/text/link.h"
54 #include "viewer/text/textarea.h"
55 #include "viewer/text/view.h"
56 #include "viewer/text/vs.h"
59 /* TODO: Some of these (particulary those encoding routines) would feel better
60 * in viewer/common/. --pasky */
62 /** @relates submitted_value */
63 struct submitted_value *
64 init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
65 struct form_control *fc, int position)
67 struct submitted_value *sv;
69 sv = mem_alloc(sizeof(*sv));
70 if (!sv) return NULL;
72 sv->value = stracpy(value);
73 if (!sv->value) { mem_free(sv); return NULL; }
75 sv->name = stracpy(name);
76 if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
78 sv->type = type;
79 sv->form_control = fc;
80 sv->position = position;
82 return sv;
85 /** @relates submitted_value */
86 void
87 done_submitted_value(struct submitted_value *sv)
89 if (!sv) return;
90 mem_free_if(sv->value);
91 mem_free_if(sv->name);
92 mem_free(sv);
95 static void
96 fixup_select_state(struct form_control *fc, struct form_state *fs)
98 int i;
100 assert(fc && fs);
101 if_assert_failed return;
103 if (fs->state >= 0
104 && fs->state < fc->nvalues
105 && !strcmp(fc->values[fs->state], fs->value))
106 return;
108 for (i = 0; i < fc->nvalues; i++)
109 if (!strcmp(fc->values[i], fs->value)) {
110 fs->state = i;
111 return;
114 fs->state = 0;
116 mem_free_set(&fs->value, stracpy(fc->nvalues
117 ? fc->values[0]
118 : (unsigned char *) ""));
121 /* menu_func_T */
122 void
123 selected_item(struct terminal *term, void *item_, void *ses_)
125 struct session *ses = ses_;
126 int item = (long) item_;
127 struct document_view *doc_view;
128 struct link *link;
129 struct form_state *fs;
130 struct form_control *fc;
132 assert(term && ses);
133 if_assert_failed return;
134 doc_view = current_frame(ses);
136 assert(doc_view && doc_view->vs && doc_view->document);
137 if_assert_failed return;
139 link = get_current_link(doc_view);
140 if (!link || link->type != LINK_SELECT) return;
142 fc = get_link_form_control(link);
143 fs = find_form_state(doc_view, fc);
144 if (fs) {
145 if (item >= 0 && item < fc->nvalues) {
146 fs->state = item;
147 mem_free_set(&fs->value, stracpy(fc->values[item]));
149 fixup_select_state(fc, fs);
152 refresh_view(ses, doc_view, 0);
155 static void
156 init_form_state(struct document_view *doc_view,
157 struct form_control *fc, struct form_state *fs)
159 struct terminal *term;
160 int doc_cp, viewer_cp;
162 assert(fc && fs);
163 if_assert_failed return;
165 doc_cp = doc_view->document->cp;
166 term = doc_view->session->tab->term;
167 viewer_cp = get_opt_codepage_tree(term->spec, "charset");
169 mem_free_set(&fs->value, NULL);
171 switch (fc->type) {
172 case FC_TEXT:
173 case FC_PASSWORD:
174 #ifdef CONFIG_FORMHIST
175 fs->value = null_or_stracpy(
176 get_form_history_value(
177 fc->form->action, fc->name));
178 #endif /* CONFIG_FORMHIST */
179 /* fall through */
180 case FC_TEXTAREA:
181 if (fs->value == NULL) {
182 fs->value = convert_string(
183 get_translation_table(doc_cp, viewer_cp),
184 fc->default_value,
185 strlen(fc->default_value),
186 viewer_cp, CSM_FORM,
187 &fs->state, NULL, NULL);
189 fs->state = fs->value ? strlen(fs->value) : 0;
190 #ifdef CONFIG_UTF8
191 if (fc->type == FC_TEXTAREA)
192 fs->state_cell = 0;
193 #endif /* CONFIG_UTF8 */
194 fs->vpos = 0;
195 break;
196 case FC_FILE:
197 fs->value = stracpy("");
198 fs->state = 0;
199 fs->vpos = 0;
200 break;
201 case FC_SELECT:
202 fs->value = convert_string(
203 get_translation_table(doc_cp, viewer_cp),
204 fc->default_value,
205 strlen(fc->default_value),
206 viewer_cp, CSM_FORM,
207 &fs->state, NULL, NULL);
208 fs->state = fc->default_state;
209 fixup_select_state(fc, fs);
210 break;
211 case FC_CHECKBOX:
212 case FC_RADIO:
213 fs->state = fc->default_state;
214 /* Fall-through */
215 case FC_SUBMIT:
216 case FC_IMAGE:
217 case FC_RESET:
218 case FC_BUTTON:
219 case FC_HIDDEN:
220 /* We don't want to recode hidden fields. */
221 fs->value = stracpy(fc->default_value);
222 break;
227 struct form_state *
228 find_form_state(struct document_view *doc_view, struct form_control *fc)
230 struct view_state *vs;
231 struct form_state *fs;
232 int n;
234 assert(doc_view && doc_view->vs && fc);
235 if_assert_failed return NULL;
237 vs = doc_view->vs;
238 n = fc->g_ctrl_num;
240 if (n >= vs->form_info_len) {
241 int nn = n + 1;
243 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
244 if (!fs) return NULL;
245 vs->form_info = fs;
246 vs->form_info_len = nn;
248 fs = &vs->form_info[n];
250 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
251 && fs->g_ctrl_num == fc->g_ctrl_num
252 && fs->position == fc->position
253 && fs->type == fc->type)
254 return fs;
256 mem_free_if(fs->value);
257 memset(fs, 0, sizeof(*fs));
258 fs->form_view = find_form_view(doc_view, fc->form);
259 fs->g_ctrl_num = fc->g_ctrl_num;
260 fs->position = fc->position;
261 fs->type = fc->type;
262 init_form_state(doc_view, fc, fs);
264 return fs;
267 struct form_control *
268 find_form_control(struct document *document, struct form_state *fs)
270 struct form *form = find_form_by_form_view(document, fs->form_view);
271 struct form_control *fc;
273 foreach (fc, form->items) {
274 if (fs->g_ctrl_num == fc->g_ctrl_num
275 && fs->position == fc->position
276 && fs->type == fc->type)
277 return fc;
280 return NULL;
283 struct form_view *
284 find_form_view_in_vs(struct view_state *vs, int form_num)
286 struct form_view *fv;
288 assert(vs);
290 foreach (fv, vs->forms)
291 if (fv->form_num == form_num)
292 return fv;
294 fv = mem_calloc(1, sizeof(*fv));
295 fv->form_num = form_num;
296 add_to_list(vs->forms, fv);
297 return fv;
300 struct form_view *
301 find_form_view(struct document_view *doc_view, struct form *form)
303 return find_form_view_in_vs(doc_view->vs, form->form_num);
306 struct form *
307 find_form_by_form_view(struct document *document, struct form_view *fv)
309 struct form *form;
311 foreach (form, document->forms) {
312 if (form->form_num == fv->form_num)
313 return form;
315 return NULL;
320 get_current_state(struct session *ses)
322 struct document_view *doc_view;
323 struct link *link;
324 struct form_state *fs;
326 assert(ses);
327 if_assert_failed return -1;
328 doc_view = current_frame(ses);
330 assert(doc_view && doc_view->vs && doc_view->document);
331 if_assert_failed return -1;
333 link = get_current_link(doc_view);
334 if (!link || link->type != LINK_SELECT) return -1;
336 fs = find_form_state(doc_view, get_link_form_control(link));
337 if (fs) return fs->state;
338 return -1;
341 void
342 draw_form_entry(struct terminal *term, struct document_view *doc_view,
343 struct link *link)
345 struct form_state *fs;
346 struct form_control *fc;
347 struct view_state *vs;
348 struct box *box;
349 int dx, dy;
351 assert(term && doc_view && doc_view->document && doc_view->vs && link);
352 if_assert_failed return;
354 fc = get_link_form_control(link);
355 assertm(fc != NULL, "link %d has no form control", (int) (link - doc_view->document->links));
356 if_assert_failed return;
358 fs = find_form_state(doc_view, fc);
359 if (!fs) return;
361 box = &doc_view->box;
362 vs = doc_view->vs;
363 dx = box->x - vs->x;
364 dy = box->y - vs->y;
365 switch (fc->type) {
366 unsigned char *s;
367 #ifdef CONFIG_UTF8
368 unsigned char *text, *end, *last_in_view;
369 int retried;
370 #endif /* CONFIG_UTF8 */
371 int len;
372 int i, x, y;
374 case FC_TEXT:
375 case FC_PASSWORD:
376 case FC_FILE:
377 if (!link->npoints) break;
379 y = link->points[0].y + dy;
380 if (!row_is_in_box(box, y))
381 break;
383 x = link->points[0].x + dx;
384 #ifdef CONFIG_UTF8
385 if (term->utf8_cp) goto utf8;
386 #endif /* CONFIG_UTF8 */
387 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
388 len = strlen(fs->value) - fs->vpos;
390 for (i = 0; i < fc->size; i++, x++) {
391 unsigned char data;
393 if (!col_is_in_box(box, x)) continue;
395 if (fs->value && i >= -fs->vpos && i < len)
396 data = fc->type != FC_PASSWORD
397 ? fs->value[i + fs->vpos] : '*';
398 else
399 data = '_';
401 draw_char_data(term, x, y, data);
403 break;
404 #ifdef CONFIG_UTF8
405 utf8:
406 retried = 0;
408 retry_after_scroll:
409 text = fs->value;
410 if (!text) text = "";
411 len = strlen(text);
412 int_bounds(&fs->state, 0, len);
413 int_bounds(&fs->vpos, 0, fs->state);
414 end = text + len;
415 text += fs->vpos;
416 last_in_view = NULL;
418 for (i = 0; i < fc->size; ) {
419 unicode_val_T data;
420 int cells, cell;
421 unsigned char *maybe_in_view = text;
423 data = utf8_to_unicode(&text, end);
424 if (data == UCS_NO_CHAR) /* end of string */
425 data = '_';
426 else if (fc->type == FC_PASSWORD)
427 data = '*';
429 cells = unicode_to_cell(data);
430 if (i + cells <= fc->size) {
431 last_in_view = maybe_in_view;
432 if (colspan_is_in_box(box, x + i, cells)) {
433 /* The character fits completely.
434 * Draw the character, and mark any
435 * further cells with UCS_NO_CHAR. */
436 draw_char_data(term, x + i, y, data);
437 for (cell = 1; cell < cells; cell++)
438 draw_char_data(term, x + i + cell,
439 y, UCS_NO_CHAR);
440 goto drew_char;
444 /* The character does not fit completely.
445 * Write UCS_ORPHAN_CELL to the cells that
446 * do fit. */
447 for (cell = 0; cell < cells; cell++) {
448 if (col_is_in_box(box, x + i + cell)
449 && i + cell < fc->size)
450 draw_char_data(term,
451 x + i + cell, y,
452 UCS_ORPHAN_CELL);
455 drew_char:
456 i += cells;
459 /* The int_bounds calls above ensured that the
460 * insertion point cannot be at the left side
461 * of the scrolled-visible part of the text.
462 * However it can still be at the right side.
463 * Check whether we need to change fs->vpos.
465 * This implementation attempts to follow
466 * these rules:
467 * - If the insertion point is at the end of
468 * the string, leave at least one empty cell
469 * so that there is a place for the cursor.
470 * - If a character follows the insertion
471 * point, make that character fully visible;
472 * note the character may be double-width.
473 * - If fc->size < 2, it is not possible to
474 * make a double-width character fully
475 * visible. In this case, it is OK if the
476 * output is ugly, but ELinks must not fall
477 * into an infinite loop or crash.
478 * - The length of the string should not affect
479 * how long this function takes. The width
480 * of the widget naturally will.
481 * - Optimize the case where fields are drawn
482 * several times without being modified.
484 * It follows that:
485 * - If the "for i" loop above hit UCS_NO_CHAR,
486 * then there is no need to scroll.
487 * - When the string ends with a double-width
488 * character that fits in only partially,
489 * then text==end, but the field may have
490 * to be scrolled. */
491 if (fs->value && last_in_view
492 && last_in_view < fs->value + fs->state) {
493 unsigned char *ptr = fs->value + fs->state;
494 int cells = fc->size;
495 enum utf8_step how = (fc->type == FC_PASSWORD)
496 ? UTF8_STEP_CHARACTERS
497 : UTF8_STEP_CELLS_FEWER;
499 /* The insertion point is at the right
500 * side of the scrolled-visible part
501 * of the text. Decide a new fs->vpos
502 * by counting cells backwards from
503 * @ptr. But first advance @ptr past
504 * the character that follows the
505 * insertion point, so that it will be
506 * fully displayed. If there is no
507 * such character, reserve one cell
508 * for the cursor anyway. */
509 if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
510 --cells;
511 ptr = utf8_step_backward(ptr, fs->value,
512 cells, how, NULL);
514 if (fs->vpos != ptr - fs->value) {
515 fs->vpos = ptr - fs->value;
516 retried = 1;
517 goto retry_after_scroll;
520 break;
521 #endif /* CONFIG_UTF8 */
522 case FC_TEXTAREA:
523 draw_textarea(term, fs, doc_view, link);
524 break;
525 case FC_CHECKBOX:
526 case FC_RADIO:
527 if (link->npoints < 2) break;
528 x = link->points[1].x + dx;
529 y = link->points[1].y + dy;
530 if (is_in_box(box, x, y))
531 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
532 break;
533 case FC_SELECT:
534 fixup_select_state(fc, fs);
535 if (fs->state < fc->nvalues)
536 s = fc->labels[fs->state];
537 else
538 /* XXX: when can this happen? --pasky */
539 s = "";
540 #ifdef CONFIG_UTF8
541 if (term->utf8_cp) goto utf8_select;
542 #endif /* CONFIG_UTF8 */
543 len = s ? strlen(s) : 0;
544 for (i = 0; i < link->npoints; i++) {
545 x = link->points[i].x + dx;
546 y = link->points[i].y + dy;
547 if (is_in_box(box, x, y))
548 draw_char_data(term, x, y, i < len ? s[i] : '_');
550 break;
551 #ifdef CONFIG_UTF8
552 utf8_select:
553 text = s;
554 end = strchr(s, '\0');
555 len = utf8_ptr2cells(text, end);
556 for (i = 0; i < link->npoints; i++) {
557 x = link->points[i].x + dx;
558 y = link->points[i].y + dy;
559 if (is_in_box(box, x, y)) {
560 unicode_val_T data;
561 if (i < len) {
562 int cell;
564 data = utf8_to_unicode(&s, end);
565 cell = unicode_to_cell(data);
566 if (i + 1 < len && cell == 2) {
567 draw_char_data(term, x++, y, data);
569 data = UCS_NO_CHAR;
570 i++;
571 } else if (cell == 2) {
572 data = UCS_ORPHAN_CELL;
574 } else
575 data = '_';
576 draw_char_data(term, x, y, data);
579 break;
580 #endif /* CONFIG_UTF8 */
581 case FC_SUBMIT:
582 case FC_IMAGE:
583 case FC_RESET:
584 case FC_BUTTON:
585 case FC_HIDDEN:
586 break;
590 void
591 draw_forms(struct terminal *term, struct document_view *doc_view)
593 struct link *l1, *l2;
595 assert(term && doc_view);
596 if_assert_failed return;
598 l1 = get_first_link(doc_view);
599 l2 = get_last_link(doc_view);
601 if (!l1 || !l2) {
602 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
603 /* Return path :-). */
604 return;
606 do {
607 struct form_control *fc = get_link_form_control(l1);
609 if (!fc) continue;
610 draw_form_entry(term, doc_view, l1);
612 } while (l1++ < l2);
616 /** @relates submitted_value */
617 void
618 done_submitted_value_list(LIST_OF(struct submitted_value) *list)
620 struct submitted_value *sv, *svtmp;
622 assert(list);
623 if_assert_failed return;
625 foreach (sv, *list) {
626 svtmp = sv;
627 sv = sv->prev;
628 del_from_list(svtmp);
629 done_submitted_value(svtmp);
633 static void
634 add_submitted_value_to_list(struct form_control *fc,
635 struct form_state *fs,
636 LIST_OF(struct submitted_value) *list)
638 struct submitted_value *sub;
639 unsigned char *name;
640 enum form_type type;
641 int position;
643 assert(fc && fs && list);
645 name = fc->name;
646 position = fc->position;
647 type = fc->type;
649 switch (fc->type) {
650 case FC_TEXT:
651 case FC_PASSWORD:
652 case FC_FILE:
653 case FC_TEXTAREA:
654 sub = init_submitted_value(name, fs->value, type, fc, position);
655 if (sub) add_to_list(*list, sub);
656 break;
658 case FC_CHECKBOX:
659 case FC_RADIO:
660 if (!fs->state) break;
661 /* fall through */
663 case FC_SUBMIT:
664 case FC_HIDDEN:
665 case FC_RESET:
666 case FC_BUTTON:
667 sub = init_submitted_value(name, fs->value, type, fc,
668 position);
669 if (sub) add_to_list(*list, sub);
670 break;
672 case FC_SELECT:
673 if (!fc->nvalues) break;
675 fixup_select_state(fc, fs);
676 sub = init_submitted_value(name, fs->value, type, fc, position);
677 if (sub) add_to_list(*list, sub);
678 break;
680 case FC_IMAGE:
681 name = straconcat(fc->name, ".x", (unsigned char *) NULL);
682 if (!name) break;
683 sub = init_submitted_value(name, "0", type, fc, position);
684 mem_free(name);
685 if (sub) add_to_list(*list, sub);
687 name = straconcat(fc->name, ".y", (unsigned char *) NULL);
688 if (!name) break;
689 sub = init_submitted_value(name, "0", type, fc, position);
690 mem_free(name);
691 if (sub) add_to_list(*list, sub);
693 break;
697 static void
698 sort_submitted_values(LIST_OF(struct submitted_value) *list)
700 while (1) {
701 struct submitted_value *sub;
702 int changed = 0;
704 foreach (sub, *list) if (list_has_next(*list, sub))
705 if (sub->next->position < sub->position) {
706 struct submitted_value *next = sub->next;
708 del_from_list(sub);
709 add_at_pos(next, sub);
710 sub = next;
711 changed = 1;
714 foreachback (sub, *list) if (list_has_next(*list, sub))
715 if (sub->next->position < sub->position) {
716 struct submitted_value *next = sub->next;
718 del_from_list(sub);
719 add_at_pos(next, sub);
720 sub = next;
721 changed = 1;
724 if (!changed) break;
728 static void
729 get_successful_controls(struct document_view *doc_view,
730 struct form_control *fc,
731 LIST_OF(struct submitted_value) *list)
733 struct form_control *fc2;
735 assert(doc_view && fc && fc->form && list);
736 if_assert_failed return;
738 foreach (fc2, fc->form->items) {
739 if (((fc2->type != FC_SUBMIT &&
740 fc2->type != FC_IMAGE &&
741 fc2->type != FC_RESET &&
742 fc2->type != FC_BUTTON) || fc2 == fc)
743 && fc2->name && fc2->name[0]) {
744 struct form_state *fs = find_form_state(doc_view, fc2);
746 if (!fs) continue;
748 add_submitted_value_to_list(fc2, fs, list);
752 sort_submitted_values(list);
755 static void
756 encode_controls(LIST_OF(struct submitted_value) *l, struct string *data,
757 int cp_from, int cp_to)
759 struct submitted_value *sv;
760 struct conv_table *convert_table = NULL;
761 int lst = 0;
763 assert(l && data);
764 if_assert_failed return;
766 foreach (sv, *l) {
767 unsigned char *p2 = NULL;
769 if (lst)
770 add_char_to_string(data, '&');
771 else
772 lst = 1;
774 encode_uri_string(data, sv->name, strlen(sv->name), 1);
775 add_char_to_string(data, '=');
777 /* Convert back to original encoding (see html_form_control()
778 * for the original recoding). */
779 if (sv->type == FC_TEXTAREA) {
780 unsigned char *p;
782 p = encode_textarea(sv);
783 if (p) {
784 if (!convert_table)
785 convert_table = get_translation_table(cp_from, cp_to);
787 p2 = convert_string(convert_table, p,
788 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
789 mem_free(p);
791 } else if (sv->type == FC_TEXT ||
792 sv->type == FC_PASSWORD) {
793 if (!convert_table)
794 convert_table = get_translation_table(cp_from, cp_to);
796 p2 = convert_string(convert_table, sv->value,
797 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
798 } else {
799 p2 = stracpy(sv->value);
802 if (p2) {
803 encode_uri_string(data, p2, strlen(p2), 1);
804 mem_free(p2);
811 #define BOUNDARY_LENGTH 32
812 #define realloc_bound_ptrs(bptrs, bptrs_size) \
813 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
815 struct boundary_info {
816 int count;
817 int *offsets;
818 unsigned char string[BOUNDARY_LENGTH];
821 /** @relates boundary_info */
822 static inline void
823 init_boundary(struct boundary_info *boundary)
825 memset(boundary, 0, sizeof(*boundary));
826 memset(boundary->string, '0', BOUNDARY_LENGTH);
829 /** Add boundary to string and save the offset
830 * @relates boundary_info */
831 static inline void
832 add_boundary(struct string *data, struct boundary_info *boundary)
834 add_to_string(data, "--");
836 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
837 boundary->offsets[boundary->count++] = data->length;
839 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
842 /** @relates boundary_info */
843 static inline unsigned char *
844 increment_boundary_counter(struct boundary_info *boundary)
846 int j;
848 /* This is just a decimal string incrementation */
849 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
850 if (boundary->string[j]++ < '9')
851 return boundary->string;
853 boundary->string[j] = '0';
856 INTERNAL("Form data boundary counter overflow");
858 return NULL;
861 /** @relates boundary_info */
862 static inline void
863 check_boundary(struct string *data, struct boundary_info *boundary)
865 unsigned char *bound = boundary->string;
866 int i;
868 /* Search between all boundaries. There is a starting and an ending
869 * boundary so only check the range of chars after the current offset
870 * and before the next offset. If some string in the form data matches
871 * the boundary string it is changed. */
872 for (i = 0; i < boundary->count - 1; i++) {
873 /* Start after the boundary string and also jump past the
874 * "\r\nContent-Disposition: form-data; name=\"" string added
875 * before any form data. */
876 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
878 /* End so that there is atleast BOUNDARY_LENGTH chars to
879 * compare. Subtract 2 char because there is no need to also
880 * compare the '--' prefix that is part of the boundary. */
881 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
882 unsigned char *pos = data->source + start_offset;
883 unsigned char *end = data->source + end_offset;
885 for (; pos <= end; pos++) {
886 if (memcmp(pos, bound, BOUNDARY_LENGTH))
887 continue;
889 /* If incrementing causes overflow bail out. There is
890 * no need to reset the boundary string with '0' since
891 * that is already done when incrementing. */
892 if (!increment_boundary_counter(boundary))
893 return;
895 /* Else start checking all boundaries using the new
896 * boundary string */
897 i = 0;
898 break;
902 /* Now update all the boundaries with the unique boundary string */
903 for (i = 0; i < boundary->count; i++)
904 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
907 /** @todo FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
908 static void
909 encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
910 struct string *data,
911 struct boundary_info *boundary, int cp_from, int cp_to)
913 struct conv_table *convert_table = NULL;
914 struct submitted_value *sv;
916 assert(ses && l && data && boundary);
917 if_assert_failed return;
919 init_boundary(boundary);
921 foreach (sv, *l) {
922 add_boundary(data, boundary);
923 add_crlf_to_string(data);
925 /** @bug FIXME: name is not encoded.
926 * from RFC 1867:
927 * multipart/form-data contains a series of parts.
928 * Each part is expected to contain a content-disposition
929 * header where the value is "form-data" and a name attribute
930 * specifies the field name within the form,
931 * e.g., 'content-disposition: form-data; name="xxxxx"',
932 * where xxxxx is the field name corresponding to that field.
933 * Field names originally in non-ASCII character sets may be
934 * encoded using the method outlined in RFC 1522. */
935 add_to_string(data, "Content-Disposition: form-data; name=\"");
936 add_to_string(data, sv->name);
937 add_char_to_string(data, '"');
939 if (sv->type == FC_FILE) {
940 #define F_BUFLEN 1024
941 int fh;
942 unsigned char buffer[F_BUFLEN];
943 unsigned char *extension;
945 add_to_string(data, "; filename=\"");
946 add_to_string(data, get_filename_position(sv->value));
947 /* It sends bad data if the file name contains ", but
948 Netscape does the same */
949 /* FIXME: We should follow RFCs 1522, 1867,
950 * 2047 (updated by rfc 2231), to provide correct support
951 * for non-ASCII and special characters in values. --Zas */
952 add_char_to_string(data, '"');
954 /* Add a Content-Type header if the type is configured */
955 extension = strrchr(sv->value, '.');
956 if (extension) {
957 unsigned char *type = get_extension_content_type(extension);
959 if (type) {
960 add_crlf_to_string(data);
961 add_to_string(data, "Content-Type: ");
962 add_to_string(data, type);
963 mem_free(type);
967 add_crlf_to_string(data);
968 add_crlf_to_string(data);
970 if (*sv->value) {
971 unsigned char *filename;
973 if (get_cmd_opt_bool("anonymous")) {
974 errno = EPERM;
975 goto encode_error;
978 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
979 filename = expand_tilde(sv->value);
980 if (!filename) goto encode_error;
982 fh = open(filename, O_RDONLY);
983 mem_free(filename);
985 if (fh == -1) goto encode_error;
986 set_bin(fh);
987 while (1) {
988 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
990 if (rd) {
991 if (rd == -1) {
992 close(fh);
993 goto encode_error;
996 add_bytes_to_string(data, buffer, rd);
998 } else {
999 break;
1002 close(fh);
1004 #undef F_BUFLEN
1005 } else {
1006 add_crlf_to_string(data);
1007 add_crlf_to_string(data);
1009 /* Convert back to original encoding (see
1010 * html_form_control() for the original recoding). */
1011 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
1012 sv->type == FC_TEXTAREA) {
1013 unsigned char *p;
1015 if (!convert_table)
1016 convert_table = get_translation_table(cp_from,
1017 cp_to);
1019 p = convert_string(convert_table, sv->value,
1020 strlen(sv->value), -1, CSM_FORM, NULL,
1021 NULL, NULL);
1022 if (p) {
1023 add_to_string(data, p);
1024 mem_free(p);
1026 } else {
1027 add_to_string(data, sv->value);
1031 add_crlf_to_string(data);
1034 /* End-boundary */
1035 add_boundary(data, boundary);
1036 add_to_string(data, "--\r\n");
1038 check_boundary(data, boundary);
1040 mem_free_if(boundary->offsets);
1041 return;
1043 encode_error:
1044 mem_free_if(boundary->offsets);
1045 done_string(data);
1047 /* XXX: This error message should move elsewhere. --Zas */
1048 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1049 N_("Error while posting form"), ALIGN_CENTER,
1050 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1051 sv->value, strerror(errno)));
1054 static void
1055 encode_newlines(struct string *string, unsigned char *data)
1057 for (; *data; data++) {
1058 if (*data == '\n' || *data == '\r') {
1059 unsigned char buffer[3];
1061 /* Hex it. */
1062 buffer[0] = '%';
1063 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1064 buffer[2] = hx(((int) *data) & 0xF);
1065 add_bytes_to_string(string, buffer, 3);
1066 } else {
1067 add_char_to_string(string, *data);
1072 static void
1073 encode_text_plain(LIST_OF(struct submitted_value) *l, struct string *data,
1074 int cp_from, int cp_to)
1076 struct submitted_value *sv;
1077 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1079 assert(l && data);
1080 if_assert_failed return;
1082 foreach (sv, *l) {
1083 unsigned char *area51 = NULL;
1084 unsigned char *value = sv->value;
1086 add_to_string(data, sv->name);
1087 add_char_to_string(data, '=');
1089 switch (sv->type) {
1090 case FC_TEXTAREA:
1091 value = area51 = encode_textarea(sv);
1092 if (!area51) break;
1093 /* Fall through */
1094 case FC_TEXT:
1095 case FC_PASSWORD:
1096 /* Convert back to original encoding (see
1097 * html_form_control() for the original recoding). */
1098 value = convert_string(convert_table, value,
1099 strlen(value), -1, CSM_FORM,
1100 NULL, NULL, NULL);
1101 default:
1102 /* Falling right through to free that textarea stuff */
1103 mem_free_if(area51);
1105 /* Did the conversion fail? */
1106 if (!value) break;
1108 encode_newlines(data, value);
1110 /* Free if we did convert something */
1111 if (value != sv->value) mem_free(value);
1114 add_crlf_to_string(data);
1118 void
1119 do_reset_form(struct document_view *doc_view, struct form *form)
1121 struct form_control *fc;
1123 assert(doc_view && doc_view->document);
1124 if_assert_failed return;
1126 foreach (fc, form->items) {
1127 struct form_state *fs = find_form_state(doc_view, fc);
1129 if (fs) init_form_state(doc_view, fc, fs);
1133 enum frame_event_status
1134 reset_form(struct session *ses, struct document_view *doc_view, int a)
1136 struct link *link = get_current_link(doc_view);
1138 if (!link) return FRAME_EVENT_OK;
1140 do_reset_form(doc_view, get_link_form_control(link)->form);
1141 draw_forms(ses->tab->term, doc_view);
1143 /* Could be the refresh return value and then ditch the draw_forms()
1144 * call. */
1145 return FRAME_EVENT_OK;
1148 struct uri *
1149 get_form_uri(struct session *ses, struct document_view *doc_view,
1150 struct form_control *fc)
1152 struct boundary_info boundary;
1153 INIT_LIST_OF(struct submitted_value, submit);
1154 struct string data;
1155 struct string go;
1156 int cp_from, cp_to;
1157 struct uri *uri;
1158 struct form *form;
1160 assert(ses && ses->tab && ses->tab->term);
1161 if_assert_failed return NULL;
1162 assert(doc_view && doc_view->document && fc && fc->form);
1163 if_assert_failed return NULL;
1165 form = fc->form;
1167 if (fc->type == FC_RESET) {
1168 do_reset_form(doc_view, form);
1169 return NULL;
1172 if (!form->action
1173 || !init_string(&data))
1174 return NULL;
1176 get_successful_controls(doc_view, fc, &submit);
1178 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset");
1179 cp_to = doc_view->document->cp;
1180 switch (form->method) {
1181 case FORM_METHOD_GET:
1182 case FORM_METHOD_POST:
1183 encode_controls(&submit, &data, cp_from, cp_to);
1184 break;
1186 case FORM_METHOD_POST_MP:
1187 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1188 break;
1190 case FORM_METHOD_POST_TEXT_PLAIN:
1191 encode_text_plain(&submit, &data, cp_from, cp_to);
1194 #ifdef CONFIG_FORMHIST
1195 /* XXX: We check data.source here because a NULL value can indicate
1196 * not only a memory allocation failure, but also an error reading
1197 * a file that is to be uploaded. TODO: Distinguish between
1198 * these two classes of errors (is it worth it?). -- Miciah */
1199 if (data.source
1200 && get_opt_bool("document.browse.forms.show_formhist"))
1201 memorize_form(ses, &submit, form);
1202 #endif
1204 done_submitted_value_list(&submit);
1206 if (!data.source
1207 || !init_string(&go)) {
1208 done_string(&data);
1209 return NULL;
1212 switch (form->method) {
1213 case FORM_METHOD_GET:
1215 unsigned char *pos = strchr(form->action, '#');
1217 if (pos) {
1218 add_bytes_to_string(&go, form->action, pos - form->action);
1219 } else {
1220 add_to_string(&go, form->action);
1223 if (strchr(go.source, '?'))
1224 add_char_to_string(&go, '&');
1225 else
1226 add_char_to_string(&go, '?');
1228 add_string_to_string(&go, &data);
1230 if (pos) add_to_string(&go, pos);
1231 break;
1233 case FORM_METHOD_POST:
1234 case FORM_METHOD_POST_MP:
1235 case FORM_METHOD_POST_TEXT_PLAIN:
1237 /* Note that we end content type here by a simple '\n',
1238 * replaced later by correct '\r\n' in http_send_header(). */
1239 int i;
1241 add_to_string(&go, form->action);
1242 add_char_to_string(&go, POST_CHAR);
1243 if (form->method == FORM_METHOD_POST) {
1244 add_to_string(&go, "application/x-www-form-urlencoded\n");
1246 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1247 /* Dunno about this one but we don't want the full
1248 * hextcat thingy. --jonas */
1249 add_to_string(&go, "text/plain\n");
1250 add_to_string(&go, data.source);
1251 break;
1253 } else {
1254 add_to_string(&go, "multipart/form-data; boundary=");
1255 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1256 add_char_to_string(&go, '\n');
1259 for (i = 0; i < data.length; i++) {
1260 unsigned char p[3];
1262 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1263 add_to_string(&go, p);
1268 done_string(&data);
1270 uri = get_uri(go.source, 0);
1271 done_string(&go);
1272 if (uri) uri->form = 1;
1274 return uri;
1277 #undef BOUNDARY_LENGTH
1280 enum frame_event_status
1281 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1283 goto_current_link(ses, doc_view, do_reload);
1284 return FRAME_EVENT_OK;
1287 void
1288 submit_given_form(struct session *ses, struct document_view *doc_view,
1289 struct form *form, int do_reload)
1291 /* Added support for submitting forms in hidden
1292 * links in 1.285, commented code can safely be removed once we have made sure the new
1293 * code does the right thing. */
1294 #if 0
1296 struct document *document = doc_view->document;
1297 int link;
1299 for (link = 0; link < document->nlinks; link++) {
1300 struct form_control *fc = get_link_form_control(&document->links[link]);
1302 if (fc && fc->form == form) {
1303 doc_view->vs->current_link = link;
1304 submit_form(ses, doc_view, 0);
1305 return;
1308 #endif
1309 if (!list_empty(form->items)) {
1310 struct form_control *fc = (struct form_control *)form->items.next;
1311 struct uri *uri;
1312 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1314 if (!fc) return;
1315 uri = get_form_uri(ses, doc_view, fc);
1316 if (!uri) return;
1317 goto_uri_frame(ses, uri, form->target, mode);
1318 done_uri(uri);
1322 void
1323 auto_submit_form(struct session *ses)
1325 struct document *document = ses->doc_view->document;
1327 if (!list_empty(document->forms))
1328 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1332 /* menu_func_T */
1333 static void
1334 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1336 unsigned char *filename = filename_;
1337 struct form_state *fs = fs_;
1339 /* The menu code doesn't free the filename data */
1340 mem_free_set(&fs->value, filename);
1341 fs->state = strlen(filename);
1342 redraw_terminal(term);
1345 /* menu_func_T */
1346 static void
1347 file_form_menu(struct terminal *term, void *path_, void *fs_)
1349 unsigned char *path = path_;
1350 struct form_state *fs = fs_;
1352 /* FIXME: It doesn't work for ../../ */
1353 #if 0
1354 int valuelen = strlen(fs->value);
1355 int pathlen = strlen(path);
1356 int no_elevator = 0;
1358 /* Don't add elevators for subdirs menus */
1359 /* It is not perfect at all because fs->value is not updated for each
1360 * newly opened file menu. Maybe it should be dropped. */
1361 for (; valuelen < pathlen; valuelen++) {
1362 if (dir_sep(path[valuelen - 1])) {
1363 no_elevator = 1;
1364 break;
1367 #endif
1369 auto_complete_file(term, 0 /* no_elevator */, path,
1370 set_file_form_state,
1371 file_form_menu, fs);
1375 enum frame_event_status
1376 field_op(struct session *ses, struct document_view *doc_view,
1377 struct link *link, struct term_event *ev)
1379 struct form_control *fc;
1380 struct form_state *fs;
1381 enum edit_action action_id;
1382 unsigned char *text;
1383 int length;
1384 enum frame_event_status status = FRAME_EVENT_REFRESH;
1385 #ifdef CONFIG_UTF8
1386 const unsigned char *ctext;
1387 int utf8 = ses->tab->term->utf8_cp;
1388 #endif /* CONFIG_UTF8 */
1390 assert(ses && doc_view && link && ev);
1391 if_assert_failed return FRAME_EVENT_OK;
1393 fc = get_link_form_control(link);
1394 assertm(fc != NULL, "link has no form control");
1395 if_assert_failed return FRAME_EVENT_OK;
1397 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1398 || ses->insert_mode == INSERT_MODE_OFF)
1399 return FRAME_EVENT_IGNORED;
1401 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1403 fs = find_form_state(doc_view, fc);
1404 if (!fs || !fs->value) return FRAME_EVENT_OK;
1406 switch (action_id) {
1407 case ACT_EDIT_LEFT:
1408 #ifdef CONFIG_UTF8
1409 if (fc->type == FC_TEXTAREA) {
1410 status = textarea_op_left(fs, fc, utf8);
1411 break;
1413 if (utf8) {
1414 unsigned char *new_value;
1416 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1417 fs->state = new_value - fs->value;
1418 } else
1419 #endif /* CONFIG_UTF8 */
1420 fs->state = int_max(fs->state - 1, 0);
1421 break;
1422 case ACT_EDIT_RIGHT:
1423 #ifdef CONFIG_UTF8
1424 if (fc->type == FC_TEXTAREA) {
1425 status = textarea_op_right(fs, fc, utf8);
1426 break;
1428 if (utf8) {
1429 unsigned char *text = fs->value + fs->state;
1430 unsigned char *end = strchr(text, '\0');
1432 utf8_to_unicode(&text, end);
1433 fs->state = (int)(text - fs->value);
1434 } else
1435 #endif /* CONFIG_UTF8 */
1436 fs->state = int_min(fs->state + 1, strlen(fs->value));
1437 break;
1438 case ACT_EDIT_HOME:
1439 #ifdef CONFIG_UTF8
1440 if (fc->type == FC_TEXTAREA) {
1441 status = textarea_op_home(fs, fc, utf8);
1442 } else {
1443 fs->state = 0;
1445 #else
1446 if (fc->type == FC_TEXTAREA) {
1447 status = textarea_op_home(fs, fc);
1448 } else {
1449 fs->state = 0;
1452 #endif /* CONFIG_UTF8 */
1453 break;
1454 case ACT_EDIT_UP:
1455 if (fc->type != FC_TEXTAREA)
1456 status = FRAME_EVENT_IGNORED;
1457 else
1458 #ifdef CONFIG_UTF8
1459 status = textarea_op_up(fs, fc, utf8);
1460 #else
1461 status = textarea_op_up(fs, fc);
1462 #endif /* CONFIG_UTF8 */
1463 break;
1464 case ACT_EDIT_DOWN:
1465 if (fc->type != FC_TEXTAREA)
1466 status = FRAME_EVENT_IGNORED;
1467 else
1468 #ifdef CONFIG_UTF8
1469 status = textarea_op_down(fs, fc, utf8);
1470 #else
1471 status = textarea_op_down(fs, fc);
1472 #endif /* CONFIG_UTF8 */
1473 break;
1474 case ACT_EDIT_END:
1475 if (fc->type == FC_TEXTAREA) {
1476 #ifdef CONFIG_UTF8
1477 status = textarea_op_end(fs, fc, utf8);
1478 #else
1479 status = textarea_op_end(fs, fc);
1480 #endif /* CONFIG_UTF8 */
1481 } else {
1482 fs->state = strlen(fs->value);
1484 break;
1485 case ACT_EDIT_BEGINNING_OF_BUFFER:
1486 if (fc->type == FC_TEXTAREA) {
1487 #ifdef CONFIG_UTF8
1488 status = textarea_op_bob(fs, fc, utf8);
1489 fs->state_cell = 0;
1490 #else
1491 status = textarea_op_bob(fs, fc);
1492 #endif /* CONFIG_UTF8 */
1493 } else {
1494 fs->state = 0;
1496 break;
1497 case ACT_EDIT_END_OF_BUFFER:
1498 if (fc->type == FC_TEXTAREA) {
1499 #ifdef CONFIG_UTF8
1500 status = textarea_op_eob(fs, fc, utf8);
1501 #else
1502 status = textarea_op_eob(fs, fc);
1503 #endif /* CONFIG_UTF8 */
1504 } else {
1505 fs->state = strlen(fs->value);
1507 break;
1508 case ACT_EDIT_OPEN_EXTERNAL:
1509 if (form_field_is_readonly(fc))
1510 status = FRAME_EVENT_IGNORED;
1511 else if (fc->type == FC_TEXTAREA)
1512 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1513 break;
1514 case ACT_EDIT_COPY_CLIPBOARD:
1515 set_clipboard_text(fs->value);
1516 status = FRAME_EVENT_OK;
1517 break;
1518 case ACT_EDIT_CUT_CLIPBOARD:
1519 set_clipboard_text(fs->value);
1520 if (!form_field_is_readonly(fc))
1521 fs->value[0] = 0;
1522 fs->state = 0;
1523 #ifdef CONFIG_UTF8
1524 if (fc->type == FC_TEXTAREA)
1525 fs->state_cell = 0;
1526 #endif /* CONFIG_UTF8 */
1527 break;
1528 case ACT_EDIT_PASTE_CLIPBOARD:
1529 if (form_field_is_readonly(fc)) break;
1531 text = get_clipboard_text();
1532 if (!text) break;
1534 length = strlen(text);
1535 if (length <= fc->maxlength) {
1536 unsigned char *v = mem_realloc(fs->value, length + 1);
1538 if (v) {
1539 fs->value = v;
1540 memmove(v, text, length + 1);
1541 fs->state = strlen(fs->value);
1542 #ifdef CONFIG_UTF8
1543 if (utf8 && fc->type == FC_TEXTAREA)
1544 fs->state_cell = 0;
1545 #endif /* CONFIG_UTF8 */
1548 mem_free(text);
1549 break;
1550 case ACT_EDIT_ENTER:
1551 if (fc->type == FC_TEXTAREA) {
1552 #ifdef CONFIG_UTF8
1553 status = textarea_op_enter(fs, fc, utf8);
1554 #else
1555 status = textarea_op_enter(fs, fc);
1556 #endif /* CONFIG_UTF8 */
1557 break;
1560 /* Set status to ok if either it is not possible to
1561 * submit the form or the posting fails. */
1562 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1563 if ((has_form_submit(fc->form)
1564 && !get_opt_bool("document.browse.forms.auto_submit"))
1565 || goto_current_link(ses, doc_view, 0)) {
1566 if (ses->insert_mode == INSERT_MODE_ON)
1567 ses->insert_mode = INSERT_MODE_OFF;
1568 status = FRAME_EVENT_OK;
1570 break;
1571 case ACT_EDIT_BACKSPACE:
1572 if (form_field_is_readonly(fc)) {
1573 status = FRAME_EVENT_IGNORED;
1574 break;
1577 if (!fs->state) {
1578 status = FRAME_EVENT_OK;
1579 break;
1581 #ifdef CONFIG_UTF8
1582 if (utf8) {
1583 int old_state = fs->state;
1584 unsigned char *new_value;
1586 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1587 fs->state = new_value - fs->value;
1589 if (old_state != fs->state) {
1590 if (fc->type == FC_TEXTAREA)
1591 fs->state_cell = 0;
1592 length = strlen(fs->value + old_state) + 1;
1593 memmove(new_value, fs->value + old_state, length);
1595 } else
1596 #endif /* CONFIG_UTF8 */
1598 length = strlen(fs->value + fs->state) + 1;
1599 text = fs->value + fs->state;
1601 memmove(text - 1, text, length);
1602 fs->state--;
1604 break;
1605 case ACT_EDIT_DELETE:
1606 if (form_field_is_readonly(fc)) {
1607 status = FRAME_EVENT_IGNORED;
1608 break;
1611 length = strlen(fs->value);
1612 if (fs->state >= length) {
1613 status = FRAME_EVENT_OK;
1614 break;
1616 #ifdef CONFIG_UTF8
1617 if (utf8) {
1618 unsigned char *end = fs->value + length;
1619 unsigned char *text = fs->value + fs->state;
1620 unsigned char *old = text;
1622 utf8_to_unicode(&text, end);
1623 if (old != text) {
1624 memmove(old, text,
1625 (int)(end - text) + 1);
1627 break;
1629 #endif /* CONFIG_UTF8 */
1630 text = fs->value + fs->state;
1632 memmove(text, text + 1, length - fs->state);
1633 break;
1634 case ACT_EDIT_KILL_TO_BOL:
1635 if (form_field_is_readonly(fc)) {
1636 status = FRAME_EVENT_IGNORED;
1637 break;
1640 if (fs->state <= 0) {
1641 status = FRAME_EVENT_OK;
1642 break;
1645 text = memrchr(fs->value, ASCII_LF, fs->state);
1646 if (text) {
1647 /* Leave the new-line character if it does not
1648 * immediately precede the cursor. */
1649 if (text != &fs->value[fs->state - 1])
1650 text++;
1651 } else {
1652 text = fs->value;
1655 length = strlen(fs->value + fs->state) + 1;
1656 memmove(text, fs->value + fs->state, length);
1658 fs->state = (int) (text - fs->value);
1659 #ifdef CONFIG_UTF8
1660 if (utf8) {
1661 if (fc->type == FC_TEXTAREA)
1662 fs->state_cell = 0;
1664 #endif /* CONFIG_UTF8 */
1665 break;
1666 case ACT_EDIT_KILL_TO_EOL:
1667 if (form_field_is_readonly(fc)) {
1668 status = FRAME_EVENT_IGNORED;
1669 break;
1672 if (!fs->value[fs->state]) {
1673 status = FRAME_EVENT_OK;
1674 break;
1677 text = strchr(fs->value + fs->state, ASCII_LF);
1678 if (!text) {
1679 fs->value[fs->state] = '\0';
1680 break;
1683 if (fs->value[fs->state] == ASCII_LF)
1684 ++text;
1686 memmove(fs->value + fs->state, text, strlen(text) + 1);
1687 break;
1689 case ACT_EDIT_KILL_WORD_BACK:
1690 if (form_field_is_readonly(fc)) {
1691 status = FRAME_EVENT_IGNORED;
1692 break;
1695 if (fs->state <= 0) {
1696 status = FRAME_EVENT_OK;
1697 break;
1700 text = &fs->value[fs->state];
1701 while (text > fs->value && isspace(*(text - 1)))
1702 --text;
1703 while (text > fs->value && !isspace(*(text - 1)))
1704 --text;
1705 if (*text == ASCII_LF
1706 && text != &fs->value[fs->state - 1])
1707 text++;
1709 length = strlen(fs->value + fs->state) + 1;
1710 memmove(text, fs->value + fs->state, length);
1712 fs->state = (int) (text - fs->value);
1713 break;
1715 case ACT_EDIT_MOVE_BACKWARD_WORD:
1716 while (fs->state > 0
1717 && isspace(fs->value[fs->state - 1]))
1718 --fs->state;
1719 while (fs->state > 0
1720 && !isspace(fs->value[fs->state - 1]))
1721 --fs->state;
1722 break;
1724 case ACT_EDIT_MOVE_FORWARD_WORD:
1725 while (isspace(fs->value[fs->state]))
1726 ++fs->state;
1727 while (fs->value[fs->state]
1728 && !isspace(fs->value[fs->state]))
1729 ++fs->state;
1730 while (isspace(fs->value[fs->state]))
1731 ++fs->state;
1732 break;
1734 case ACT_EDIT_AUTO_COMPLETE:
1735 if (fc->type != FC_FILE
1736 || form_field_is_readonly(fc)) {
1737 status = FRAME_EVENT_IGNORED;
1738 break;
1741 file_form_menu(ses->tab->term, fs->value, fs);
1742 break;
1744 case ACT_EDIT_CANCEL:
1745 if (ses->insert_mode == INSERT_MODE_ON)
1746 ses->insert_mode = INSERT_MODE_OFF;
1747 else
1748 status = FRAME_EVENT_IGNORED;
1749 break;
1751 case ACT_EDIT_REDRAW:
1752 redraw_terminal_cls(ses->tab->term);
1753 status = FRAME_EVENT_OK;
1754 break;
1756 default:
1757 if (!check_kbd_textinput_key(ev)) {
1758 status = FRAME_EVENT_IGNORED;
1759 break;
1762 if (form_field_is_readonly(fc)
1763 #ifndef CONFIG_UTF8
1764 || strlen(fs->value) >= fc->maxlength
1765 || !insert_in_string(&fs->value, fs->state, "?", 1)
1766 #endif /* CONFIG_UTF8 */
1769 status = FRAME_EVENT_OK;
1770 break;
1773 #ifdef CONFIG_UTF8
1774 /* fs->value is in the charset of the terminal. */
1775 ctext = u2cp_no_nbsp(get_kbd_key(ev),
1776 get_opt_codepage_tree(ses->tab->term->spec,
1777 "charset"));
1778 length = strlen(ctext);
1780 if (strlen(fs->value) + length > fc->maxlength
1781 || !insert_in_string(&fs->value, fs->state, ctext, length)) {
1782 status = FRAME_EVENT_OK;
1783 break;
1786 fs->state += length;
1787 if (fc->type == FC_TEXTAREA)
1788 fs->state_cell = 0;
1789 #else
1790 fs->value[fs->state++] = get_kbd_key(ev);
1791 #endif /* CONFIG_UTF8 */
1792 break;
1795 return status;
1798 static unsigned char *
1799 get_form_label(struct form_control *fc)
1801 assert(fc->form);
1802 switch (fc->type) {
1803 case FC_RESET:
1804 return N_("Reset form");
1805 case FC_BUTTON:
1806 return N_("Harmless button");
1807 case FC_HIDDEN:
1808 return NULL;
1809 case FC_SUBMIT:
1810 case FC_IMAGE:
1811 if (!fc->form->action) return NULL;
1813 if (fc->form->method == FORM_METHOD_GET)
1814 return N_("Submit form to");
1815 return N_("Post form to");
1816 case FC_RADIO:
1817 return N_("Radio button");
1818 case FC_CHECKBOX:
1819 return N_("Checkbox");
1820 case FC_SELECT:
1821 return N_("Select field");
1822 case FC_TEXT:
1823 return N_("Text field");
1824 case FC_TEXTAREA:
1825 return N_("Text area");
1826 case FC_FILE:
1827 return N_("File upload");
1828 case FC_PASSWORD:
1829 return N_("Password field");
1832 return NULL;
1835 static inline void
1836 add_form_attr_to_string(struct string *string, struct terminal *term,
1837 unsigned char *name, unsigned char *value)
1839 add_to_string(string, ", ");
1840 add_to_string(string, _(name, term));
1841 if (value) {
1842 add_char_to_string(string, ' ');
1843 add_to_string(string, value);
1847 unsigned char *
1848 get_form_info(struct session *ses, struct document_view *doc_view)
1850 struct terminal *term = ses->tab->term;
1851 struct link *link = get_current_link(doc_view);
1852 struct form_control *fc;
1853 unsigned char *label, *key;
1854 struct string str;
1856 assert(link);
1858 fc = get_link_form_control(link);
1859 label = get_form_label(fc);
1860 if (!label) return NULL;
1862 if (!init_string(&str)) return NULL;
1864 add_to_string(&str, _(label, term));
1866 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1867 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1870 switch (fc->type) {
1871 case FC_CHECKBOX:
1872 case FC_RADIO:
1874 struct form_state *fs = find_form_state(doc_view, fc);
1876 if (!fs->value || !fs->value[0])
1877 break;
1879 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1880 break;
1883 case FC_TEXT:
1884 case FC_PASSWORD:
1885 case FC_FILE:
1886 case FC_TEXTAREA:
1888 struct uri *uri;
1889 unsigned char *uristring;
1891 if (form_field_is_readonly(fc)) {
1892 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1895 /* Should we add info about entering insert mode or add info
1896 * about submitting the form? */
1897 if (ses->insert_mode == INSERT_MODE_OFF) {
1898 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1900 if (!key) break;
1902 if (form_field_is_readonly(fc))
1903 label = N_("press %s to navigate");
1904 else
1905 label = N_("press %s to edit");
1907 add_to_string(&str, " (");
1908 add_format_to_string(&str, _(label, term), key);
1909 add_char_to_string(&str, ')');
1910 mem_free(key);
1911 break;
1915 if (fc->type == FC_TEXTAREA)
1916 break;
1918 assert(fc->form);
1920 if (!fc->form->action
1921 || (has_form_submit(fc->form)
1922 && !get_opt_bool("document.browse.forms.auto_submit")))
1923 break;
1925 uri = get_uri(fc->form->action, 0);
1926 if (!uri) break;
1928 /* Add the uri with password and post info stripped */
1929 uristring = get_uri_string(uri, URI_PUBLIC);
1930 done_uri(uri);
1932 if (!uristring) break;
1934 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1935 if (!key) {
1936 mem_free(uristring);
1937 break;
1940 if (fc->form->method == FORM_METHOD_GET)
1941 label = N_("press %s to submit to %s");
1942 else
1943 label = N_("press %s to post to %s");
1945 add_to_string(&str, " (");
1946 add_format_to_string(&str, _(label, term), key, uristring);
1947 mem_free(uristring);
1948 mem_free(key);
1950 add_char_to_string(&str, ')');
1951 break;
1953 case FC_SUBMIT:
1954 case FC_IMAGE:
1955 add_char_to_string(&str, ' ');
1957 assert(fc->form);
1958 /* Add the uri with password and post info stripped */
1959 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
1960 break;
1962 case FC_HIDDEN:
1963 case FC_RESET:
1964 case FC_BUTTON:
1965 case FC_SELECT:
1966 break;
1969 if (link->accesskey
1970 && get_opt_bool("document.browse.accesskey.display")) {
1971 add_to_string(&str, " (");
1972 add_accesskey_to_string(&str, link->accesskey);
1973 add_char_to_string(&str, ')');
1976 return str.source;
1979 static void
1980 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
1982 struct session *ses = ses_;
1983 struct document_view *doc_view;
1984 int link_number = *(int *) link_number_;
1986 mem_free(link_number_);
1988 assert(term && ses);
1989 if_assert_failed return;
1991 doc_view = current_frame(ses);
1992 if (!doc_view) return;
1994 assert(doc_view->vs && doc_view->document);
1995 if_assert_failed return;
1997 jump_to_link_number(ses, doc_view, link_number);
1998 refresh_view(ses, doc_view, 0);
2001 void
2002 link_form_menu(struct session *ses)
2004 struct document_view *doc_view;
2005 struct link *link;
2006 struct menu_item *mi;
2007 struct form_control *fc;
2008 struct form *form;
2010 assert(ses);
2011 if_assert_failed return;
2013 doc_view = current_frame(ses);
2014 if (!doc_view) return;
2016 assert(doc_view->vs && doc_view->document);
2017 if_assert_failed return;
2019 link = get_current_link(doc_view);
2020 if (!link) return;
2022 assert(link_is_form(link));
2024 fc = get_link_form_control(link);
2025 if (!fc) return;
2027 form = fc->form;
2029 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2030 if (!mi) return;
2032 foreach (fc, form->items) {
2033 unsigned char *text;
2034 unsigned char *rtext;
2035 int link_number;
2036 struct string str;
2038 switch (fc->type) {
2039 case FC_HIDDEN:
2040 continue;
2042 case FC_SUBMIT:
2043 case FC_IMAGE:
2044 if (!form->action)
2045 text = N_("Useless button");
2046 else
2047 text = N_("Submit button");
2048 break;
2050 default:
2051 text = get_form_label(fc);
2054 link_number = get_form_control_link(doc_view->document, fc);
2055 if (link_number < 0
2056 || !init_string(&str))
2057 continue;
2059 assert(text);
2060 add_to_string(&str, _(text, ses->tab->term));
2062 rtext = fc->name;
2063 if (!rtext) rtext = fc->alt;
2065 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2066 link_form_menu_func, intdup(link_number),
2067 FREE_DATA);
2070 do_menu(ses->tab->term, mi, ses, 1);