Retry only for https protocol
[elinks.git] / src / viewer / text / form.c
blob92ef20ea9c211f5b952eb66fedd8b627e533c646
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 "ecmascript/ecmascript.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/random.h"
50 #include "util/string.h"
51 #include "viewer/action.h"
52 #include "viewer/text/draw.h"
53 #include "viewer/text/form.h"
54 #include "viewer/text/link.h"
55 #include "viewer/text/textarea.h"
56 #include "viewer/text/view.h"
57 #include "viewer/text/vs.h"
60 /* TODO: Some of these (particulary those encoding routines) would feel better
61 * in viewer/common/. --pasky */
63 struct files_offset {
64 LIST_HEAD(struct files_offset);
65 /* offset of the filename in the data generated by encode_multipart.
66 * data[begin] is the FILE_CHAR, data + begin + 1 is the filename. */
67 int begin;
68 /* end of filename. data[end] is the FILE_CHAR. In normal strings
69 * it would be a nul char. */
70 int end;
74 /** @relates submitted_value */
76 struct submitted_value *
77 init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
78 struct form_control *fc, int position)
80 struct submitted_value *sv;
82 sv = mem_alloc(sizeof(*sv));
83 if (!sv) return NULL;
85 sv->value = stracpy(value);
86 if (!sv->value) { mem_free(sv); return NULL; }
88 sv->name = stracpy(name);
89 if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
91 sv->type = type;
92 sv->form_control = fc;
93 sv->position = position;
95 return sv;
98 /** @relates submitted_value */
99 void
100 done_submitted_value(struct submitted_value *sv)
102 if (!sv) return;
103 mem_free_if(sv->value);
104 mem_free_if(sv->name);
105 mem_free(sv);
108 static void
109 fixup_select_state(struct form_control *fc, struct form_state *fs)
111 int i;
113 assert(fc && fs);
114 if_assert_failed return;
116 if (fs->state >= 0
117 && fs->state < fc->nvalues
118 && !strcmp(fc->values[fs->state], fs->value))
119 return;
121 for (i = 0; i < fc->nvalues; i++)
122 if (!strcmp(fc->values[i], fs->value)) {
123 fs->state = i;
124 return;
127 fs->state = 0;
129 mem_free_set(&fs->value, stracpy(fc->nvalues
130 ? fc->values[0]
131 : (unsigned char *) ""));
134 /* menu_func_T */
135 void
136 selected_item(struct terminal *term, void *item_, void *ses_)
138 struct session *ses = ses_;
139 int item = (long) item_;
140 struct document_view *doc_view;
141 struct link *link;
142 struct form_state *fs;
143 struct form_control *fc;
145 assert(term && ses);
146 if_assert_failed return;
147 doc_view = current_frame(ses);
149 assert(doc_view && doc_view->vs && doc_view->document);
150 if_assert_failed return;
152 link = get_current_link(doc_view);
153 if (!link || link->type != LINK_SELECT) return;
155 fc = get_link_form_control(link);
156 fs = find_form_state(doc_view, fc);
157 if (fs) {
158 if (item >= 0 && item < fc->nvalues) {
159 fs->state = item;
160 mem_free_set(&fs->value, stracpy(fc->values[item]));
162 fixup_select_state(fc, fs);
165 refresh_view(ses, doc_view, 0);
168 static void
169 init_form_state(struct document_view *doc_view,
170 struct form_control *fc, struct form_state *fs)
172 struct terminal *term;
173 int doc_cp, viewer_cp;
175 assert(fc && fs);
176 if_assert_failed return;
178 doc_cp = doc_view->document->cp;
179 term = doc_view->session->tab->term;
180 viewer_cp = get_terminal_codepage(term);
182 mem_free_set(&fs->value, NULL);
184 switch (fc->type) {
185 case FC_TEXT:
186 case FC_PASSWORD:
187 #ifdef CONFIG_FORMHIST
188 fs->value = null_or_stracpy(
189 get_form_history_value(
190 fc->form->action, fc->name));
191 #endif /* CONFIG_FORMHIST */
192 /* fall through */
193 case FC_TEXTAREA:
194 if (fs->value == NULL) {
195 fs->value = convert_string(
196 get_translation_table(doc_cp, viewer_cp),
197 fc->default_value,
198 strlen(fc->default_value),
199 viewer_cp, CSM_FORM,
200 &fs->state, NULL, NULL);
202 fs->state = fs->value ? strlen(fs->value) : 0;
203 #ifdef CONFIG_UTF8
204 if (fc->type == FC_TEXTAREA)
205 fs->state_cell = 0;
206 #endif /* CONFIG_UTF8 */
207 fs->vpos = 0;
208 break;
209 case FC_FILE:
210 fs->value = stracpy("");
211 fs->state = 0;
212 fs->vpos = 0;
213 break;
214 case FC_SELECT:
215 fs->value = convert_string(
216 get_translation_table(doc_cp, viewer_cp),
217 fc->default_value,
218 strlen(fc->default_value),
219 viewer_cp, CSM_FORM,
220 &fs->state, NULL, NULL);
221 fs->state = fc->default_state;
222 fixup_select_state(fc, fs);
223 break;
224 case FC_CHECKBOX:
225 case FC_RADIO:
226 fs->state = fc->default_state;
227 /* Fall-through */
228 case FC_SUBMIT:
229 case FC_IMAGE:
230 case FC_RESET:
231 case FC_BUTTON:
232 case FC_HIDDEN:
233 /* We don't want to recode hidden fields. */
234 fs->value = stracpy(fc->default_value);
235 break;
240 struct form_state *
241 find_form_state(struct document_view *doc_view, struct form_control *fc)
243 struct view_state *vs;
244 struct form_state *fs;
245 int n;
247 assert(doc_view && doc_view->vs && fc);
248 if_assert_failed return NULL;
250 vs = doc_view->vs;
251 n = fc->g_ctrl_num;
253 if (n >= vs->form_info_len) {
254 int nn = n + 1;
255 #ifdef CONFIG_ECMASCRIPT
256 const struct form_state *const old_form_info = vs->form_info;
257 #endif
259 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
260 if (!fs) return NULL;
261 vs->form_info = fs;
262 vs->form_info_len = nn;
264 #ifdef CONFIG_ECMASCRIPT
265 /* TODO: Standard C does not allow this comparison;
266 * if the memory to which old_form_info pointed has
267 * been freed, then the value of the pointer itself is
268 * indeterminate. Fixing this would require changing
269 * mem_align_alloc to tell the caller whether it did
270 * realloc or not. */
271 if (vs->form_info != old_form_info) {
272 /* vs->form_info[] was moved to a different address.
273 * Update all the ECMAScript objects that have
274 * pointers to its elements. */
275 for (nn = 0; nn < vs->form_info_len; nn++)
276 ecmascript_moved_form_state(&vs->form_info[nn]);
278 #endif /* CONFIG_ECMASCRIPT */
280 fs = &vs->form_info[n];
282 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
283 && fs->g_ctrl_num == fc->g_ctrl_num
284 && fs->position == fc->position
285 && fs->type == fc->type)
286 return fs;
288 mem_free_if(fs->value);
289 memset(fs, 0, sizeof(*fs));
290 fs->form_view = find_form_view(doc_view, fc->form);
291 fs->g_ctrl_num = fc->g_ctrl_num;
292 fs->position = fc->position;
293 fs->type = fc->type;
294 init_form_state(doc_view, fc, fs);
296 return fs;
299 struct form_control *
300 find_form_control(struct document *document, struct form_state *fs)
302 struct form *form = find_form_by_form_view(document, fs->form_view);
303 struct form_control *fc;
305 foreach (fc, form->items) {
306 if (fs->g_ctrl_num == fc->g_ctrl_num
307 && fs->position == fc->position
308 && fs->type == fc->type)
309 return fc;
312 return NULL;
315 struct form_view *
316 find_form_view_in_vs(struct view_state *vs, int form_num)
318 struct form_view *fv;
320 assert(vs);
322 foreach (fv, vs->forms)
323 if (fv->form_num == form_num)
324 return fv;
326 fv = mem_calloc(1, sizeof(*fv));
327 fv->form_num = form_num;
328 add_to_list(vs->forms, fv);
329 return fv;
332 struct form_view *
333 find_form_view(struct document_view *doc_view, struct form *form)
335 return find_form_view_in_vs(doc_view->vs, form->form_num);
338 struct form *
339 find_form_by_form_view(struct document *document, struct form_view *fv)
341 struct form *form;
343 foreach (form, document->forms) {
344 if (form->form_num == fv->form_num)
345 return form;
347 return NULL;
350 /** Free any data owned by @a fs, but not the struct form_state
351 * itself, because that is normally allocated as part of an array.
352 * @relates form_state */
353 void
354 done_form_state(struct form_state *fs)
356 #ifdef CONFIG_ECMASCRIPT
357 ecmascript_detach_form_state(fs);
358 #endif
359 mem_free_if(fs->value);
362 /** Free @a fv and any data owned by it. This does not call
363 * del_from_list(fv), so the caller must usually do that first.
364 * @relates form_view */
365 void
366 done_form_view(struct form_view *fv)
368 #ifdef CONFIG_ECMASCRIPT
369 ecmascript_detach_form_view(fv);
370 #endif
371 mem_free(fv);
375 get_current_state(struct session *ses)
377 struct document_view *doc_view;
378 struct link *link;
379 struct form_state *fs;
381 assert(ses);
382 if_assert_failed return -1;
383 doc_view = current_frame(ses);
385 assert(doc_view && doc_view->vs && doc_view->document);
386 if_assert_failed return -1;
388 link = get_current_link(doc_view);
389 if (!link || link->type != LINK_SELECT) return -1;
391 fs = find_form_state(doc_view, get_link_form_control(link));
392 if (fs) return fs->state;
393 return -1;
396 void
397 draw_form_entry(struct terminal *term, struct document_view *doc_view,
398 struct link *link)
400 struct form_state *fs;
401 struct form_control *fc;
402 struct view_state *vs;
403 struct box *box;
404 int dx, dy;
406 assert(term && doc_view && doc_view->document && doc_view->vs && link);
407 if_assert_failed return;
409 fc = get_link_form_control(link);
410 assertm(fc != NULL, "link %d has no form control", (int) (link - doc_view->document->links));
411 if_assert_failed return;
413 fs = find_form_state(doc_view, fc);
414 if (!fs) return;
416 box = &doc_view->box;
417 vs = doc_view->vs;
418 dx = box->x - vs->x;
419 dy = box->y - vs->y;
420 switch (fc->type) {
421 unsigned char *s;
422 #ifdef CONFIG_UTF8
423 unsigned char *text, *end, *last_in_view;
424 #endif /* CONFIG_UTF8 */
425 int len;
426 int i, x, y;
428 case FC_TEXT:
429 case FC_PASSWORD:
430 case FC_FILE:
431 if (!link->npoints) break;
433 y = link->points[0].y + dy;
434 if (!row_is_in_box(box, y))
435 break;
437 x = link->points[0].x + dx;
438 #ifdef CONFIG_UTF8
439 if (term->utf8_cp) goto utf8;
440 #endif /* CONFIG_UTF8 */
441 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
442 len = strlen(fs->value) - fs->vpos;
444 for (i = 0; i < fc->size; i++, x++) {
445 unsigned char data;
447 if (!col_is_in_box(box, x)) continue;
449 if (fs->value && i >= -fs->vpos && i < len)
450 data = fc->type != FC_PASSWORD
451 ? fs->value[i + fs->vpos] : '*';
452 else
453 data = '_';
455 draw_char_data(term, x, y, data);
457 break;
458 #ifdef CONFIG_UTF8
459 utf8:
461 retry_after_scroll:
462 text = fs->value;
463 if (!text) text = "";
464 len = strlen(text);
465 int_bounds(&fs->state, 0, len);
466 int_bounds(&fs->vpos, 0, fs->state);
467 end = text + len;
468 text += fs->vpos;
469 last_in_view = NULL;
471 for (i = 0; i < fc->size; ) {
472 unicode_val_T data;
473 int cells, cell;
474 unsigned char *maybe_in_view = text;
476 data = utf8_to_unicode(&text, end);
477 if (data == UCS_NO_CHAR) /* end of string */
478 data = '_';
479 else if (fc->type == FC_PASSWORD)
480 data = '*';
482 cells = unicode_to_cell(data);
483 if (i + cells <= fc->size) {
484 last_in_view = maybe_in_view;
485 if (colspan_is_in_box(box, x + i, cells)) {
486 /* The character fits completely.
487 * Draw the character, and mark any
488 * further cells with UCS_NO_CHAR. */
489 draw_char_data(term, x + i, y, data);
490 for (cell = 1; cell < cells; cell++)
491 draw_char_data(term, x + i + cell,
492 y, UCS_NO_CHAR);
493 goto drew_char;
497 /* The character does not fit completely.
498 * Write UCS_ORPHAN_CELL to the cells that
499 * do fit. */
500 for (cell = 0; cell < cells; cell++) {
501 if (col_is_in_box(box, x + i + cell)
502 && i + cell < fc->size)
503 draw_char_data(term,
504 x + i + cell, y,
505 UCS_ORPHAN_CELL);
508 drew_char:
509 i += cells;
512 /* The int_bounds calls above ensured that the
513 * insertion point cannot be at the left side
514 * of the scrolled-visible part of the text.
515 * However it can still be at the right side.
516 * Check whether we need to change fs->vpos.
518 * This implementation attempts to follow
519 * these rules:
520 * - If the insertion point is at the end of
521 * the string, leave at least one empty cell
522 * so that there is a place for the cursor.
523 * - If a character follows the insertion
524 * point, make that character fully visible;
525 * note the character may be double-width.
526 * - If fc->size < 2, it is not possible to
527 * make a double-width character fully
528 * visible. In this case, it is OK if the
529 * output is ugly, but ELinks must not fall
530 * into an infinite loop or crash.
531 * - The length of the string should not affect
532 * how long this function takes. The width
533 * of the widget naturally will.
534 * - Optimize the case where fields are drawn
535 * several times without being modified.
537 * It follows that:
538 * - If the "for i" loop above hit UCS_NO_CHAR,
539 * then there is no need to scroll.
540 * - When the string ends with a double-width
541 * character that fits in only partially,
542 * then text==end, but the field may have
543 * to be scrolled. */
544 if (fs->value && last_in_view
545 && last_in_view < fs->value + fs->state) {
546 unsigned char *ptr = fs->value + fs->state;
547 int cells = fc->size;
548 enum utf8_step how = (fc->type == FC_PASSWORD)
549 ? UTF8_STEP_CHARACTERS
550 : UTF8_STEP_CELLS_FEWER;
552 /* The insertion point is at the right
553 * side of the scrolled-visible part
554 * of the text. Decide a new fs->vpos
555 * by counting cells backwards from
556 * @ptr. But first advance @ptr past
557 * the character that follows the
558 * insertion point, so that it will be
559 * fully displayed. If there is no
560 * such character, reserve one cell
561 * for the cursor anyway. */
562 if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
563 --cells;
564 ptr = utf8_step_backward(ptr, fs->value,
565 cells, how, NULL);
567 if (fs->vpos != ptr - fs->value) {
568 fs->vpos = ptr - fs->value;
569 goto retry_after_scroll;
572 break;
573 #endif /* CONFIG_UTF8 */
574 case FC_TEXTAREA:
575 draw_textarea(term, fs, doc_view, link);
576 break;
577 case FC_CHECKBOX:
578 case FC_RADIO:
579 if (link->npoints < 2) break;
580 x = link->points[1].x + dx;
581 y = link->points[1].y + dy;
582 if (is_in_box(box, x, y))
583 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
584 break;
585 case FC_SELECT:
586 fixup_select_state(fc, fs);
587 if (fs->state < fc->nvalues)
588 s = fc->labels[fs->state];
589 else
590 /* XXX: when can this happen? --pasky */
591 s = "";
592 #ifdef CONFIG_UTF8
593 if (term->utf8_cp) goto utf8_select;
594 #endif /* CONFIG_UTF8 */
595 len = s ? strlen(s) : 0;
596 for (i = 0; i < link->npoints; i++) {
597 x = link->points[i].x + dx;
598 y = link->points[i].y + dy;
599 if (is_in_box(box, x, y))
600 draw_char_data(term, x, y, i < len ? s[i] : '_');
602 break;
603 #ifdef CONFIG_UTF8
604 utf8_select:
605 text = s;
606 end = strchr((const char *)s, '\0');
607 len = utf8_ptr2cells(text, end);
608 for (i = 0; i < link->npoints; i++) {
609 x = link->points[i].x + dx;
610 y = link->points[i].y + dy;
611 if (is_in_box(box, x, y)) {
612 unicode_val_T data;
613 if (i < len) {
614 int cell;
616 data = utf8_to_unicode(&s, end);
617 cell = unicode_to_cell(data);
618 if (i + 1 < len && cell == 2) {
619 draw_char_data(term, x++, y, data);
621 data = UCS_NO_CHAR;
622 i++;
623 } else if (cell == 2) {
624 data = UCS_ORPHAN_CELL;
626 } else
627 data = '_';
628 draw_char_data(term, x, y, data);
631 break;
632 #endif /* CONFIG_UTF8 */
633 case FC_SUBMIT:
634 case FC_IMAGE:
635 case FC_RESET:
636 case FC_BUTTON:
637 case FC_HIDDEN:
638 break;
642 void
643 draw_forms(struct terminal *term, struct document_view *doc_view)
645 struct link *l1, *l2;
647 assert(term && doc_view);
648 if_assert_failed return;
650 l1 = get_first_link(doc_view);
651 l2 = get_last_link(doc_view);
653 if (!l1 || !l2) {
654 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
655 /* Return path :-). */
656 return;
658 do {
659 struct form_control *fc = get_link_form_control(l1);
661 if (!fc) continue;
662 draw_form_entry(term, doc_view, l1);
664 } while (l1++ < l2);
668 /** @relates submitted_value */
669 void
670 done_submitted_value_list(LIST_OF(struct submitted_value) *list)
672 struct submitted_value *sv, *svtmp;
674 assert(list);
675 if_assert_failed return;
677 foreach (sv, *list) {
678 svtmp = sv;
679 sv = sv->prev;
680 del_from_list(svtmp);
681 done_submitted_value(svtmp);
685 static void
686 add_submitted_value_to_list(struct form_control *fc,
687 struct form_state *fs,
688 LIST_OF(struct submitted_value) *list)
690 struct submitted_value *sub;
691 unsigned char *name;
692 enum form_type type;
693 int position;
695 assert(fc && fs && list);
697 name = fc->name;
698 position = fc->position;
699 type = fc->type;
701 switch (fc->type) {
702 case FC_TEXT:
703 case FC_PASSWORD:
704 case FC_FILE:
705 case FC_TEXTAREA:
706 sub = init_submitted_value(name, fs->value, type, fc, position);
707 if (sub) add_to_list(*list, sub);
708 break;
710 case FC_CHECKBOX:
711 case FC_RADIO:
712 if (!fs->state) break;
713 /* fall through */
715 case FC_SUBMIT:
716 case FC_HIDDEN:
717 case FC_RESET:
718 case FC_BUTTON:
719 sub = init_submitted_value(name, fs->value, type, fc,
720 position);
721 if (sub) add_to_list(*list, sub);
722 break;
724 case FC_SELECT:
725 if (!fc->nvalues) break;
727 fixup_select_state(fc, fs);
728 sub = init_submitted_value(name, fs->value, type, fc, position);
729 if (sub) add_to_list(*list, sub);
730 break;
732 case FC_IMAGE:
733 name = straconcat(fc->name, ".x", (unsigned char *) NULL);
734 if (!name) break;
735 sub = init_submitted_value(name, "0", type, fc, position);
736 mem_free(name);
737 if (sub) add_to_list(*list, sub);
739 name = straconcat(fc->name, ".y", (unsigned char *) NULL);
740 if (!name) break;
741 sub = init_submitted_value(name, "0", type, fc, position);
742 mem_free(name);
743 if (sub) add_to_list(*list, sub);
745 break;
749 static void
750 sort_submitted_values(LIST_OF(struct submitted_value) *list)
752 while (1) {
753 struct submitted_value *sub;
754 int changed = 0;
756 foreach (sub, *list) if (list_has_next(*list, sub))
757 if (sub->next->position < sub->position) {
758 struct submitted_value *next = sub->next;
760 del_from_list(sub);
761 add_at_pos(next, sub);
762 sub = next;
763 changed = 1;
766 foreachback (sub, *list) if (list_has_next(*list, sub))
767 if (sub->next->position < sub->position) {
768 struct submitted_value *next = sub->next;
770 del_from_list(sub);
771 add_at_pos(next, sub);
772 sub = next;
773 changed = 1;
776 if (!changed) break;
780 static void
781 get_successful_controls(struct document_view *doc_view,
782 struct form_control *fc,
783 LIST_OF(struct submitted_value) *list)
785 struct form_control *fc2;
787 assert(doc_view && fc && fc->form && list);
788 if_assert_failed return;
790 foreach (fc2, fc->form->items) {
791 if (((fc2->type != FC_SUBMIT &&
792 fc2->type != FC_IMAGE &&
793 fc2->type != FC_RESET &&
794 fc2->type != FC_BUTTON) || fc2 == fc)
795 && fc2->name && fc2->name[0]) {
796 struct form_state *fs = find_form_state(doc_view, fc2);
798 if (!fs) continue;
800 add_submitted_value_to_list(fc2, fs, list);
804 sort_submitted_values(list);
807 unsigned char *
808 encode_crlf(struct submitted_value *sv)
810 struct string newtext;
811 int i;
813 assert(sv && sv->value);
814 if_assert_failed return NULL;
816 if (!init_string(&newtext)) return NULL;
818 for (i = 0; sv->value[i]; i++) {
819 if (sv->value[i] == '\r') {
820 if (sv->value[i+1] != '\n')
821 add_crlf_to_string(&newtext);
822 } else if (sv->value[i] == '\n')
823 add_crlf_to_string(&newtext);
824 else
825 add_char_to_string(&newtext, sv->value[i]);
828 return newtext.source;
831 static void
832 encode_controls(LIST_OF(struct submitted_value) *l, struct string *data,
833 int cp_from, int cp_to)
835 struct submitted_value *sv;
836 struct conv_table *convert_table = NULL;
837 int lst = 0;
839 assert(l && data);
840 if_assert_failed return;
842 foreach (sv, *l) {
843 unsigned char *p2 = NULL;
845 if (lst)
846 add_char_to_string(data, '&');
847 else
848 lst = 1;
850 encode_uri_string(data, sv->name, strlen(sv->name), 1);
851 add_char_to_string(data, '=');
853 /* Convert back to original encoding (see html_form_control()
854 * for the original recoding). */
855 if (sv->type == FC_TEXTAREA) {
856 unsigned char *p;
858 p = encode_textarea(sv);
859 if (p) {
860 if (!convert_table)
861 convert_table = get_translation_table(cp_from, cp_to);
863 p2 = convert_string(convert_table, p,
864 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
865 mem_free(p);
867 } else if (sv->type == FC_TEXT ||
868 sv->type == FC_PASSWORD) {
869 if (!convert_table)
870 convert_table = get_translation_table(cp_from, cp_to);
872 p2 = convert_string(convert_table, sv->value,
873 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
874 } else if (sv->type == FC_HIDDEN) {
875 p2 = encode_crlf(sv);
876 } else {
877 p2 = stracpy(sv->value);
880 if (p2) {
881 encode_uri_string(data, p2, strlen(p2), 1);
882 mem_free(p2);
889 #define BOUNDARY_LENGTH 32
890 #define realloc_bound_ptrs(bptrs, bptrs_size) \
891 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
893 struct boundary_info {
894 int count;
895 int *offsets;
896 unsigned char string[BOUNDARY_LENGTH];
899 /** @relates boundary_info */
900 static void
901 randomize_boundary(unsigned char *data, int length)
903 int i;
905 random_nonce(data, length);
906 for (i = 0; i < length; i++) {
907 /* Only [0-9A-Za-z]. */
908 data[i] = data[i] & 63;
909 if (data[i] < 10) data[i] += '0';
910 else if (data[i] < 36) data[i] = data[i] - 10 + 'A';
911 else if (data[i] < 62) data[i] = data[i] - 36 + 'a';
912 else data[i] = '0';
916 /** @relates boundary_info */
917 static inline void
918 init_boundary(struct boundary_info *boundary)
920 memset(boundary, 0, sizeof(*boundary));
921 randomize_boundary(boundary->string, BOUNDARY_LENGTH);
924 /** Add boundary to string and save the offset
925 * @relates boundary_info */
926 static inline void
927 add_boundary(struct string *data, struct boundary_info *boundary)
929 add_to_string(data, "--");
931 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
932 boundary->offsets[boundary->count++] = data->length;
934 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
938 /** Format a multipart/form-data body for a POST request.
940 * @param ses
941 * Display an info_box() in the terminal of this session if an error
942 * occurs.
943 * @param[in] l
944 * List of values to be sent to the server.
945 * @param[out] data
946 * Append the body here. This is in the same format as uri.post,
947 * except this never has a Content-Type at the beginning, the
948 * literal parts are not encoded in hexadecimal, and the file names
949 * are not percent-encoded. Therefore the result would be ambiguous
950 * without @a bfs.
951 * @param[out] boundary
952 * A random boundary %string, and a list of offsets where the
953 * boundary was used, so that the caller can in principle change the
954 * %string and update all of its uses if the original one conflicts
955 * with some of the submitted values. However, the caller does not
956 * do that nowadays because reading through the attached files would
957 * be too expensive. It just assumes the boundary is random enough.
958 * @param[out] bfs
959 * List of offsets of names of files to be uploaded. This is how
960 * the caller knows which occurrences of ::FILE_CHAR in @a data
961 * should be encoded and which ones should not.
962 * @param[in] cp_from
963 * Codepage of the submitted-value strings in @a l.
964 * @param[in] cp_to
965 * Codepage wanted by the server.
967 * @todo FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
968 static void
969 encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
970 struct string *data, struct boundary_info *boundary,
971 LIST_OF(struct files_offset) *bfs, int cp_from, int cp_to)
973 struct conv_table *convert_table = NULL;
974 struct submitted_value *sv;
976 assert(ses && l && data && boundary);
977 if_assert_failed return;
979 init_boundary(boundary);
981 foreach (sv, *l) {
982 add_boundary(data, boundary);
983 add_crlf_to_string(data);
985 /** @bug FIXME: name is not encoded.
986 * From RFC 1867:
987 * multipart/form-data contains a series of parts.
988 * Each %part is expected to contain a content-disposition
989 * header where the value is "form-data" and a name attribute
990 * specifies the field name within the form,
991 * e.g., 'content-disposition: form-data; name="xxxxx"',
992 * where xxxxx is the field name corresponding to that field.
993 * Field names originally in non-ASCII character sets may be
994 * encoded using the method outlined in RFC 1522. */
995 add_to_string(data, "Content-Disposition: form-data; name=\"");
996 add_to_string(data, sv->name);
997 add_char_to_string(data, '"');
999 if (sv->type == FC_FILE) {
1000 unsigned char *extension;
1002 add_to_string(data, "; filename=\"");
1003 add_to_string(data, get_filename_position(sv->value));
1004 /* It sends bad data if the file name contains ", but
1005 Netscape does the same */
1006 /* FIXME: We should follow RFCs 1522, 1867,
1007 * 2047 (updated by rfc 2231), to provide correct support
1008 * for non-ASCII and special characters in values. --Zas */
1009 add_char_to_string(data, '"');
1011 /* Add a Content-Type header if the type is configured */
1012 extension = strrchr((const char *)sv->value, '.');
1013 if (extension) {
1014 unsigned char *type = get_extension_content_type(extension);
1016 if (type) {
1017 add_crlf_to_string(data);
1018 add_to_string(data, "Content-Type: ");
1019 add_to_string(data, type);
1020 mem_free(type);
1024 add_crlf_to_string(data);
1025 add_crlf_to_string(data);
1027 if (*sv->value) {
1028 unsigned char *filename;
1029 struct files_offset *bfs_new;
1031 if (get_cmd_opt_bool("anonymous")) {
1032 errno = EPERM;
1033 goto encode_error;
1036 filename = expand_tilde(sv->value);
1037 if (!filename) goto encode_error;
1039 if (access(filename, R_OK)) {
1040 mem_free(filename);
1041 goto encode_error;
1044 bfs_new = mem_calloc(1, sizeof(*bfs_new));
1045 if (!bfs_new) {
1046 mem_free(filename);
1047 goto encode_error;
1049 bfs_new->begin = data->length;
1050 add_char_to_string(data, FILE_CHAR);
1051 add_to_string(data, filename);
1052 add_char_to_string(data, FILE_CHAR);
1053 bfs_new->end = data->length;
1054 add_to_list_end(*bfs, bfs_new);
1055 mem_free(filename);
1057 } else {
1058 add_crlf_to_string(data);
1059 add_crlf_to_string(data);
1061 /* Convert back to original encoding (see
1062 * html_special_form_control() for the original
1063 * recoding). */
1064 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
1065 sv->type == FC_TEXTAREA) {
1066 unsigned char *p;
1068 if (!convert_table)
1069 convert_table = get_translation_table(cp_from,
1070 cp_to);
1072 p = convert_string(convert_table, sv->value,
1073 strlen(sv->value), -1, CSM_FORM, NULL,
1074 NULL, NULL);
1075 if (p) {
1076 add_to_string(data, p);
1077 mem_free(p);
1079 } else {
1080 add_to_string(data, sv->value);
1084 add_crlf_to_string(data);
1087 /* End-boundary */
1088 add_boundary(data, boundary);
1089 add_to_string(data, "--\r\n");
1091 mem_free_if(boundary->offsets);
1092 return;
1094 encode_error:
1095 free_list(*bfs);
1096 mem_free_if(boundary->offsets);
1097 done_string(data);
1099 /* XXX: This error message should move elsewhere. --Zas */
1100 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1101 N_("Error while posting form"), ALIGN_CENTER,
1102 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1103 sv->value, strerror(errno)));
1106 static void
1107 encode_newlines(struct string *string, unsigned char *data)
1109 for (; *data; data++) {
1110 if (*data == '\n' || *data == '\r') {
1111 unsigned char buffer[3];
1113 /* Hex it. */
1114 buffer[0] = '%';
1115 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1116 buffer[2] = hx(((int) *data) & 0xF);
1117 add_bytes_to_string(string, buffer, 3);
1118 } else {
1119 add_char_to_string(string, *data);
1124 static void
1125 encode_text_plain(LIST_OF(struct submitted_value) *l, struct string *data,
1126 int cp_from, int cp_to)
1128 struct submitted_value *sv;
1129 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1131 assert(l && data);
1132 if_assert_failed return;
1134 foreach (sv, *l) {
1135 unsigned char *area51 = NULL;
1136 unsigned char *value = sv->value;
1138 add_to_string(data, sv->name);
1139 add_char_to_string(data, '=');
1141 switch (sv->type) {
1142 case FC_TEXTAREA:
1143 value = area51 = encode_textarea(sv);
1144 if (!area51) break;
1145 /* Fall through */
1146 case FC_HIDDEN:
1147 if (!area51) value = area51 = encode_crlf(sv);
1148 if (!area51) break;
1149 /* Fall through */
1150 case FC_TEXT:
1151 case FC_PASSWORD:
1152 /* Convert back to original encoding (see
1153 * html_form_control() for the original recoding). */
1154 value = convert_string(convert_table, value,
1155 strlen(value), -1, CSM_FORM,
1156 NULL, NULL, NULL);
1157 default:
1158 /* Falling right through to free that textarea stuff */
1159 mem_free_if(area51);
1161 /* Did the conversion fail? */
1162 if (!value) break;
1164 encode_newlines(data, value);
1166 /* Free if we did convert something */
1167 if (value != sv->value) mem_free(value);
1170 add_crlf_to_string(data);
1174 void
1175 do_reset_form(struct document_view *doc_view, struct form *form)
1177 struct form_control *fc;
1179 assert(doc_view && doc_view->document);
1180 if_assert_failed return;
1182 foreach (fc, form->items) {
1183 struct form_state *fs = find_form_state(doc_view, fc);
1185 if (fs) init_form_state(doc_view, fc, fs);
1189 enum frame_event_status
1190 reset_form(struct session *ses, struct document_view *doc_view, int a)
1192 struct link *link = get_current_link(doc_view);
1194 if (!link) return FRAME_EVENT_OK;
1196 do_reset_form(doc_view, get_link_form_control(link)->form);
1197 draw_forms(ses->tab->term, doc_view);
1199 /* Could be the refresh return value and then ditch the draw_forms()
1200 * call. */
1201 return FRAME_EVENT_OK;
1204 struct uri *
1205 get_form_uri(struct session *ses, struct document_view *doc_view,
1206 struct form_control *fc)
1208 struct boundary_info boundary;
1209 INIT_LIST_OF(struct submitted_value, submit);
1210 INIT_LIST_OF(struct files_offset, bfs);
1211 struct string data;
1212 struct string go;
1213 int cp_from, cp_to;
1214 struct uri *uri;
1215 struct form *form;
1217 assert(ses && ses->tab && ses->tab->term);
1218 if_assert_failed return NULL;
1219 assert(doc_view && doc_view->document && fc && fc->form);
1220 if_assert_failed return NULL;
1222 form = fc->form;
1224 if (fc->type == FC_RESET) {
1225 do_reset_form(doc_view, form);
1226 return NULL;
1229 if (!form->action
1230 || !init_string(&data))
1231 return NULL;
1233 get_successful_controls(doc_view, fc, &submit);
1235 cp_from = get_terminal_codepage(ses->tab->term);
1236 cp_to = doc_view->document->cp;
1237 switch (form->method) {
1238 case FORM_METHOD_GET:
1239 case FORM_METHOD_POST:
1240 encode_controls(&submit, &data, cp_from, cp_to);
1241 break;
1243 case FORM_METHOD_POST_MP:
1244 encode_multipart(ses, &submit, &data, &boundary,
1245 &bfs, cp_from, cp_to);
1246 break;
1248 case FORM_METHOD_POST_TEXT_PLAIN:
1249 encode_text_plain(&submit, &data, cp_from, cp_to);
1252 #ifdef CONFIG_FORMHIST
1253 /* XXX: We check data.source here because a NULL value can indicate
1254 * not only a memory allocation failure, but also an error reading
1255 * a file that is to be uploaded. TODO: Distinguish between
1256 * these two classes of errors (is it worth it?). -- Miciah */
1257 if (data.source
1258 && get_opt_bool("document.browse.forms.show_formhist", ses))
1259 memorize_form(ses, &submit, form);
1260 #endif
1262 done_submitted_value_list(&submit);
1264 if (!data.source
1265 || !init_string(&go)) {
1266 done_string(&data);
1267 return NULL;
1270 switch (form->method) {
1271 case FORM_METHOD_GET:
1273 unsigned char *pos = strchr((const char *)form->action, '#');
1275 if (pos) {
1276 add_bytes_to_string(&go, form->action, pos - form->action);
1277 } else {
1278 add_to_string(&go, form->action);
1281 if (strchr((const char *)go.source, '?'))
1282 add_char_to_string(&go, '&');
1283 else
1284 add_char_to_string(&go, '?');
1286 add_string_to_string(&go, &data);
1288 if (pos) add_to_string(&go, pos);
1289 break;
1291 case FORM_METHOD_POST:
1292 case FORM_METHOD_POST_MP:
1293 case FORM_METHOD_POST_TEXT_PLAIN:
1295 /* Note that we end content type here by a simple '\n',
1296 * replaced later by correct '\r\n' in http_send_header(). */
1298 add_to_string(&go, form->action);
1299 add_char_to_string(&go, POST_CHAR);
1300 if (form->method == FORM_METHOD_POST) {
1301 add_to_string(&go, "application/x-www-form-urlencoded\n");
1303 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1304 /* Dunno about this one but we don't want the full
1305 * hextcat thingy. --jonas */
1306 add_to_string(&go, "text/plain\n");
1307 add_to_string(&go, data.source);
1308 break;
1310 } else {
1311 add_to_string(&go, "multipart/form-data; boundary=");
1312 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1313 add_char_to_string(&go, '\n');
1316 if (list_empty(bfs)) {
1317 int i;
1319 for (i = 0; i < data.length; i++) {
1320 unsigned char p[3];
1322 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1323 add_to_string(&go, p);
1325 } else {
1326 struct files_offset *b;
1327 int i = 0;
1329 foreach (b, bfs) {
1330 for (; i < b->begin; i++) {
1331 unsigned char p[3];
1333 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1334 add_to_string(&go, p);
1336 assert(i == b->begin);
1337 assert(b->end - b->begin >= 2);
1338 assert(data.source[b->begin] == FILE_CHAR);
1339 assert(data.source[b->end - 1] == FILE_CHAR);
1340 add_char_to_string(&go, FILE_CHAR);
1341 encode_uri_string(&go, data.source + i + 1,
1342 b->end - b->begin - 2, 0);
1343 add_char_to_string(&go, FILE_CHAR);
1344 i = b->end;
1346 for (; i < data.length; i++) {
1347 unsigned char p[3];
1349 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1350 add_to_string(&go, p);
1356 done_string(&data);
1358 uri = get_uri(go.source, 0);
1359 done_string(&go);
1360 if (uri) {
1361 uri->form = 1;
1363 free_list(bfs);
1365 return uri;
1368 #undef BOUNDARY_LENGTH
1371 enum frame_event_status
1372 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1374 goto_current_link(ses, doc_view, do_reload);
1375 return FRAME_EVENT_OK;
1378 void
1379 submit_given_form(struct session *ses, struct document_view *doc_view,
1380 struct form *form, int do_reload)
1382 /* Added support for submitting forms in hidden
1383 * links in 1.285, commented code can safely be removed once we have made sure the new
1384 * code does the right thing. */
1385 #if 0
1387 struct document *document = doc_view->document;
1388 int link;
1390 for (link = 0; link < document->nlinks; link++) {
1391 struct form_control *fc = get_link_form_control(&document->links[link]);
1393 if (fc && fc->form == form) {
1394 doc_view->vs->current_link = link;
1395 submit_form(ses, doc_view, 0);
1396 return;
1399 #endif
1400 if (!list_empty(form->items)) {
1401 struct form_control *fc = (struct form_control *)form->items.next;
1402 struct uri *uri;
1403 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1405 if (!fc) return;
1406 uri = get_form_uri(ses, doc_view, fc);
1407 if (!uri) return;
1408 goto_uri_frame(ses, uri, form->target, mode);
1409 done_uri(uri);
1413 void
1414 auto_submit_form(struct session *ses)
1416 struct document *document = ses->doc_view->document;
1418 if (!list_empty(document->forms))
1419 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1423 /* menu_func_T */
1424 static void
1425 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1427 unsigned char *filename = filename_;
1428 struct form_state *fs = fs_;
1430 /* The menu code doesn't free the filename data */
1431 mem_free_set(&fs->value, filename);
1432 fs->state = strlen(filename);
1433 redraw_terminal(term);
1436 /* menu_func_T */
1437 static void
1438 file_form_menu(struct terminal *term, void *path_, void *fs_)
1440 unsigned char *path = path_;
1441 struct form_state *fs = fs_;
1443 /* FIXME: It doesn't work for ../../ */
1444 #if 0
1445 int valuelen = strlen(fs->value);
1446 int pathlen = strlen(path);
1447 int no_elevator = 0;
1449 /* Don't add elevators for subdirs menus */
1450 /* It is not perfect at all because fs->value is not updated for each
1451 * newly opened file menu. Maybe it should be dropped. */
1452 for (; valuelen < pathlen; valuelen++) {
1453 if (dir_sep(path[valuelen - 1])) {
1454 no_elevator = 1;
1455 break;
1458 #endif
1460 auto_complete_file(term, 0 /* no_elevator */, path,
1461 set_file_form_state,
1462 file_form_menu, fs);
1466 enum frame_event_status
1467 field_op(struct session *ses, struct document_view *doc_view,
1468 struct link *link, struct term_event *ev)
1470 struct form_control *fc;
1471 struct form_state *fs;
1472 enum edit_action action_id;
1473 unsigned char *text;
1474 int length;
1475 enum frame_event_status status = FRAME_EVENT_REFRESH;
1476 #ifdef CONFIG_UTF8
1477 const unsigned char *ctext;
1478 int utf8 = ses->tab->term->utf8_cp;
1479 #endif /* CONFIG_UTF8 */
1481 assert(ses && doc_view && link && ev);
1482 if_assert_failed return FRAME_EVENT_OK;
1484 fc = get_link_form_control(link);
1485 assertm(fc != NULL, "link has no form control");
1486 if_assert_failed return FRAME_EVENT_OK;
1488 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1489 || (ses->insert_mode == INSERT_MODE_OFF
1490 && !(get_kbd_modifier(ev) & KBD_MOD_PASTE)))
1491 return FRAME_EVENT_IGNORED;
1493 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1495 fs = find_form_state(doc_view, fc);
1496 if (!fs || !fs->value) return FRAME_EVENT_OK;
1498 switch (action_id) {
1499 case ACT_EDIT_LEFT:
1500 #ifdef CONFIG_UTF8
1501 if (fc->type == FC_TEXTAREA) {
1502 status = textarea_op_left(fs, fc, utf8);
1503 break;
1505 if (utf8) {
1506 unsigned char *new_value;
1508 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1509 fs->state = new_value - fs->value;
1510 } else
1511 #endif /* CONFIG_UTF8 */
1512 fs->state = int_max(fs->state - 1, 0);
1513 break;
1514 case ACT_EDIT_RIGHT:
1515 #ifdef CONFIG_UTF8
1516 if (fc->type == FC_TEXTAREA) {
1517 status = textarea_op_right(fs, fc, utf8);
1518 break;
1520 if (utf8) {
1521 unsigned char *text = fs->value + fs->state;
1522 unsigned char *end = strchr((const char *)text, '\0');
1524 utf8_to_unicode(&text, end);
1525 fs->state = (int)(text - fs->value);
1526 } else
1527 #endif /* CONFIG_UTF8 */
1528 fs->state = int_min(fs->state + 1, strlen(fs->value));
1529 break;
1530 case ACT_EDIT_HOME:
1531 #ifdef CONFIG_UTF8
1532 if (fc->type == FC_TEXTAREA) {
1533 status = textarea_op_home(fs, fc, utf8);
1534 } else {
1535 fs->state = 0;
1537 #else
1538 if (fc->type == FC_TEXTAREA) {
1539 status = textarea_op_home(fs, fc);
1540 } else {
1541 fs->state = 0;
1544 #endif /* CONFIG_UTF8 */
1545 break;
1546 case ACT_EDIT_UP:
1547 if (fc->type != FC_TEXTAREA)
1548 status = FRAME_EVENT_IGNORED;
1549 else
1550 #ifdef CONFIG_UTF8
1551 status = textarea_op_up(fs, fc, utf8);
1552 #else
1553 status = textarea_op_up(fs, fc);
1554 #endif /* CONFIG_UTF8 */
1555 break;
1556 case ACT_EDIT_DOWN:
1557 if (fc->type != FC_TEXTAREA)
1558 status = FRAME_EVENT_IGNORED;
1559 else
1560 #ifdef CONFIG_UTF8
1561 status = textarea_op_down(fs, fc, utf8);
1562 #else
1563 status = textarea_op_down(fs, fc);
1564 #endif /* CONFIG_UTF8 */
1565 break;
1566 case ACT_EDIT_END:
1567 if (fc->type == FC_TEXTAREA) {
1568 #ifdef CONFIG_UTF8
1569 status = textarea_op_end(fs, fc, utf8);
1570 #else
1571 status = textarea_op_end(fs, fc);
1572 #endif /* CONFIG_UTF8 */
1573 } else {
1574 fs->state = strlen(fs->value);
1576 break;
1577 case ACT_EDIT_BEGINNING_OF_BUFFER:
1578 if (fc->type == FC_TEXTAREA) {
1579 #ifdef CONFIG_UTF8
1580 status = textarea_op_bob(fs, fc, utf8);
1581 fs->state_cell = 0;
1582 #else
1583 status = textarea_op_bob(fs, fc);
1584 #endif /* CONFIG_UTF8 */
1585 } else {
1586 fs->state = 0;
1588 break;
1589 case ACT_EDIT_END_OF_BUFFER:
1590 if (fc->type == FC_TEXTAREA) {
1591 #ifdef CONFIG_UTF8
1592 status = textarea_op_eob(fs, fc, utf8);
1593 #else
1594 status = textarea_op_eob(fs, fc);
1595 #endif /* CONFIG_UTF8 */
1596 } else {
1597 fs->state = strlen(fs->value);
1599 break;
1600 case ACT_EDIT_OPEN_EXTERNAL:
1601 if (form_field_is_readonly(fc))
1602 status = FRAME_EVENT_IGNORED;
1603 else if (fc->type == FC_TEXTAREA)
1604 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1605 break;
1606 case ACT_EDIT_COPY_CLIPBOARD:
1607 set_clipboard_text(fs->value);
1608 status = FRAME_EVENT_OK;
1609 break;
1610 case ACT_EDIT_CUT_CLIPBOARD:
1611 set_clipboard_text(fs->value);
1612 if (!form_field_is_readonly(fc))
1613 fs->value[0] = 0;
1614 fs->state = 0;
1615 #ifdef CONFIG_UTF8
1616 if (fc->type == FC_TEXTAREA)
1617 fs->state_cell = 0;
1618 #endif /* CONFIG_UTF8 */
1619 break;
1620 case ACT_EDIT_PASTE_CLIPBOARD:
1621 if (form_field_is_readonly(fc)) break;
1623 text = get_clipboard_text();
1624 if (!text) break;
1626 length = strlen(text);
1627 if (length <= fc->maxlength) {
1628 unsigned char *v = mem_realloc(fs->value, length + 1);
1630 if (v) {
1631 fs->value = v;
1632 memmove(v, text, length + 1);
1633 fs->state = strlen(fs->value);
1634 #ifdef CONFIG_UTF8
1635 if (utf8 && fc->type == FC_TEXTAREA)
1636 fs->state_cell = 0;
1637 #endif /* CONFIG_UTF8 */
1640 mem_free(text);
1641 break;
1642 case ACT_EDIT_ENTER:
1643 if (fc->type == FC_TEXTAREA) {
1644 #ifdef CONFIG_UTF8
1645 status = textarea_op_enter(fs, fc, utf8);
1646 #else
1647 status = textarea_op_enter(fs, fc);
1648 #endif /* CONFIG_UTF8 */
1649 break;
1652 /* Set status to ok if either it is not possible to
1653 * submit the form or the posting fails. */
1654 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1655 if ((has_form_submit(fc->form)
1656 && !get_opt_bool("document.browse.forms.auto_submit", ses))
1657 || goto_link(ses, doc_view, link, 0)) {
1658 if (ses->insert_mode == INSERT_MODE_ON)
1659 ses->insert_mode = INSERT_MODE_OFF;
1660 status = FRAME_EVENT_OK;
1662 break;
1663 case ACT_EDIT_BACKSPACE:
1664 if (form_field_is_readonly(fc)) {
1665 status = FRAME_EVENT_IGNORED;
1666 break;
1669 if (!fs->state) {
1670 status = FRAME_EVENT_OK;
1671 break;
1673 #ifdef CONFIG_UTF8
1674 if (utf8) {
1675 int old_state = fs->state;
1676 unsigned char *new_value;
1678 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1679 fs->state = new_value - fs->value;
1681 if (old_state != fs->state) {
1682 if (fc->type == FC_TEXTAREA)
1683 fs->state_cell = 0;
1684 length = strlen(fs->value + old_state) + 1;
1685 memmove(new_value, fs->value + old_state, length);
1687 } else
1688 #endif /* CONFIG_UTF8 */
1690 length = strlen(fs->value + fs->state) + 1;
1691 text = fs->value + fs->state;
1693 memmove(text - 1, text, length);
1694 fs->state--;
1696 break;
1697 case ACT_EDIT_DELETE:
1698 if (form_field_is_readonly(fc)) {
1699 status = FRAME_EVENT_IGNORED;
1700 break;
1703 length = strlen(fs->value);
1704 if (fs->state >= length) {
1705 status = FRAME_EVENT_OK;
1706 break;
1708 #ifdef CONFIG_UTF8
1709 if (utf8) {
1710 unsigned char *end = fs->value + length;
1711 unsigned char *text = fs->value + fs->state;
1712 unsigned char *old = text;
1714 utf8_to_unicode(&text, end);
1715 if (old != text) {
1716 memmove(old, text,
1717 (int)(end - text) + 1);
1719 break;
1721 #endif /* CONFIG_UTF8 */
1722 text = fs->value + fs->state;
1724 memmove(text, text + 1, length - fs->state);
1725 break;
1726 case ACT_EDIT_KILL_TO_BOL:
1727 if (form_field_is_readonly(fc)) {
1728 status = FRAME_EVENT_IGNORED;
1729 break;
1732 if (fs->state <= 0) {
1733 status = FRAME_EVENT_OK;
1734 break;
1737 text = memrchr(fs->value, ASCII_LF, fs->state);
1738 if (text) {
1739 /* Leave the new-line character if it does not
1740 * immediately precede the cursor. */
1741 if (text != &fs->value[fs->state - 1])
1742 text++;
1743 } else {
1744 text = fs->value;
1747 length = strlen(fs->value + fs->state) + 1;
1748 memmove(text, fs->value + fs->state, length);
1750 fs->state = (int) (text - fs->value);
1751 #ifdef CONFIG_UTF8
1752 if (utf8) {
1753 if (fc->type == FC_TEXTAREA)
1754 fs->state_cell = 0;
1756 #endif /* CONFIG_UTF8 */
1757 break;
1758 case ACT_EDIT_KILL_TO_EOL:
1759 if (form_field_is_readonly(fc)) {
1760 status = FRAME_EVENT_IGNORED;
1761 break;
1764 if (!fs->value[fs->state]) {
1765 status = FRAME_EVENT_OK;
1766 break;
1769 text = strchr((const char *)(fs->value + fs->state), ASCII_LF);
1770 if (!text) {
1771 fs->value[fs->state] = '\0';
1772 break;
1775 if (fs->value[fs->state] == ASCII_LF)
1776 ++text;
1778 memmove(fs->value + fs->state, text, strlen(text) + 1);
1779 break;
1781 case ACT_EDIT_KILL_WORD_BACK:
1782 if (form_field_is_readonly(fc)) {
1783 status = FRAME_EVENT_IGNORED;
1784 break;
1787 if (fs->state <= 0) {
1788 status = FRAME_EVENT_OK;
1789 break;
1792 text = &fs->value[fs->state];
1793 while (text > fs->value && isspace(*(text - 1)))
1794 --text;
1795 while (text > fs->value && !isspace(*(text - 1)))
1796 --text;
1797 if (*text == ASCII_LF
1798 && text != &fs->value[fs->state - 1])
1799 text++;
1801 length = strlen(fs->value + fs->state) + 1;
1802 memmove(text, fs->value + fs->state, length);
1804 fs->state = (int) (text - fs->value);
1805 break;
1807 case ACT_EDIT_MOVE_BACKWARD_WORD:
1808 while (fs->state > 0
1809 && isspace(fs->value[fs->state - 1]))
1810 --fs->state;
1811 while (fs->state > 0
1812 && !isspace(fs->value[fs->state - 1]))
1813 --fs->state;
1814 break;
1816 case ACT_EDIT_MOVE_FORWARD_WORD:
1817 while (isspace(fs->value[fs->state]))
1818 ++fs->state;
1819 while (fs->value[fs->state]
1820 && !isspace(fs->value[fs->state]))
1821 ++fs->state;
1822 while (isspace(fs->value[fs->state]))
1823 ++fs->state;
1824 break;
1826 case ACT_EDIT_AUTO_COMPLETE:
1827 if (fc->type != FC_FILE
1828 || form_field_is_readonly(fc)) {
1829 status = FRAME_EVENT_IGNORED;
1830 break;
1833 file_form_menu(ses->tab->term, fs->value, fs);
1834 break;
1836 case ACT_EDIT_CANCEL:
1837 if (ses->insert_mode == INSERT_MODE_ON)
1838 ses->insert_mode = INSERT_MODE_OFF;
1839 else
1840 status = FRAME_EVENT_IGNORED;
1841 break;
1843 case ACT_EDIT_REDRAW:
1844 redraw_terminal_cls(ses->tab->term);
1845 status = FRAME_EVENT_OK;
1846 break;
1848 default:
1849 if (!check_kbd_textinput_key(ev)) {
1850 status = FRAME_EVENT_IGNORED;
1851 break;
1854 if (form_field_is_readonly(fc)
1855 #ifndef CONFIG_UTF8
1856 || strlen(fs->value) >= fc->maxlength
1857 || !insert_in_string(&fs->value, fs->state, "?", 1)
1858 #endif /* CONFIG_UTF8 */
1861 status = FRAME_EVENT_OK;
1862 break;
1865 #ifdef CONFIG_UTF8
1866 /* fs->value is in the charset of the terminal. */
1867 ctext = u2cp_no_nbsp(get_kbd_key(ev),
1868 get_terminal_codepage(ses->tab->term));
1869 length = strlen(ctext);
1871 if (strlen(fs->value) + length > fc->maxlength
1872 || !insert_in_string(&fs->value, fs->state, ctext, length)) {
1873 status = FRAME_EVENT_OK;
1874 break;
1877 fs->state += length;
1878 if (fc->type == FC_TEXTAREA)
1879 fs->state_cell = 0;
1880 #else
1881 fs->value[fs->state++] = get_kbd_key(ev);
1882 #endif /* CONFIG_UTF8 */
1883 break;
1886 return status;
1889 static unsigned char *
1890 get_form_label(struct form_control *fc)
1892 assert(fc->form);
1893 switch (fc->type) {
1894 case FC_RESET:
1895 return N_("Reset form");
1896 case FC_BUTTON:
1897 return N_("Harmless button");
1898 case FC_HIDDEN:
1899 return NULL;
1900 case FC_SUBMIT:
1901 case FC_IMAGE:
1902 if (!fc->form->action) return NULL;
1904 if (fc->form->method == FORM_METHOD_GET)
1905 return N_("Submit form to");
1906 return N_("Post form to");
1907 case FC_RADIO:
1908 return N_("Radio button");
1909 case FC_CHECKBOX:
1910 return N_("Checkbox");
1911 case FC_SELECT:
1912 return N_("Select field");
1913 case FC_TEXT:
1914 return N_("Text field");
1915 case FC_TEXTAREA:
1916 return N_("Text area");
1917 case FC_FILE:
1918 return N_("File upload");
1919 case FC_PASSWORD:
1920 return N_("Password field");
1923 return NULL;
1926 static inline void
1927 add_form_attr_to_string(struct string *string, struct terminal *term,
1928 unsigned char *name, unsigned char *value)
1930 add_to_string(string, ", ");
1931 add_to_string(string, _(name, term));
1932 if (value) {
1933 add_char_to_string(string, ' ');
1934 add_to_string(string, value);
1938 unsigned char *
1939 get_form_info(struct session *ses, struct document_view *doc_view)
1941 struct terminal *term = ses->tab->term;
1942 struct link *link = get_current_link(doc_view);
1943 struct form_control *fc;
1944 unsigned char *label, *key;
1945 struct string str;
1947 assert(link);
1949 fc = get_link_form_control(link);
1950 label = get_form_label(fc);
1951 if (!label) return NULL;
1953 if (!init_string(&str)) return NULL;
1955 add_to_string(&str, _(label, term));
1957 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1958 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1961 switch (fc->type) {
1962 case FC_CHECKBOX:
1963 case FC_RADIO:
1965 struct form_state *fs = find_form_state(doc_view, fc);
1967 if (!fs->value || !fs->value[0])
1968 break;
1970 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1971 break;
1974 case FC_TEXT:
1975 case FC_PASSWORD:
1976 case FC_FILE:
1977 case FC_TEXTAREA:
1979 struct uri *uri;
1980 unsigned char *uristring;
1982 if (form_field_is_readonly(fc)) {
1983 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1986 /* Should we add info about entering insert mode or add info
1987 * about submitting the form? */
1988 if (ses->insert_mode == INSERT_MODE_OFF) {
1989 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1991 if (!key) break;
1993 if (form_field_is_readonly(fc))
1994 label = N_("press %s to navigate");
1995 else
1996 label = N_("press %s to edit");
1998 add_to_string(&str, " (");
1999 add_format_to_string(&str, _(label, term), key);
2000 add_char_to_string(&str, ')');
2001 mem_free(key);
2002 break;
2006 if (fc->type == FC_TEXTAREA)
2007 break;
2009 assert(fc->form);
2011 if (!fc->form->action
2012 || (has_form_submit(fc->form)
2013 && !get_opt_bool("document.browse.forms.auto_submit",
2014 ses)))
2015 break;
2017 uri = get_uri(fc->form->action, 0);
2018 if (!uri) break;
2020 /* Add the uri with password and post info stripped */
2021 uristring = get_uri_string(uri, URI_PUBLIC);
2022 done_uri(uri);
2024 if (!uristring) break;
2026 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
2027 if (!key) {
2028 mem_free(uristring);
2029 break;
2032 if (fc->form->method == FORM_METHOD_GET)
2033 label = N_("press %s to submit to %s");
2034 else
2035 label = N_("press %s to post to %s");
2037 add_to_string(&str, " (");
2038 add_format_to_string(&str, _(label, term), key, uristring);
2039 mem_free(uristring);
2040 mem_free(key);
2042 add_char_to_string(&str, ')');
2043 break;
2045 case FC_SUBMIT:
2046 case FC_IMAGE:
2047 add_char_to_string(&str, ' ');
2049 assert(fc->form);
2050 /* Add the uri with password and post info stripped */
2051 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
2052 break;
2054 case FC_HIDDEN:
2055 case FC_RESET:
2056 case FC_BUTTON:
2057 case FC_SELECT:
2058 break;
2061 if (link->accesskey
2062 && get_opt_bool("document.browse.accesskey.display", ses)) {
2063 add_to_string(&str, " (");
2064 add_accesskey_to_string(&str, link->accesskey);
2065 add_char_to_string(&str, ')');
2068 return str.source;
2071 static void
2072 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
2074 struct session *ses = ses_;
2075 struct document_view *doc_view;
2076 int link_number = *(int *) link_number_;
2078 mem_free(link_number_);
2080 assert(term && ses);
2081 if_assert_failed return;
2083 doc_view = current_frame(ses);
2084 if (!doc_view) return;
2086 assert(doc_view->vs && doc_view->document);
2087 if_assert_failed return;
2089 jump_to_link_number(ses, doc_view, link_number);
2090 refresh_view(ses, doc_view, 0);
2093 void
2094 link_form_menu(struct session *ses)
2096 struct document_view *doc_view;
2097 struct link *link;
2098 struct menu_item *mi;
2099 struct form_control *fc;
2100 struct form *form;
2102 assert(ses);
2103 if_assert_failed return;
2105 doc_view = current_frame(ses);
2106 if (!doc_view) return;
2108 assert(doc_view->vs && doc_view->document);
2109 if_assert_failed return;
2111 link = get_current_link(doc_view);
2112 if (!link) return;
2114 assert(link_is_form(link));
2116 fc = get_link_form_control(link);
2117 if (!fc) return;
2119 form = fc->form;
2121 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2122 if (!mi) return;
2124 foreach (fc, form->items) {
2125 unsigned char *text;
2126 unsigned char *rtext;
2127 int link_number;
2128 struct string str;
2130 switch (fc->type) {
2131 case FC_HIDDEN:
2132 continue;
2134 case FC_SUBMIT:
2135 case FC_IMAGE:
2136 if (!form->action)
2137 text = N_("Useless button");
2138 else
2139 text = N_("Submit button");
2140 break;
2142 default:
2143 text = get_form_label(fc);
2146 link_number = get_form_control_link(doc_view->document, fc);
2147 if (link_number < 0
2148 || !init_string(&str))
2149 continue;
2151 assert(text);
2152 add_to_string(&str, _(text, ses->tab->term));
2154 rtext = fc->name;
2155 if (!rtext) rtext = fc->alt;
2157 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2158 link_form_menu_func, intdup(link_number),
2159 FREE_DATA);
2162 do_menu(ses->tab->term, mi, ses, 1);