Names of enum constants should be in upper case.
[elinks.git] / src / viewer / text / form.c
blobfa4305230ed14ba6bf4de0ad9fd7afc365db7b0f
1 /* Forms viewing/manipulation handling */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #ifndef _GNU_SOURCE
8 #define _GNU_SOURCE /* XXX: we want memrchr() ! */
9 #endif
11 #include <errno.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #ifdef HAVE_UNISTD_H
17 #include <unistd.h>
18 #endif
19 #ifdef HAVE_FCNTL_H
20 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
21 #endif
23 #include "elinks.h"
25 #include "bfu/listmenu.h"
26 #include "bfu/dialog.h"
27 #include "config/kbdbind.h"
28 #include "dialogs/menu.h"
29 #include "document/document.h"
30 #include "document/forms.h"
31 #include "document/html/parser.h"
32 #include "document/view.h"
33 #include "intl/gettext/libintl.h"
34 #include "formhist/formhist.h"
35 #include "mime/mime.h"
36 #include "osdep/ascii.h"
37 #include "osdep/osdep.h"
38 #include "protocol/uri.h"
39 #include "session/session.h"
40 #include "session/task.h"
41 #include "terminal/kbd.h"
42 #include "terminal/terminal.h"
43 #include "terminal/window.h"
44 #include "util/conv.h"
45 #include "util/error.h"
46 #include "util/file.h"
47 #include "util/memory.h"
48 #include "util/string.h"
49 #include "viewer/action.h"
50 #include "viewer/text/draw.h"
51 #include "viewer/text/form.h"
52 #include "viewer/text/link.h"
53 #include "viewer/text/textarea.h"
54 #include "viewer/text/view.h"
55 #include "viewer/text/vs.h"
58 /* TODO: Some of these (particulary those encoding routines) would feel better
59 * in viewer/common/. --pasky */
61 struct submitted_value *
62 init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
63 struct form_control *fc, int position)
65 struct submitted_value *sv;
67 sv = mem_alloc(sizeof(*sv));
68 if (!sv) return NULL;
70 sv->value = stracpy(value);
71 if (!sv->value) { mem_free(sv); return NULL; }
73 sv->name = stracpy(name);
74 if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
76 sv->type = type;
77 sv->form_control = fc;
78 sv->position = position;
80 return sv;
83 void
84 done_submitted_value(struct submitted_value *sv)
86 if (!sv) return;
87 mem_free_if(sv->value);
88 mem_free_if(sv->name);
89 mem_free(sv);
92 static void
93 fixup_select_state(struct form_control *fc, struct form_state *fs)
95 int i;
97 assert(fc && fs);
98 if_assert_failed return;
100 if (fs->state >= 0
101 && fs->state < fc->nvalues
102 && !strcmp(fc->values[fs->state], fs->value))
103 return;
105 for (i = 0; i < fc->nvalues; i++)
106 if (!strcmp(fc->values[i], fs->value)) {
107 fs->state = i;
108 return;
111 fs->state = 0;
113 mem_free_set(&fs->value, stracpy(fc->nvalues
114 ? fc->values[0]
115 : (unsigned char *) ""));
118 /* menu_func_T */
119 void
120 selected_item(struct terminal *term, void *item_, void *ses_)
122 struct session *ses = ses_;
123 int item = (long) item_;
124 struct document_view *doc_view;
125 struct link *link;
126 struct form_state *fs;
127 struct form_control *fc;
129 assert(term && ses);
130 if_assert_failed return;
131 doc_view = current_frame(ses);
133 assert(doc_view && doc_view->vs && doc_view->document);
134 if_assert_failed return;
136 link = get_current_link(doc_view);
137 if (!link || link->type != LINK_SELECT) return;
139 fc = get_link_form_control(link);
140 fs = find_form_state(doc_view, fc);
141 if (fs) {
142 if (item >= 0 && item < fc->nvalues) {
143 fs->state = item;
144 mem_free_set(&fs->value, stracpy(fc->values[item]));
146 fixup_select_state(fc, fs);
149 refresh_view(ses, doc_view, 0);
152 static void
153 init_form_state(struct form_control *fc, struct form_state *fs)
155 assert(fc && fs);
156 if_assert_failed return;
158 mem_free_set(&fs->value, NULL);
160 switch (fc->type) {
161 case FC_TEXT:
162 case FC_PASSWORD:
163 case FC_TEXTAREA:
164 fs->value = stracpy(fc->default_value);
165 fs->state = strlen(fc->default_value);
166 #ifdef CONFIG_UTF8
167 if (fc->type == FC_TEXTAREA)
168 fs->state_cell = 0;
169 #endif /* CONFIG_UTF8 */
170 fs->vpos = 0;
171 break;
172 case FC_FILE:
173 fs->value = stracpy("");
174 fs->state = 0;
175 fs->vpos = 0;
176 break;
177 case FC_SELECT:
178 fs->value = stracpy(fc->default_value);
179 fs->state = fc->default_state;
180 fixup_select_state(fc, fs);
181 break;
182 case FC_CHECKBOX:
183 case FC_RADIO:
184 fs->state = fc->default_state;
185 /* Fall-through */
186 case FC_SUBMIT:
187 case FC_IMAGE:
188 case FC_RESET:
189 case FC_BUTTON:
190 case FC_HIDDEN:
191 fs->value = stracpy(fc->default_value);
192 break;
197 struct form_state *
198 find_form_state(struct document_view *doc_view, struct form_control *fc)
200 struct view_state *vs;
201 struct form_state *fs;
202 int n;
204 assert(doc_view && doc_view->vs && fc);
205 if_assert_failed return NULL;
207 vs = doc_view->vs;
208 n = fc->g_ctrl_num;
210 if (n >= vs->form_info_len) {
211 int nn = n + 1;
213 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
214 if (!fs) return NULL;
215 vs->form_info = fs;
216 vs->form_info_len = nn;
218 fs = &vs->form_info[n];
220 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
221 && fs->g_ctrl_num == fc->g_ctrl_num
222 && fs->position == fc->position
223 && fs->type == fc->type)
224 return fs;
226 mem_free_if(fs->value);
227 memset(fs, 0, sizeof(*fs));
228 fs->form_view = find_form_view(doc_view, fc->form);
229 fs->g_ctrl_num = fc->g_ctrl_num;
230 fs->position = fc->position;
231 fs->type = fc->type;
232 init_form_state(fc, fs);
234 return fs;
237 struct form_control *
238 find_form_control(struct document *document, struct form_state *fs)
240 struct form *form = find_form_by_form_view(document, fs->form_view);
241 struct form_control *fc;
243 foreach (fc, form->items) {
244 if (fs->g_ctrl_num == fc->g_ctrl_num
245 && fs->position == fc->position
246 && fs->type == fc->type)
247 return fc;
250 return NULL;
253 struct form_view *
254 find_form_view_in_vs(struct view_state *vs, int form_num)
256 struct form_view *fv;
258 assert(vs);
260 foreach (fv, vs->forms)
261 if (fv->form_num == form_num)
262 return fv;
264 fv = mem_calloc(1, sizeof(*fv));
265 fv->form_num = form_num;
266 add_to_list(vs->forms, fv);
267 return fv;
270 struct form_view *
271 find_form_view(struct document_view *doc_view, struct form *form)
273 return find_form_view_in_vs(doc_view->vs, form->form_num);
276 struct form *
277 find_form_by_form_view(struct document *document, struct form_view *fv)
279 struct form *form;
281 foreach (form, document->forms) {
282 if (form->form_num == fv->form_num)
283 return form;
285 return NULL;
290 get_current_state(struct session *ses)
292 struct document_view *doc_view;
293 struct link *link;
294 struct form_state *fs;
296 assert(ses);
297 if_assert_failed return -1;
298 doc_view = current_frame(ses);
300 assert(doc_view && doc_view->vs && doc_view->document);
301 if_assert_failed return -1;
303 link = get_current_link(doc_view);
304 if (!link || link->type != LINK_SELECT) return -1;
306 fs = find_form_state(doc_view, get_link_form_control(link));
307 if (fs) return fs->state;
308 return -1;
311 void
312 draw_form_entry(struct terminal *term, struct document_view *doc_view,
313 struct link *link)
315 struct form_state *fs;
316 struct form_control *fc;
317 struct view_state *vs;
318 struct box *box;
319 int dx, dy;
321 assert(term && doc_view && doc_view->document && doc_view->vs && link);
322 if_assert_failed return;
324 fc = get_link_form_control(link);
325 assertm(fc, "link %d has no form control", (int) (link - doc_view->document->links));
326 if_assert_failed return;
328 fs = find_form_state(doc_view, fc);
329 if (!fs) return;
331 box = &doc_view->box;
332 vs = doc_view->vs;
333 dx = box->x - vs->x;
334 dy = box->y - vs->y;
335 switch (fc->type) {
336 unsigned char *s;
337 #ifdef CONFIG_UTF8
338 unsigned char *text, *end, *last_in_view;
339 int retried;
340 #endif /* CONFIG_UTF8 */
341 int len;
342 int i, x, y;
344 case FC_TEXT:
345 case FC_PASSWORD:
346 case FC_FILE:
347 if (!link->npoints) break;
349 y = link->points[0].y + dy;
350 if (!row_is_in_box(box, y))
351 break;
353 x = link->points[0].x + dx;
354 #ifdef CONFIG_UTF8
355 if (term->utf8) goto utf8;
356 #endif /* CONFIG_UTF8 */
357 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
358 len = strlen(fs->value) - fs->vpos;
360 for (i = 0; i < fc->size; i++, x++) {
361 unsigned char data;
363 if (!col_is_in_box(box, x)) continue;
365 if (fs->value && i >= -fs->vpos && i < len)
366 data = fc->type != FC_PASSWORD
367 ? fs->value[i + fs->vpos] : '*';
368 else
369 data = '_';
371 draw_char_data(term, x, y, data);
373 break;
374 #ifdef CONFIG_UTF8
375 utf8:
376 retried = 0;
378 retry_after_scroll:
379 text = fs->value;
380 if (!text) text = "";
381 len = strlen(text);
382 int_bounds(&fs->state, 0, len);
383 int_bounds(&fs->vpos, 0, fs->state);
384 end = text + len;
385 text += fs->vpos;
386 last_in_view = NULL;
388 for (i = 0; i < fc->size; ) {
389 unicode_val_T data;
390 int cells, cell;
391 unsigned char *maybe_in_view = text;
393 data = utf8_to_unicode(&text, end);
394 if (data == UCS_NO_CHAR) /* end of string */
395 data = '_';
396 else if (fc->type == FC_PASSWORD)
397 data = '*';
399 cells = unicode_to_cell(data);
400 if (i + cells <= fc->size) {
401 last_in_view = maybe_in_view;
402 if (colspan_is_in_box(box, x + i, cells)) {
403 /* The character fits completely.
404 * Draw the character, and mark any
405 * further cells with UCS_NO_CHAR. */
406 draw_char_data(term, x + i, y, data);
407 for (cell = 1; cell < cells; cell++)
408 draw_char_data(term, x + i + cell,
409 y, UCS_NO_CHAR);
410 goto drew_char;
414 /* The character does not fit completely.
415 * Write spaces to the cells that do fit. */
416 for (cell = 0; cell < cells; cell++) {
417 if (col_is_in_box(box, x + i + cell)
418 && i + cell < fc->size)
419 draw_char_data(term,
420 x + i + cell,
421 y, ' ');
424 drew_char:
425 i += cells;
428 /* The int_bounds calls above ensured that the
429 * insertion point cannot be at the left side
430 * of the scrolled-visible part of the text.
431 * However it can still be at the right side.
432 * Check whether we need to change fs->vpos.
434 * This implementation attempts to follow
435 * these rules:
436 * - If the insertion point is at the end of
437 * the string, leave at least one empty cell
438 * so that there is a place for the cursor.
439 * - If a character follows the insertion
440 * point, make that character fully visible;
441 * note the character may be double-width.
442 * - If fc->size < 2, it is not possible to
443 * make a double-width character fully
444 * visible. In this case, it is OK if the
445 * output is ugly, but ELinks must not fall
446 * into an infinite loop or crash.
447 * - The length of the string should not affect
448 * how long this function takes. The width
449 * of the widget naturally will.
450 * - Optimize the case where fields are drawn
451 * several times without being modified.
453 * It follows that:
454 * - If the "for i" loop above hit UCS_NO_CHAR,
455 * then there is no need to scroll.
456 * - When the string ends with a double-width
457 * character that fits in only partially,
458 * then text==end, but the field may have
459 * to be scrolled. */
460 if (fs->value && last_in_view
461 && last_in_view < fs->value + fs->state) {
462 unsigned char *ptr = fs->value + fs->state;
463 int cells = fc->size;
464 enum utf8_step how = (fc->type == FC_PASSWORD)
465 ? UTF8_STEP_CHARACTERS
466 : UTF8_STEP_CELLS_FEWER;
468 /* The insertion point is at the right
469 * side of the scrolled-visible part
470 * of the text. Decide a new fs->vpos
471 * by counting cells backwards from
472 * @ptr. But first advance @ptr past
473 * the character that follows the
474 * insertion point, so that it will be
475 * fully displayed. If there is no
476 * such character, reserve one cell
477 * for the cursor anyway. */
478 if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
479 --cells;
480 ptr = utf8_step_backward(ptr, fs->value,
481 cells, how, NULL);
483 if (fs->vpos != ptr - fs->value) {
484 fs->vpos = ptr - fs->value;
485 retried = 1;
486 goto retry_after_scroll;
489 break;
490 #endif /* CONFIG_UTF8 */
491 case FC_TEXTAREA:
492 draw_textarea(term, fs, doc_view, link);
493 break;
494 case FC_CHECKBOX:
495 case FC_RADIO:
496 if (link->npoints < 2) break;
497 x = link->points[1].x + dx;
498 y = link->points[1].y + dy;
499 if (is_in_box(box, x, y))
500 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
501 break;
502 case FC_SELECT:
503 fixup_select_state(fc, fs);
504 if (fs->state < fc->nvalues)
505 s = fc->labels[fs->state];
506 else
507 /* XXX: when can this happen? --pasky */
508 s = "";
509 #ifdef CONFIG_UTF8
510 if (term->utf8) goto utf8_select;
511 #endif /* CONFIG_UTF8 */
512 len = s ? strlen(s) : 0;
513 for (i = 0; i < link->npoints; i++) {
514 x = link->points[i].x + dx;
515 y = link->points[i].y + dy;
516 if (is_in_box(box, x, y))
517 draw_char_data(term, x, y, i < len ? s[i] : '_');
519 break;
520 #ifdef CONFIG_UTF8
521 utf8_select:
522 text = s;
523 end = strchr(s, '\0');
524 len = utf8_ptr2cells(text, end);
525 for (i = 0; i < link->npoints; i++) {
526 x = link->points[i].x + dx;
527 y = link->points[i].y + dy;
528 if (is_in_box(box, x, y)) {
529 unicode_val_T data;
530 if (i < len) {
531 int cell;
533 data = utf8_to_unicode(&s, end);
534 cell = unicode_to_cell(data);
535 if (i + 1 < len && cell == 2) {
536 draw_char_data(term, x++, y, data);
538 data = UCS_NO_CHAR;
539 i++;
540 } else if (cell == 2) {
541 data = ' ';
543 } else
544 data = '_';
545 draw_char_data(term, x, y, data);
548 break;
549 #endif /* CONFIG_UTF8 */
550 case FC_SUBMIT:
551 case FC_IMAGE:
552 case FC_RESET:
553 case FC_BUTTON:
554 case FC_HIDDEN:
555 break;
559 void
560 draw_forms(struct terminal *term, struct document_view *doc_view)
562 struct link *l1, *l2;
564 assert(term && doc_view);
565 if_assert_failed return;
567 l1 = get_first_link(doc_view);
568 l2 = get_last_link(doc_view);
570 if (!l1 || !l2) {
571 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
572 /* Return path :-). */
573 return;
575 do {
576 struct form_control *fc = get_link_form_control(l1);
578 if (!fc) continue;
579 #ifdef CONFIG_FORMHIST
580 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD) {
581 unsigned char *value;
583 assert(fc->form);
584 value = get_form_history_value(fc->form->action, fc->name);
586 if (value)
587 mem_free_set(&fc->default_value,
588 stracpy(value));
590 #endif /* CONFIG_FORMHIST */
591 draw_form_entry(term, doc_view, l1);
593 } while (l1++ < l2);
597 void
598 done_submitted_value_list(struct list_head *list)
600 struct submitted_value *sv, *svtmp;
602 assert(list);
603 if_assert_failed return;
605 foreach (sv, *list) {
606 svtmp = sv;
607 sv = sv->prev;
608 del_from_list(svtmp);
609 done_submitted_value(svtmp);
613 static void
614 add_submitted_value_to_list(struct form_control *fc,
615 struct form_state *fs,
616 struct list_head *list)
618 struct submitted_value *sub;
619 unsigned char *name;
620 enum form_type type;
621 int position;
623 assert(fc && fs && list);
625 name = fc->name;
626 position = fc->position;
627 type = fc->type;
629 switch (fc->type) {
630 case FC_TEXT:
631 case FC_PASSWORD:
632 case FC_FILE:
633 case FC_TEXTAREA:
634 sub = init_submitted_value(name, fs->value, type, fc, position);
635 if (sub) add_to_list(*list, sub);
636 break;
638 case FC_CHECKBOX:
639 case FC_RADIO:
640 if (!fs->state) break;
641 /* fall through */
643 case FC_SUBMIT:
644 case FC_HIDDEN:
645 case FC_RESET:
646 case FC_BUTTON:
647 sub = init_submitted_value(name, fs->value, type, fc,
648 position);
649 if (sub) add_to_list(*list, sub);
650 break;
652 case FC_SELECT:
653 if (!fc->nvalues) break;
655 fixup_select_state(fc, fs);
656 sub = init_submitted_value(name, fs->value, type, fc, position);
657 if (sub) add_to_list(*list, sub);
658 break;
660 case FC_IMAGE:
661 name = straconcat(fc->name, ".x", NULL);
662 if (!name) break;
663 sub = init_submitted_value(name, "0", type, fc, position);
664 mem_free(name);
665 if (sub) add_to_list(*list, sub);
667 name = straconcat(fc->name, ".y", NULL);
668 if (!name) break;
669 sub = init_submitted_value(name, "0", type, fc, position);
670 mem_free(name);
671 if (sub) add_to_list(*list, sub);
673 break;
677 static void
678 sort_submitted_values(struct list_head *list)
680 while (1) {
681 struct submitted_value *sub;
682 int changed = 0;
684 foreach (sub, *list) if (list_has_next(*list, sub))
685 if (sub->next->position < sub->position) {
686 struct submitted_value *next = sub->next;
688 del_from_list(sub);
689 add_at_pos(next, sub);
690 sub = next;
691 changed = 1;
694 foreachback (sub, *list) if (list_has_next(*list, sub))
695 if (sub->next->position < sub->position) {
696 struct submitted_value *next = sub->next;
698 del_from_list(sub);
699 add_at_pos(next, sub);
700 sub = next;
701 changed = 1;
704 if (!changed) break;
708 static void
709 get_successful_controls(struct document_view *doc_view,
710 struct form_control *fc, struct list_head *list)
712 struct form_control *fc2;
714 assert(doc_view && fc && fc->form && list);
715 if_assert_failed return;
717 foreach (fc2, fc->form->items) {
718 if (((fc2->type != FC_SUBMIT &&
719 fc2->type != FC_IMAGE &&
720 fc2->type != FC_RESET &&
721 fc2->type != FC_BUTTON) || fc2 == fc)
722 && fc2->name && fc2->name[0]) {
723 struct form_state *fs = find_form_state(doc_view, fc2);
725 if (!fs) continue;
727 add_submitted_value_to_list(fc2, fs, list);
731 sort_submitted_values(list);
734 static void
735 encode_controls(struct list_head *l, struct string *data,
736 int cp_from, int cp_to)
738 struct submitted_value *sv;
739 struct conv_table *convert_table = NULL;
740 int lst = 0;
742 assert(l && data);
743 if_assert_failed return;
745 foreach (sv, *l) {
746 unsigned char *p2 = NULL;
748 if (lst)
749 add_char_to_string(data, '&');
750 else
751 lst = 1;
753 encode_uri_string(data, sv->name, strlen(sv->name), 1);
754 add_char_to_string(data, '=');
756 /* Convert back to original encoding (see html_form_control()
757 * for the original recoding). */
758 if (sv->type == FC_TEXTAREA) {
759 unsigned char *p;
761 p = encode_textarea(sv);
762 if (p) {
763 if (!convert_table)
764 convert_table = get_translation_table(cp_from, cp_to);
766 p2 = convert_string(convert_table, p,
767 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
768 mem_free(p);
770 } else if (sv->type == FC_TEXT ||
771 sv->type == FC_PASSWORD) {
772 if (!convert_table)
773 convert_table = get_translation_table(cp_from, cp_to);
775 p2 = convert_string(convert_table, sv->value,
776 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
777 } else {
778 p2 = stracpy(sv->value);
781 if (p2) {
782 encode_uri_string(data, p2, strlen(p2), 1);
783 mem_free(p2);
790 #define BOUNDARY_LENGTH 32
791 #define realloc_bound_ptrs(bptrs, bptrs_size) \
792 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
794 struct boundary_info {
795 int count;
796 int *offsets;
797 unsigned char string[BOUNDARY_LENGTH];
800 static inline void
801 init_boundary(struct boundary_info *boundary)
803 memset(boundary, 0, sizeof(*boundary));
804 memset(boundary->string, '0', BOUNDARY_LENGTH);
807 /* Add boundary to string and save the offset */
808 static inline void
809 add_boundary(struct string *data, struct boundary_info *boundary)
811 add_to_string(data, "--");
813 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
814 boundary->offsets[boundary->count++] = data->length;
816 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
819 static inline unsigned char *
820 increment_boundary_counter(struct boundary_info *boundary)
822 int j;
824 /* This is just a decimal string incrementation */
825 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
826 if (boundary->string[j]++ < '9')
827 return boundary->string;
829 boundary->string[j] = '0';
832 INTERNAL("Form data boundary counter overflow");
834 return NULL;
837 static inline void
838 check_boundary(struct string *data, struct boundary_info *boundary)
840 unsigned char *bound = boundary->string;
841 int i;
843 /* Search between all boundaries. There is a starting and an ending
844 * boundary so only check the range of chars after the current offset
845 * and before the next offset. If some string in the form data matches
846 * the boundary string it is changed. */
847 for (i = 0; i < boundary->count - 1; i++) {
848 /* Start after the boundary string and also jump past the
849 * "\r\nContent-Disposition: form-data; name=\"" string added
850 * before any form data. */
851 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
853 /* End so that there is atleast BOUNDARY_LENGTH chars to
854 * compare. Subtract 2 char because there is no need to also
855 * compare the '--' prefix that is part of the boundary. */
856 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
857 unsigned char *pos = data->source + start_offset;
858 unsigned char *end = data->source + end_offset;
860 for (; pos <= end; pos++) {
861 if (memcmp(pos, bound, BOUNDARY_LENGTH))
862 continue;
864 /* If incrementing causes overflow bail out. There is
865 * no need to reset the boundary string with '0' since
866 * that is already done when incrementing. */
867 if (!increment_boundary_counter(boundary))
868 return;
870 /* Else start checking all boundaries using the new
871 * boundary string */
872 i = 0;
873 break;
877 /* Now update all the boundaries with the unique boundary string */
878 for (i = 0; i < boundary->count; i++)
879 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
882 /* FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
883 static void
884 encode_multipart(struct session *ses, struct list_head *l, struct string *data,
885 struct boundary_info *boundary, int cp_from, int cp_to)
887 struct conv_table *convert_table = NULL;
888 struct submitted_value *sv;
890 assert(ses && l && data && boundary);
891 if_assert_failed return;
893 init_boundary(boundary);
895 foreach (sv, *l) {
896 add_boundary(data, boundary);
897 add_crlf_to_string(data);
899 /* FIXME: name is not encoded.
900 * from RFC 1867:
901 * multipart/form-data contains a series of parts.
902 * Each part is expected to contain a content-disposition
903 * header where the value is "form-data" and a name attribute
904 * specifies the field name within the form,
905 * e.g., 'content-disposition: form-data; name="xxxxx"',
906 * where xxxxx is the field name corresponding to that field.
907 * Field names originally in non-ASCII character sets may be
908 * encoded using the method outlined in RFC 1522. */
909 add_to_string(data, "Content-Disposition: form-data; name=\"");
910 add_to_string(data, sv->name);
911 add_char_to_string(data, '"');
913 if (sv->type == FC_FILE) {
914 #define F_BUFLEN 1024
915 int fh;
916 unsigned char buffer[F_BUFLEN];
917 unsigned char *extension;
919 add_to_string(data, "; filename=\"");
920 add_to_string(data, get_filename_position(sv->value));
921 /* It sends bad data if the file name contains ", but
922 Netscape does the same */
923 /* FIXME: We should follow RFCs 1522, 1867,
924 * 2047 (updated by rfc 2231), to provide correct support
925 * for non-ASCII and special characters in values. --Zas */
926 add_char_to_string(data, '"');
928 /* Add a Content-Type header if the type is configured */
929 extension = strrchr(sv->value, '.');
930 if (extension) {
931 unsigned char *type = get_extension_content_type(extension);
933 if (type) {
934 add_crlf_to_string(data);
935 add_to_string(data, "Content-Type: ");
936 add_to_string(data, type);
937 mem_free(type);
941 add_crlf_to_string(data);
942 add_crlf_to_string(data);
944 if (*sv->value) {
945 unsigned char *filename;
947 if (get_cmd_opt_bool("anonymous")) {
948 errno = EPERM;
949 goto encode_error;
952 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
953 filename = expand_tilde(sv->value);
954 if (!filename) goto encode_error;
956 fh = open(filename, O_RDONLY);
957 mem_free(filename);
959 if (fh == -1) goto encode_error;
960 set_bin(fh);
961 while (1) {
962 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
964 if (rd) {
965 if (rd == -1) {
966 close(fh);
967 goto encode_error;
970 add_bytes_to_string(data, buffer, rd);
972 } else {
973 break;
976 close(fh);
978 #undef F_BUFLEN
979 } else {
980 add_crlf_to_string(data);
981 add_crlf_to_string(data);
983 /* Convert back to original encoding (see
984 * html_form_control() for the original recoding). */
985 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
986 sv->type == FC_TEXTAREA) {
987 unsigned char *p;
989 if (!convert_table)
990 convert_table = get_translation_table(cp_from,
991 cp_to);
993 p = convert_string(convert_table, sv->value,
994 strlen(sv->value), -1, CSM_FORM, NULL,
995 NULL, NULL);
996 if (p) {
997 add_to_string(data, p);
998 mem_free(p);
1000 } else {
1001 add_to_string(data, sv->value);
1005 add_crlf_to_string(data);
1008 /* End-boundary */
1009 add_boundary(data, boundary);
1010 add_to_string(data, "--\r\n");
1012 check_boundary(data, boundary);
1014 mem_free_if(boundary->offsets);
1015 return;
1017 encode_error:
1018 mem_free_if(boundary->offsets);
1019 done_string(data);
1021 /* XXX: This error message should move elsewhere. --Zas */
1022 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1023 N_("Error while posting form"), ALIGN_CENTER,
1024 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1025 sv->value, strerror(errno)));
1028 static void
1029 encode_newlines(struct string *string, unsigned char *data)
1031 for (; *data; data++) {
1032 if (*data == '\n' || *data == '\r') {
1033 unsigned char buffer[3];
1035 /* Hex it. */
1036 buffer[0] = '%';
1037 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1038 buffer[2] = hx(((int) *data) & 0xF);
1039 add_bytes_to_string(string, buffer, 3);
1040 } else {
1041 add_char_to_string(string, *data);
1046 static void
1047 encode_text_plain(struct list_head *l, struct string *data,
1048 int cp_from, int cp_to)
1050 struct submitted_value *sv;
1051 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1053 assert(l && data);
1054 if_assert_failed return;
1056 foreach (sv, *l) {
1057 unsigned char *area51 = NULL;
1058 unsigned char *value = sv->value;
1060 add_to_string(data, sv->name);
1061 add_char_to_string(data, '=');
1063 switch (sv->type) {
1064 case FC_TEXTAREA:
1065 value = area51 = encode_textarea(sv);
1066 if (!area51) break;
1067 /* Fall through */
1068 case FC_TEXT:
1069 case FC_PASSWORD:
1070 /* Convert back to original encoding (see
1071 * html_form_control() for the original recoding). */
1072 value = convert_string(convert_table, value,
1073 strlen(value), -1, CSM_FORM,
1074 NULL, NULL, NULL);
1075 default:
1076 /* Falling right through to free that textarea stuff */
1077 mem_free_if(area51);
1079 /* Did the conversion fail? */
1080 if (!value) break;
1082 encode_newlines(data, value);
1084 /* Free if we did convert something */
1085 if (value != sv->value) mem_free(value);
1088 add_crlf_to_string(data);
1092 void
1093 do_reset_form(struct document_view *doc_view, struct form *form)
1095 struct form_control *fc;
1097 assert(doc_view && doc_view->document);
1098 if_assert_failed return;
1100 foreach (fc, form->items) {
1101 struct form_state *fs = find_form_state(doc_view, fc);
1103 if (fs) init_form_state(fc, fs);
1107 enum frame_event_status
1108 reset_form(struct session *ses, struct document_view *doc_view, int a)
1110 struct link *link = get_current_link(doc_view);
1112 if (!link) return FRAME_EVENT_OK;
1114 do_reset_form(doc_view, get_link_form_control(link)->form);
1115 draw_forms(ses->tab->term, doc_view);
1117 /* Could be the refresh return value and then ditch the draw_forms()
1118 * call. */
1119 return FRAME_EVENT_OK;
1122 struct uri *
1123 get_form_uri(struct session *ses, struct document_view *doc_view,
1124 struct form_control *fc)
1126 struct boundary_info boundary;
1127 INIT_LIST_HEAD(submit);
1128 struct string data;
1129 struct string go;
1130 int cp_from, cp_to;
1131 struct uri *uri;
1132 struct form *form;
1134 assert(ses && ses->tab && ses->tab->term);
1135 if_assert_failed return NULL;
1136 assert(doc_view && doc_view->document && fc && fc->form);
1137 if_assert_failed return NULL;
1139 form = fc->form;
1141 if (fc->type == FC_RESET) {
1142 do_reset_form(doc_view, form);
1143 return NULL;
1146 if (!form->action
1147 || !init_string(&data))
1148 return NULL;
1150 get_successful_controls(doc_view, fc, &submit);
1152 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset");
1153 cp_to = doc_view->document->cp;
1154 switch (form->method) {
1155 case FORM_METHOD_GET:
1156 case FORM_METHOD_POST:
1157 encode_controls(&submit, &data, cp_from, cp_to);
1158 break;
1160 case FORM_METHOD_POST_MP:
1161 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1162 break;
1164 case FORM_METHOD_POST_TEXT_PLAIN:
1165 encode_text_plain(&submit, &data, cp_from, cp_to);
1168 #ifdef CONFIG_FORMHIST
1169 /* XXX: We check data.source here because a NULL value can indicate
1170 * not only a memory allocation failure, but also an error reading
1171 * a file that is to be uploaded. TODO: Distinguish between
1172 * these two classes of errors (is it worth it?). -- Miciah */
1173 if (data.source
1174 && get_opt_bool("document.browse.forms.show_formhist"))
1175 memorize_form(ses, &submit, form);
1176 #endif
1178 done_submitted_value_list(&submit);
1180 if (!data.source
1181 || !init_string(&go)) {
1182 done_string(&data);
1183 return NULL;
1186 switch (form->method) {
1187 case FORM_METHOD_GET:
1189 unsigned char *pos = strchr(form->action, '#');
1191 if (pos) {
1192 add_bytes_to_string(&go, form->action, pos - form->action);
1193 } else {
1194 add_to_string(&go, form->action);
1197 if (strchr(go.source, '?'))
1198 add_char_to_string(&go, '&');
1199 else
1200 add_char_to_string(&go, '?');
1202 add_string_to_string(&go, &data);
1204 if (pos) add_to_string(&go, pos);
1205 break;
1207 case FORM_METHOD_POST:
1208 case FORM_METHOD_POST_MP:
1209 case FORM_METHOD_POST_TEXT_PLAIN:
1211 /* Note that we end content type here by a simple '\n',
1212 * replaced later by correct '\r\n' in http_send_header(). */
1213 int i;
1215 add_to_string(&go, form->action);
1216 add_char_to_string(&go, POST_CHAR);
1217 if (form->method == FORM_METHOD_POST) {
1218 add_to_string(&go, "application/x-www-form-urlencoded\n");
1220 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1221 /* Dunno about this one but we don't want the full
1222 * hextcat thingy. --jonas */
1223 add_to_string(&go, "text/plain\n");
1224 add_to_string(&go, data.source);
1225 break;
1227 } else {
1228 add_to_string(&go, "multipart/form-data; boundary=");
1229 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1230 add_char_to_string(&go, '\n');
1233 for (i = 0; i < data.length; i++) {
1234 unsigned char p[3];
1236 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1237 add_to_string(&go, p);
1242 done_string(&data);
1244 uri = get_uri(go.source, 0);
1245 done_string(&go);
1246 if (uri) uri->form = 1;
1248 return uri;
1251 #undef BOUNDARY_LENGTH
1254 enum frame_event_status
1255 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1257 goto_current_link(ses, doc_view, do_reload);
1258 return FRAME_EVENT_OK;
1261 void
1262 submit_given_form(struct session *ses, struct document_view *doc_view,
1263 struct form *form, int do_reload)
1265 /* Added support for submitting forms in hidden
1266 * links in 1.285, commented code can safely be removed once we have made sure the new
1267 * code does the right thing. */
1268 #if 0
1270 struct document *document = doc_view->document;
1271 int link;
1273 for (link = 0; link < document->nlinks; link++) {
1274 struct form_control *fc = get_link_form_control(&document->links[link]);
1276 if (fc && fc->form == form) {
1277 doc_view->vs->current_link = link;
1278 submit_form(ses, doc_view, 0);
1279 return;
1282 #endif
1283 if (!list_empty(form->items)) {
1284 struct form_control *fc = (struct form_control *)form->items.next;
1285 struct uri *uri;
1286 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1288 if (!fc) return;
1289 uri = get_form_uri(ses, doc_view, fc);
1290 if (!uri) return;
1291 goto_uri_frame(ses, uri, form->target, mode);
1292 done_uri(uri);
1296 void
1297 auto_submit_form(struct session *ses)
1299 struct document *document = ses->doc_view->document;
1301 if (!list_empty(document->forms))
1302 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1306 /* menu_func_T */
1307 static void
1308 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1310 unsigned char *filename = filename_;
1311 struct form_state *fs = fs_;
1313 /* The menu code doesn't free the filename data */
1314 mem_free_set(&fs->value, filename);
1315 fs->state = strlen(filename);
1316 redraw_terminal(term);
1319 /* menu_func_T */
1320 static void
1321 file_form_menu(struct terminal *term, void *path_, void *fs_)
1323 unsigned char *path = path_;
1324 struct form_state *fs = fs_;
1326 /* FIXME: It doesn't work for ../../ */
1327 #if 0
1328 int valuelen = strlen(fs->value);
1329 int pathlen = strlen(path);
1330 int no_elevator = 0;
1332 /* Don't add elevators for subdirs menus */
1333 /* It is not perfect at all because fs->value is not updated for each
1334 * newly opened file menu. Maybe it should be dropped. */
1335 for (; valuelen < pathlen; valuelen++) {
1336 if (dir_sep(path[valuelen - 1])) {
1337 no_elevator = 1;
1338 break;
1341 #endif
1343 auto_complete_file(term, 0 /* no_elevator */, path,
1344 set_file_form_state,
1345 file_form_menu, fs);
1349 enum frame_event_status
1350 field_op(struct session *ses, struct document_view *doc_view,
1351 struct link *link, struct term_event *ev)
1353 struct form_control *fc;
1354 struct form_state *fs;
1355 enum edit_action action_id;
1356 unsigned char *text;
1357 int length;
1358 enum frame_event_status status = FRAME_EVENT_REFRESH;
1359 #ifdef CONFIG_UTF8
1360 int utf8 = ses->tab->term->utf8;
1361 #endif /* CONFIG_UTF8 */
1363 assert(ses && doc_view && link && ev);
1364 if_assert_failed return FRAME_EVENT_OK;
1366 fc = get_link_form_control(link);
1367 assertm(fc, "link has no form control");
1368 if_assert_failed return FRAME_EVENT_OK;
1370 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1371 || ses->insert_mode == INSERT_MODE_OFF)
1372 return FRAME_EVENT_IGNORED;
1374 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1376 fs = find_form_state(doc_view, fc);
1377 if (!fs || !fs->value) return FRAME_EVENT_OK;
1379 switch (action_id) {
1380 case ACT_EDIT_LEFT:
1381 #ifdef CONFIG_UTF8
1382 if (fc->type == FC_TEXTAREA) {
1383 status = textarea_op_left(fs, fc, utf8);
1384 break;
1386 if (utf8) {
1387 unsigned char *new_value;
1389 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1390 fs->state = new_value - fs->value;
1391 } else
1392 #endif /* CONFIG_UTF8 */
1393 fs->state = int_max(fs->state - 1, 0);
1394 break;
1395 case ACT_EDIT_RIGHT:
1396 #ifdef CONFIG_UTF8
1397 if (fc->type == FC_TEXTAREA) {
1398 status = textarea_op_right(fs, fc, utf8);
1399 break;
1401 if (utf8) {
1402 unsigned char *text = fs->value + fs->state;
1403 unsigned char *end = strchr(text, '\0');
1405 utf8_to_unicode(&text, end);
1406 fs->state = (int)(text - fs->value);
1407 } else
1408 #endif /* CONFIG_UTF8 */
1409 fs->state = int_min(fs->state + 1, strlen(fs->value));
1410 break;
1411 case ACT_EDIT_HOME:
1412 #ifdef CONFIG_UTF8
1413 if (fc->type == FC_TEXTAREA) {
1414 status = textarea_op_home(fs, fc, utf8);
1415 } else {
1416 fs->state = 0;
1418 #else
1419 if (fc->type == FC_TEXTAREA) {
1420 status = textarea_op_home(fs, fc);
1421 } else {
1422 fs->state = 0;
1425 #endif /* CONFIG_UTF8 */
1426 break;
1427 case ACT_EDIT_UP:
1428 if (fc->type != FC_TEXTAREA)
1429 status = FRAME_EVENT_IGNORED;
1430 else
1431 #ifdef CONFIG_UTF8
1432 status = textarea_op_up(fs, fc, utf8);
1433 #else
1434 status = textarea_op_up(fs, fc);
1435 #endif /* CONFIG_UTF8 */
1436 break;
1437 case ACT_EDIT_DOWN:
1438 if (fc->type != FC_TEXTAREA)
1439 status = FRAME_EVENT_IGNORED;
1440 else
1441 #ifdef CONFIG_UTF8
1442 status = textarea_op_down(fs, fc, utf8);
1443 #else
1444 status = textarea_op_down(fs, fc);
1445 #endif /* CONFIG_UTF8 */
1446 break;
1447 case ACT_EDIT_END:
1448 if (fc->type == FC_TEXTAREA) {
1449 #ifdef CONFIG_UTF8
1450 status = textarea_op_end(fs, fc, utf8);
1451 #else
1452 status = textarea_op_end(fs, fc);
1453 #endif /* CONFIG_UTF8 */
1454 } else {
1455 fs->state = strlen(fs->value);
1457 break;
1458 case ACT_EDIT_BEGINNING_OF_BUFFER:
1459 if (fc->type == FC_TEXTAREA) {
1460 #ifdef CONFIG_UTF8
1461 status = textarea_op_bob(fs, fc, utf8);
1462 fs->state_cell = 0;
1463 #else
1464 status = textarea_op_bob(fs, fc);
1465 #endif /* CONFIG_UTF8 */
1466 } else {
1467 fs->state = 0;
1469 break;
1470 case ACT_EDIT_END_OF_BUFFER:
1471 if (fc->type == FC_TEXTAREA) {
1472 #ifdef CONFIG_UTF8
1473 status = textarea_op_eob(fs, fc, utf8);
1474 #else
1475 status = textarea_op_eob(fs, fc);
1476 #endif /* CONFIG_UTF8 */
1477 } else {
1478 fs->state = strlen(fs->value);
1480 break;
1481 case ACT_EDIT_OPEN_EXTERNAL:
1482 if (form_field_is_readonly(fc))
1483 status = FRAME_EVENT_IGNORED;
1484 else if (fc->type == FC_TEXTAREA)
1485 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1486 break;
1487 case ACT_EDIT_COPY_CLIPBOARD:
1488 set_clipboard_text(fs->value);
1489 status = FRAME_EVENT_OK;
1490 break;
1491 case ACT_EDIT_CUT_CLIPBOARD:
1492 set_clipboard_text(fs->value);
1493 if (!form_field_is_readonly(fc))
1494 fs->value[0] = 0;
1495 fs->state = 0;
1496 #ifdef CONFIG_UTF8
1497 if (fc->type == FC_TEXTAREA)
1498 fs->state_cell = 0;
1499 #endif /* CONFIG_UTF8 */
1500 break;
1501 case ACT_EDIT_PASTE_CLIPBOARD:
1502 if (form_field_is_readonly(fc)) break;
1504 text = get_clipboard_text();
1505 if (!text) break;
1507 length = strlen(text);
1508 if (length <= fc->maxlength) {
1509 unsigned char *v = mem_realloc(fs->value, length + 1);
1511 if (v) {
1512 fs->value = v;
1513 memmove(v, text, length + 1);
1514 fs->state = strlen(fs->value);
1515 #ifdef CONFIG_UTF8
1516 if (utf8 && fc->type == FC_TEXTAREA)
1517 fs->state_cell = 0;
1518 #endif /* CONFIG_UTF8 */
1521 mem_free(text);
1522 break;
1523 case ACT_EDIT_ENTER:
1524 if (fc->type == FC_TEXTAREA) {
1525 #ifdef CONFIG_UTF8
1526 status = textarea_op_enter(fs, fc, utf8);
1527 #else
1528 status = textarea_op_enter(fs, fc);
1529 #endif /* CONFIG_UTF8 */
1530 break;
1533 /* Set status to ok if either it is not possible to
1534 * submit the form or the posting fails. */
1535 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1536 if ((has_form_submit(fc->form)
1537 && !get_opt_bool("document.browse.forms.auto_submit"))
1538 || goto_current_link(ses, doc_view, 0)) {
1539 if (ses->insert_mode == INSERT_MODE_ON)
1540 ses->insert_mode = INSERT_MODE_OFF;
1541 status = FRAME_EVENT_OK;
1543 break;
1544 case ACT_EDIT_BACKSPACE:
1545 if (form_field_is_readonly(fc)) {
1546 status = FRAME_EVENT_IGNORED;
1547 break;
1550 if (!fs->state) {
1551 status = FRAME_EVENT_OK;
1552 break;
1554 #ifdef CONFIG_UTF8
1555 if (utf8) {
1556 int old_state = fs->state;
1557 unsigned char *new_value;
1559 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1560 fs->state = new_value - fs->value;
1562 if (old_state != fs->state) {
1563 if (fc->type == FC_TEXTAREA)
1564 fs->state_cell = 0;
1565 length = strlen(fs->value + old_state) + 1;
1566 memmove(new_value, fs->value + old_state, length);
1568 } else
1569 #endif /* CONFIG_UTF8 */
1571 length = strlen(fs->value + fs->state) + 1;
1572 text = fs->value + fs->state;
1574 memmove(text - 1, text, length);
1575 fs->state--;
1577 break;
1578 case ACT_EDIT_DELETE:
1579 if (form_field_is_readonly(fc)) {
1580 status = FRAME_EVENT_IGNORED;
1581 break;
1584 length = strlen(fs->value);
1585 if (fs->state >= length) {
1586 status = FRAME_EVENT_OK;
1587 break;
1589 #ifdef CONFIG_UTF8
1590 if (utf8) {
1591 unsigned char *end = fs->value + length;
1592 unsigned char *text = fs->value + fs->state;
1593 unsigned char *old = text;
1595 utf8_to_unicode(&text, end);
1596 if (old != text) {
1597 memmove(old, text,
1598 (int)(end - text) + 1);
1600 break;
1602 #endif /* CONFIG_UTF8 */
1603 text = fs->value + fs->state;
1605 memmove(text, text + 1, length - fs->state);
1606 break;
1607 case ACT_EDIT_KILL_TO_BOL:
1608 if (form_field_is_readonly(fc)) {
1609 status = FRAME_EVENT_IGNORED;
1610 break;
1613 if (fs->state <= 0) {
1614 status = FRAME_EVENT_OK;
1615 break;
1618 text = memrchr(fs->value, ASCII_LF, fs->state);
1619 if (text) {
1620 /* Leave the new-line character if it does not
1621 * immediately precede the cursor. */
1622 if (text != &fs->value[fs->state - 1])
1623 text++;
1624 } else {
1625 text = fs->value;
1628 length = strlen(fs->value + fs->state) + 1;
1629 memmove(text, fs->value + fs->state, length);
1631 fs->state = (int) (text - fs->value);
1632 #ifdef CONFIG_UTF8
1633 if (utf8) {
1634 if (fc->type == FC_TEXTAREA)
1635 fs->state_cell = 0;
1637 #endif /* CONFIG_UTF8 */
1638 break;
1639 case ACT_EDIT_KILL_TO_EOL:
1640 if (form_field_is_readonly(fc)) {
1641 status = FRAME_EVENT_IGNORED;
1642 break;
1645 if (!fs->value[fs->state]) {
1646 status = FRAME_EVENT_OK;
1647 break;
1650 text = strchr(fs->value + fs->state, ASCII_LF);
1651 if (!text) {
1652 fs->value[fs->state] = '\0';
1653 break;
1656 if (fs->value[fs->state] == ASCII_LF)
1657 ++text;
1659 memmove(fs->value + fs->state, text, strlen(text) + 1);
1660 break;
1662 case ACT_EDIT_KILL_WORD_BACK:
1663 if (form_field_is_readonly(fc)) {
1664 status = FRAME_EVENT_IGNORED;
1665 break;
1668 if (fs->state <= 0) {
1669 status = FRAME_EVENT_OK;
1670 break;
1673 text = &fs->value[fs->state];
1674 while (text > fs->value && isspace(*(text - 1)))
1675 --text;
1676 while (text > fs->value && !isspace(*(text - 1)))
1677 --text;
1678 if (*text == ASCII_LF
1679 && text != &fs->value[fs->state - 1])
1680 text++;
1682 length = strlen(fs->value + fs->state) + 1;
1683 memmove(text, fs->value + fs->state, length);
1685 fs->state = (int) (text - fs->value);
1686 break;
1688 case ACT_EDIT_MOVE_BACKWARD_WORD:
1689 while (fs->state > 0
1690 && isspace(fs->value[fs->state - 1]))
1691 --fs->state;
1692 while (fs->state > 0
1693 && !isspace(fs->value[fs->state - 1]))
1694 --fs->state;
1695 break;
1697 case ACT_EDIT_MOVE_FORWARD_WORD:
1698 while (isspace(fs->value[fs->state]))
1699 ++fs->state;
1700 while (fs->value[fs->state]
1701 && !isspace(fs->value[fs->state]))
1702 ++fs->state;
1703 while (isspace(fs->value[fs->state]))
1704 ++fs->state;
1705 break;
1707 case ACT_EDIT_AUTO_COMPLETE:
1708 if (fc->type != FC_FILE
1709 || form_field_is_readonly(fc)) {
1710 status = FRAME_EVENT_IGNORED;
1711 break;
1714 file_form_menu(ses->tab->term, fs->value, fs);
1715 break;
1717 case ACT_EDIT_CANCEL:
1718 if (ses->insert_mode == INSERT_MODE_ON)
1719 ses->insert_mode = INSERT_MODE_OFF;
1720 else
1721 status = FRAME_EVENT_IGNORED;
1722 break;
1724 case ACT_EDIT_REDRAW:
1725 redraw_terminal_cls(ses->tab->term);
1726 status = FRAME_EVENT_OK;
1727 break;
1729 default:
1730 if (!check_kbd_textinput_key(ev)) {
1731 status = FRAME_EVENT_IGNORED;
1732 break;
1735 if (form_field_is_readonly(fc)
1736 #ifndef CONFIG_UTF8
1737 || strlen(fs->value) >= fc->maxlength
1738 || !insert_in_string(&fs->value, fs->state, "?", 1)
1739 #endif /* CONFIG_UTF8 */
1742 status = FRAME_EVENT_OK;
1743 break;
1746 #ifdef CONFIG_UTF8
1747 if (ses->tab->term->utf8) {
1748 /* fs->value is in UTF-8 regardless of
1749 * the charset of the terminal. */
1750 text = encode_utf8(get_kbd_key(ev));
1751 } else {
1752 /* fs->value is in the charset of the
1753 * terminal. */
1754 int cp = get_opt_codepage_tree(ses->tab->term->spec,
1755 "charset");
1757 text = u2cp_no_nbsp(get_kbd_key(ev), cp);
1759 length = strlen(text);
1761 if (strlen(fs->value) + length > fc->maxlength
1762 || !insert_in_string(&fs->value, fs->state, text, length)) {
1763 status = FRAME_EVENT_OK;
1764 break;
1767 fs->state += length;
1768 if (fc->type == FC_TEXTAREA)
1769 fs->state_cell = 0;
1770 #else
1771 fs->value[fs->state++] = get_kbd_key(ev);
1772 #endif /* CONFIG_UTF8 */
1773 break;
1776 return status;
1779 unsigned char *
1780 get_form_label(struct form_control *fc)
1782 assert(fc->form);
1783 switch (fc->type) {
1784 case FC_RESET:
1785 return N_("Reset form");
1786 case FC_BUTTON:
1787 return N_("Harmless button");
1788 case FC_HIDDEN:
1789 return NULL;
1790 case FC_SUBMIT:
1791 case FC_IMAGE:
1792 if (!fc->form->action) return NULL;
1794 if (fc->form->method == FORM_METHOD_GET)
1795 return N_("Submit form to");
1796 return N_("Post form to");
1797 case FC_RADIO:
1798 return N_("Radio button");
1799 case FC_CHECKBOX:
1800 return N_("Checkbox");
1801 case FC_SELECT:
1802 return N_("Select field");
1803 case FC_TEXT:
1804 return N_("Text field");
1805 case FC_TEXTAREA:
1806 return N_("Text area");
1807 case FC_FILE:
1808 return N_("File upload");
1809 case FC_PASSWORD:
1810 return N_("Password field");
1813 return NULL;
1816 static inline void
1817 add_form_attr_to_string(struct string *string, struct terminal *term,
1818 unsigned char *name, unsigned char *value)
1820 add_to_string(string, ", ");
1821 add_to_string(string, _(name, term));
1822 if (value) {
1823 add_char_to_string(string, ' ');
1824 add_to_string(string, value);
1828 unsigned char *
1829 get_form_info(struct session *ses, struct document_view *doc_view)
1831 struct terminal *term = ses->tab->term;
1832 struct link *link = get_current_link(doc_view);
1833 struct form_control *fc;
1834 unsigned char *label, *key;
1835 struct string str;
1837 assert(link);
1839 fc = get_link_form_control(link);
1840 label = get_form_label(fc);
1841 if (!label) return NULL;
1843 if (!init_string(&str)) return NULL;
1845 add_to_string(&str, _(label, term));
1847 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1848 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1851 switch (fc->type) {
1852 case FC_CHECKBOX:
1853 case FC_RADIO:
1855 struct form_state *fs = find_form_state(doc_view, fc);
1857 if (!fs->value || !fs->value[0])
1858 break;
1860 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1861 break;
1864 case FC_TEXT:
1865 case FC_PASSWORD:
1866 case FC_FILE:
1867 case FC_TEXTAREA:
1869 struct uri *uri;
1870 unsigned char *uristring;
1872 if (form_field_is_readonly(fc)) {
1873 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1876 /* Should we add info about entering insert mode or add info
1877 * about submitting the form? */
1878 if (ses->insert_mode == INSERT_MODE_OFF) {
1879 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1881 if (!key) break;
1883 if (form_field_is_readonly(fc))
1884 label = N_("press %s to navigate");
1885 else
1886 label = N_("press %s to edit");
1888 add_to_string(&str, " (");
1889 add_format_to_string(&str, _(label, term), key);
1890 add_char_to_string(&str, ')');
1891 mem_free(key);
1892 break;
1896 if (fc->type == FC_TEXTAREA)
1897 break;
1899 assert(fc->form);
1901 if (!fc->form->action
1902 || (has_form_submit(fc->form)
1903 && !get_opt_bool("document.browse.forms.auto_submit")))
1904 break;
1906 uri = get_uri(fc->form->action, 0);
1907 if (!uri) break;
1909 /* Add the uri with password and post info stripped */
1910 uristring = get_uri_string(uri, URI_PUBLIC);
1911 done_uri(uri);
1913 if (!uristring) break;
1915 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1916 if (!key) {
1917 mem_free(uristring);
1918 break;
1921 if (fc->form->method == FORM_METHOD_GET)
1922 label = N_("press %s to submit to %s");
1923 else
1924 label = N_("press %s to post to %s");
1926 add_to_string(&str, " (");
1927 add_format_to_string(&str, _(label, term), key, uristring);
1928 mem_free(uristring);
1929 mem_free(key);
1931 add_char_to_string(&str, ')');
1932 break;
1934 case FC_SUBMIT:
1935 case FC_IMAGE:
1936 add_char_to_string(&str, ' ');
1938 assert(fc->form);
1939 /* Add the uri with password and post info stripped */
1940 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
1941 break;
1943 case FC_HIDDEN:
1944 case FC_RESET:
1945 case FC_BUTTON:
1946 case FC_SELECT:
1947 break;
1950 if (link->accesskey
1951 && get_opt_bool("document.browse.accesskey.display")) {
1952 add_to_string(&str, " (");
1953 add_accesskey_to_string(&str, link->accesskey);
1954 add_char_to_string(&str, ')');
1957 return str.source;
1960 static void
1961 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
1963 struct session *ses = ses_;
1964 struct document_view *doc_view;
1965 int link_number = *(int *) link_number_;
1967 mem_free(link_number_);
1969 assert(term && ses);
1970 if_assert_failed return;
1972 doc_view = current_frame(ses);
1973 if (!doc_view) return;
1975 assert(doc_view->vs && doc_view->document);
1976 if_assert_failed return;
1978 jump_to_link_number(ses, doc_view, link_number);
1979 refresh_view(ses, doc_view, 0);
1982 void
1983 link_form_menu(struct session *ses)
1985 struct document_view *doc_view;
1986 struct link *link;
1987 struct menu_item *mi;
1988 struct form_control *fc;
1989 struct form *form;
1991 assert(ses);
1992 if_assert_failed return;
1994 doc_view = current_frame(ses);
1995 if (!doc_view) return;
1997 assert(doc_view->vs && doc_view->document);
1998 if_assert_failed return;
2000 link = get_current_link(doc_view);
2001 if (!link) return;
2003 assert(link_is_form(link));
2005 fc = get_link_form_control(link);
2006 if (!fc) return;
2008 form = fc->form;
2010 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2011 if (!mi) return;
2013 foreach (fc, form->items) {
2014 unsigned char *text;
2015 unsigned char *rtext;
2016 int link_number;
2017 struct string str;
2019 switch (fc->type) {
2020 case FC_HIDDEN:
2021 continue;
2023 case FC_SUBMIT:
2024 case FC_IMAGE:
2025 if (!form->action)
2026 text = N_("Useless button");
2027 else
2028 text = N_("Submit button");
2029 break;
2031 default:
2032 text = get_form_label(fc);
2035 link_number = get_form_control_link(doc_view->document, fc);
2036 if (link_number < 0
2037 || !init_string(&str))
2038 continue;
2040 assert(text);
2041 add_to_string(&str, _(text, ses->tab->term));
2043 rtext = fc->name;
2044 if (!rtext) rtext = fc->alt;
2046 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2047 link_form_menu_func, intdup(link_number),
2048 FREE_DATA);
2051 do_menu(ses->tab->term, mi, ses, 1);