UTF-8: Fix scrolling of input fields.
[elinks.git] / src / viewer / text / form.c
blobe2248d685631232de48cf2136eb15c9ff2b716be
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_UTF_8
167 if (fc->type == FC_TEXT)
168 fs->state_cell = utf8_ptr2cells(fs->value, NULL);
169 if (fc->type == FC_PASSWORD)
170 fs->state_cell = utf8_ptr2chars(fs->value, NULL);
171 if (fc->type == FC_TEXTAREA)
172 fs->state_cell = 0;
173 #endif /* CONFIG_UTF_8 */
174 fs->vpos = 0;
175 break;
176 case FC_FILE:
177 fs->value = stracpy("");
178 fs->state = 0;
179 #ifdef CONFIG_UTF_8
180 fs->state_cell = 0;
181 #endif /* CONFIG_UTF_8 */
182 fs->vpos = 0;
183 break;
184 case FC_SELECT:
185 fs->value = stracpy(fc->default_value);
186 fs->state = fc->default_state;
187 fixup_select_state(fc, fs);
188 break;
189 case FC_CHECKBOX:
190 case FC_RADIO:
191 fs->state = fc->default_state;
192 /* Fall-through */
193 case FC_SUBMIT:
194 case FC_IMAGE:
195 case FC_RESET:
196 case FC_BUTTON:
197 case FC_HIDDEN:
198 fs->value = stracpy(fc->default_value);
199 break;
204 struct form_state *
205 find_form_state(struct document_view *doc_view, struct form_control *fc)
207 struct view_state *vs;
208 struct form_state *fs;
209 int n;
211 assert(doc_view && doc_view->vs && fc);
212 if_assert_failed return NULL;
214 vs = doc_view->vs;
215 n = fc->g_ctrl_num;
217 if (n >= vs->form_info_len) {
218 int nn = n + 1;
220 fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
221 if (!fs) return NULL;
222 vs->form_info = fs;
223 vs->form_info_len = nn;
225 fs = &vs->form_info[n];
227 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
228 && fs->g_ctrl_num == fc->g_ctrl_num
229 && fs->position == fc->position
230 && fs->type == fc->type)
231 return fs;
233 mem_free_if(fs->value);
234 memset(fs, 0, sizeof(*fs));
235 fs->form_view = find_form_view(doc_view, fc->form);
236 fs->g_ctrl_num = fc->g_ctrl_num;
237 fs->position = fc->position;
238 fs->type = fc->type;
239 init_form_state(fc, fs);
241 return fs;
244 struct form_control *
245 find_form_control(struct document *document, struct form_state *fs)
247 struct form *form = find_form_by_form_view(document, fs->form_view);
248 struct form_control *fc;
250 foreach (fc, form->items) {
251 if (fs->g_ctrl_num == fc->g_ctrl_num
252 && fs->position == fc->position
253 && fs->type == fc->type)
254 return fc;
257 return NULL;
260 struct form_view *
261 find_form_view_in_vs(struct view_state *vs, int form_num)
263 struct form_view *fv;
265 assert(vs);
267 foreach (fv, vs->forms)
268 if (fv->form_num == form_num)
269 return fv;
271 fv = mem_calloc(1, sizeof(*fv));
272 fv->form_num = form_num;
273 add_to_list(vs->forms, fv);
274 return fv;
277 struct form_view *
278 find_form_view(struct document_view *doc_view, struct form *form)
280 return find_form_view_in_vs(doc_view->vs, form->form_num);
283 struct form *
284 find_form_by_form_view(struct document *document, struct form_view *fv)
286 struct form *form;
288 foreach (form, document->forms) {
289 if (form->form_num == fv->form_num)
290 return form;
292 return NULL;
297 get_current_state(struct session *ses)
299 struct document_view *doc_view;
300 struct link *link;
301 struct form_state *fs;
303 assert(ses);
304 if_assert_failed return -1;
305 doc_view = current_frame(ses);
307 assert(doc_view && doc_view->vs && doc_view->document);
308 if_assert_failed return -1;
310 link = get_current_link(doc_view);
311 if (!link || link->type != LINK_SELECT) return -1;
313 fs = find_form_state(doc_view, get_link_form_control(link));
314 if (fs) return fs->state;
315 return -1;
318 void
319 draw_form_entry(struct terminal *term, struct document_view *doc_view,
320 struct link *link)
322 struct form_state *fs;
323 struct form_control *fc;
324 struct view_state *vs;
325 struct box *box;
326 int dx, dy;
328 assert(term && doc_view && doc_view->document && doc_view->vs && link);
329 if_assert_failed return;
331 fc = get_link_form_control(link);
332 assertm(fc, "link %d has no form control", (int) (link - doc_view->document->links));
333 if_assert_failed return;
335 fs = find_form_state(doc_view, fc);
336 if (!fs) return;
338 box = &doc_view->box;
339 vs = doc_view->vs;
340 dx = box->x - vs->x;
341 dy = box->y - vs->y;
342 switch (fc->type) {
343 unsigned char *s;
344 #ifdef CONFIG_UTF_8
345 unsigned char *text, *end, *last_in_view;
346 int retried;
347 #endif /* CONFIG_UTF_8 */
348 int len;
349 int i, x, y;
351 case FC_TEXT:
352 case FC_PASSWORD:
353 case FC_FILE:
354 if (!link->npoints) break;
356 y = link->points[0].y + dy;
357 if (!row_is_in_box(box, y))
358 break;
360 x = link->points[0].x + dx;
361 #ifdef CONFIG_UTF_8
362 if (term->utf8) goto utf_8;
363 #endif /* CONFIG_UTF_8 */
364 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
365 len = strlen(fs->value) - fs->vpos;
367 for (i = 0; i < fc->size; i++, x++) {
368 unsigned char data;
370 if (!col_is_in_box(box, x)) continue;
372 if (fs->value && i >= -fs->vpos && i < len)
373 data = fc->type != FC_PASSWORD
374 ? fs->value[i + fs->vpos] : '*';
375 else
376 data = '_';
378 draw_char_data(term, x, y, data);
380 break;
381 #ifdef CONFIG_UTF_8
382 utf_8:
383 retried = 0;
385 retry_after_scroll:
386 text = fs->value;
387 if (!text) text = "";
388 len = strlen(text);
389 int_bounds(&fs->state, 0, len);
390 int_bounds(&fs->vpos, 0, fs->state);
391 end = text + len;
392 text += fs->vpos;
393 last_in_view = NULL;
395 for (i = 0; i < fc->size; ) {
396 unicode_val_T data;
397 int cells, cell;
398 unsigned char *maybe_in_view = text;
400 data = utf_8_to_unicode(&text, end);
401 if (data == UCS_NO_CHAR) /* end of string */
402 data = '_';
403 else if (fc->type == FC_PASSWORD)
404 data = '*';
406 cells = unicode_to_cell(data);
407 if (i + cells <= fc->size) {
408 last_in_view = maybe_in_view;
409 if (colspan_is_in_box(box, x + i, cells)) {
410 /* The character fits completely.
411 * Draw the character, and mark any
412 * further cells with UCS_NO_CHAR. */
413 draw_char_data(term, x + i, y, data);
414 for (cell = 1; cell < cells; cell++)
415 draw_char_data(term, x + i + cell,
416 y, UCS_NO_CHAR);
417 goto drew_char;
421 /* The character does not fit completely.
422 * Write spaces to the cells that do fit. */
423 for (cell = 0; cell < cells; cell++) {
424 if (col_is_in_box(box, x + i + cell)
425 && i + cell < fc->size)
426 draw_char_data(term,
427 x + i + cell,
428 y, ' ');
431 drew_char:
432 i += cells;
435 /* The int_bounds calls above ensured that the
436 * insertion point cannot be at the left side
437 * of the scrolled-visible part of the text.
438 * However it can still be at the right side.
439 * Check whether we need to change fs->vpos.
441 * This implementation attempts to follow
442 * these rules:
443 * - If the insertion point is at the end of
444 * the string, leave at least one empty cell
445 * so that there is a place for the cursor.
446 * - If a character follows the insertion
447 * point, make that character fully visible;
448 * note the character may be double-width.
449 * - If fc->size < 2, it is not possible to
450 * make a double-width character fully
451 * visible. In this case, it is OK if the
452 * output is ugly, but ELinks must not fall
453 * into an infinite loop or crash.
454 * - The length of the string should not affect
455 * how long this function takes. The width
456 * of the widget naturally will.
457 * - Optimize the case where fields are drawn
458 * several times without being modified.
460 * It follows that:
461 * - If the "for i" loop above hit UCS_NO_CHAR,
462 * then there is no need to scroll.
463 * - When the string ends with a double-width
464 * character that fits in only partially,
465 * then text==end, but the field may have
466 * to be scrolled. */
467 if (fs->value && last_in_view
468 && last_in_view < fs->value + fs->state) {
469 unsigned char *ptr = fs->value + fs->state;
470 int cells = fc->size;
471 enum utf8_step how = (fc->type == FC_PASSWORD)
472 ? utf8_step_characters
473 : utf8_step_cells_fewer;
475 /* The insertion point is at the right
476 * side of the scrolled-visible part
477 * of the text. Decide a new fs->vpos
478 * by counting cells backwards from
479 * @ptr. But first advance @ptr past
480 * the character that follows the
481 * insertion point, so that it will be
482 * fully displayed. If there is no
483 * such character, reserve one cell
484 * for the cursor anyway. */
485 if (utf_8_to_unicode(&ptr, end) == UCS_NO_CHAR)
486 --cells;
487 ptr = utf8_step_backward(ptr, fs->value,
488 cells, how, NULL);
490 if (fs->vpos != ptr - fs->value) {
491 fs->vpos = ptr - fs->value;
492 retried = 1;
493 goto retry_after_scroll;
496 break;
497 #endif /* CONFIG_UTF_8 */
498 case FC_TEXTAREA:
499 draw_textarea(term, fs, doc_view, link);
500 break;
501 case FC_CHECKBOX:
502 case FC_RADIO:
503 if (link->npoints < 2) break;
504 x = link->points[1].x + dx;
505 y = link->points[1].y + dy;
506 if (is_in_box(box, x, y))
507 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
508 break;
509 case FC_SELECT:
510 fixup_select_state(fc, fs);
511 if (fs->state < fc->nvalues)
512 s = fc->labels[fs->state];
513 else
514 /* XXX: when can this happen? --pasky */
515 s = "";
516 #ifdef CONFIG_UTF_8
517 if (term->utf8) goto utf_8_select;
518 #endif /* CONFIG_UTF_8 */
519 len = s ? strlen(s) : 0;
520 for (i = 0; i < link->npoints; i++) {
521 x = link->points[i].x + dx;
522 y = link->points[i].y + dy;
523 if (is_in_box(box, x, y))
524 draw_char_data(term, x, y, i < len ? s[i] : '_');
526 break;
527 #ifdef CONFIG_UTF_8
528 utf_8_select:
529 text = s;
530 end = strchr(s, '\0');
531 len = utf8_ptr2cells(text, end);
532 for (i = 0; i < link->npoints; i++) {
533 x = link->points[i].x + dx;
534 y = link->points[i].y + dy;
535 if (is_in_box(box, x, y)) {
536 unicode_val_T data;
537 if (i < len) {
538 int cell;
540 data = utf_8_to_unicode(&s, end);
541 cell = unicode_to_cell(data);
542 if (i + 1 < len && cell == 2) {
543 draw_char_data(term, x++, y, data);
545 data = UCS_NO_CHAR;
546 i++;
547 } else if (cell == 2) {
548 data = ' ';
550 } else
551 data = '_';
552 draw_char_data(term, x, y, data);
555 break;
556 #endif /* CONFIG_UTF_8 */
557 case FC_SUBMIT:
558 case FC_IMAGE:
559 case FC_RESET:
560 case FC_BUTTON:
561 case FC_HIDDEN:
562 break;
566 void
567 draw_forms(struct terminal *term, struct document_view *doc_view)
569 struct link *l1, *l2;
571 assert(term && doc_view);
572 if_assert_failed return;
574 l1 = get_first_link(doc_view);
575 l2 = get_last_link(doc_view);
577 if (!l1 || !l2) {
578 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
579 /* Return path :-). */
580 return;
582 do {
583 struct form_control *fc = get_link_form_control(l1);
585 if (!fc) continue;
586 #ifdef CONFIG_FORMHIST
587 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD) {
588 unsigned char *value;
590 assert(fc->form);
591 value = get_form_history_value(fc->form->action, fc->name);
593 if (value)
594 mem_free_set(&fc->default_value,
595 stracpy(value));
597 #endif /* CONFIG_FORMHIST */
598 draw_form_entry(term, doc_view, l1);
600 } while (l1++ < l2);
604 void
605 done_submitted_value_list(struct list_head *list)
607 struct submitted_value *sv, *svtmp;
609 assert(list);
610 if_assert_failed return;
612 foreach (sv, *list) {
613 svtmp = sv;
614 sv = sv->prev;
615 del_from_list(svtmp);
616 done_submitted_value(svtmp);
620 static void
621 add_submitted_value_to_list(struct form_control *fc,
622 struct form_state *fs,
623 struct list_head *list)
625 struct submitted_value *sub;
626 unsigned char *name;
627 enum form_type type;
628 int position;
630 assert(fc && fs && list);
632 name = fc->name;
633 position = fc->position;
634 type = fc->type;
636 switch (fc->type) {
637 case FC_TEXT:
638 case FC_PASSWORD:
639 case FC_FILE:
640 case FC_TEXTAREA:
641 sub = init_submitted_value(name, fs->value, type, fc, position);
642 if (sub) add_to_list(*list, sub);
643 break;
645 case FC_CHECKBOX:
646 case FC_RADIO:
647 if (!fs->state) break;
648 /* fall through */
650 case FC_SUBMIT:
651 case FC_HIDDEN:
652 case FC_RESET:
653 case FC_BUTTON:
654 sub = init_submitted_value(name, fs->value, type, fc,
655 position);
656 if (sub) add_to_list(*list, sub);
657 break;
659 case FC_SELECT:
660 if (!fc->nvalues) break;
662 fixup_select_state(fc, fs);
663 sub = init_submitted_value(name, fs->value, type, fc, position);
664 if (sub) add_to_list(*list, sub);
665 break;
667 case FC_IMAGE:
668 name = straconcat(fc->name, ".x", NULL);
669 if (!name) break;
670 sub = init_submitted_value(name, "0", type, fc, position);
671 mem_free(name);
672 if (sub) add_to_list(*list, sub);
674 name = straconcat(fc->name, ".y", NULL);
675 if (!name) break;
676 sub = init_submitted_value(name, "0", type, fc, position);
677 mem_free(name);
678 if (sub) add_to_list(*list, sub);
680 break;
684 static void
685 sort_submitted_values(struct list_head *list)
687 while (1) {
688 struct submitted_value *sub;
689 int changed = 0;
691 foreach (sub, *list) if (list_has_next(*list, sub))
692 if (sub->next->position < sub->position) {
693 struct submitted_value *next = sub->next;
695 del_from_list(sub);
696 add_at_pos(next, sub);
697 sub = next;
698 changed = 1;
701 foreachback (sub, *list) if (list_has_next(*list, sub))
702 if (sub->next->position < sub->position) {
703 struct submitted_value *next = sub->next;
705 del_from_list(sub);
706 add_at_pos(next, sub);
707 sub = next;
708 changed = 1;
711 if (!changed) break;
715 static void
716 get_successful_controls(struct document_view *doc_view,
717 struct form_control *fc, struct list_head *list)
719 struct form_control *fc2;
721 assert(doc_view && fc && fc->form && list);
722 if_assert_failed return;
724 foreach (fc2, fc->form->items) {
725 if (((fc2->type != FC_SUBMIT &&
726 fc2->type != FC_IMAGE &&
727 fc2->type != FC_RESET &&
728 fc2->type != FC_BUTTON) || fc2 == fc)
729 && fc2->name && fc2->name[0]) {
730 struct form_state *fs = find_form_state(doc_view, fc2);
732 if (!fs) continue;
734 add_submitted_value_to_list(fc2, fs, list);
738 sort_submitted_values(list);
741 static void
742 encode_controls(struct list_head *l, struct string *data,
743 int cp_from, int cp_to)
745 struct submitted_value *sv;
746 struct conv_table *convert_table = NULL;
747 int lst = 0;
749 assert(l && data);
750 if_assert_failed return;
752 foreach (sv, *l) {
753 unsigned char *p2 = NULL;
755 if (lst)
756 add_char_to_string(data, '&');
757 else
758 lst = 1;
760 encode_uri_string(data, sv->name, strlen(sv->name), 1);
761 add_char_to_string(data, '=');
763 /* Convert back to original encoding (see html_form_control()
764 * for the original recoding). */
765 if (sv->type == FC_TEXTAREA) {
766 unsigned char *p;
768 p = encode_textarea(sv);
769 if (p) {
770 if (!convert_table)
771 convert_table = get_translation_table(cp_from, cp_to);
773 p2 = convert_string(convert_table, p,
774 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
775 mem_free(p);
777 } else if (sv->type == FC_TEXT ||
778 sv->type == FC_PASSWORD) {
779 if (!convert_table)
780 convert_table = get_translation_table(cp_from, cp_to);
782 p2 = convert_string(convert_table, sv->value,
783 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
784 } else {
785 p2 = stracpy(sv->value);
788 if (p2) {
789 encode_uri_string(data, p2, strlen(p2), 1);
790 mem_free(p2);
797 #define BOUNDARY_LENGTH 32
798 #define realloc_bound_ptrs(bptrs, bptrs_size) \
799 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
801 struct boundary_info {
802 int count;
803 int *offsets;
804 unsigned char string[BOUNDARY_LENGTH];
807 static inline void
808 init_boundary(struct boundary_info *boundary)
810 memset(boundary, 0, sizeof(*boundary));
811 memset(boundary->string, '0', BOUNDARY_LENGTH);
814 /* Add boundary to string and save the offset */
815 static inline void
816 add_boundary(struct string *data, struct boundary_info *boundary)
818 add_to_string(data, "--");
820 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
821 boundary->offsets[boundary->count++] = data->length;
823 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
826 static inline unsigned char *
827 increment_boundary_counter(struct boundary_info *boundary)
829 int j;
831 /* This is just a decimal string incrementation */
832 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
833 if (boundary->string[j]++ < '9')
834 return boundary->string;
836 boundary->string[j] = '0';
839 INTERNAL("Form data boundary counter overflow");
841 return NULL;
844 static inline void
845 check_boundary(struct string *data, struct boundary_info *boundary)
847 unsigned char *bound = boundary->string;
848 int i;
850 /* Search between all boundaries. There is a starting and an ending
851 * boundary so only check the range of chars after the current offset
852 * and before the next offset. If some string in the form data matches
853 * the boundary string it is changed. */
854 for (i = 0; i < boundary->count - 1; i++) {
855 /* Start after the boundary string and also jump past the
856 * "\r\nContent-Disposition: form-data; name=\"" string added
857 * before any form data. */
858 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
860 /* End so that there is atleast BOUNDARY_LENGTH chars to
861 * compare. Subtract 2 char because there is no need to also
862 * compare the '--' prefix that is part of the boundary. */
863 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
864 unsigned char *pos = data->source + start_offset;
865 unsigned char *end = data->source + end_offset;
867 for (; pos <= end; pos++) {
868 if (memcmp(pos, bound, BOUNDARY_LENGTH))
869 continue;
871 /* If incrementing causes overflow bail out. There is
872 * no need to reset the boundary string with '0' since
873 * that is already done when incrementing. */
874 if (!increment_boundary_counter(boundary))
875 return;
877 /* Else start checking all boundaries using the new
878 * boundary string */
879 i = 0;
880 break;
884 /* Now update all the boundaries with the unique boundary string */
885 for (i = 0; i < boundary->count; i++)
886 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
889 /* FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
890 static void
891 encode_multipart(struct session *ses, struct list_head *l, struct string *data,
892 struct boundary_info *boundary, int cp_from, int cp_to)
894 struct conv_table *convert_table = NULL;
895 struct submitted_value *sv;
897 assert(ses && l && data && boundary);
898 if_assert_failed return;
900 init_boundary(boundary);
902 foreach (sv, *l) {
903 add_boundary(data, boundary);
904 add_crlf_to_string(data);
906 /* FIXME: name is not encoded.
907 * from RFC 1867:
908 * multipart/form-data contains a series of parts.
909 * Each part is expected to contain a content-disposition
910 * header where the value is "form-data" and a name attribute
911 * specifies the field name within the form,
912 * e.g., 'content-disposition: form-data; name="xxxxx"',
913 * where xxxxx is the field name corresponding to that field.
914 * Field names originally in non-ASCII character sets may be
915 * encoded using the method outlined in RFC 1522. */
916 add_to_string(data, "Content-Disposition: form-data; name=\"");
917 add_to_string(data, sv->name);
918 add_char_to_string(data, '"');
920 if (sv->type == FC_FILE) {
921 #define F_BUFLEN 1024
922 int fh;
923 unsigned char buffer[F_BUFLEN];
924 unsigned char *extension;
926 add_to_string(data, "; filename=\"");
927 add_to_string(data, get_filename_position(sv->value));
928 /* It sends bad data if the file name contains ", but
929 Netscape does the same */
930 /* FIXME: We should follow RFCs 1522, 1867,
931 * 2047 (updated by rfc 2231), to provide correct support
932 * for non-ASCII and special characters in values. --Zas */
933 add_char_to_string(data, '"');
935 /* Add a Content-Type header if the type is configured */
936 extension = strrchr(sv->value, '.');
937 if (extension) {
938 unsigned char *type = get_extension_content_type(extension);
940 if (type) {
941 add_crlf_to_string(data);
942 add_to_string(data, "Content-Type: ");
943 add_to_string(data, type);
944 mem_free(type);
948 add_crlf_to_string(data);
949 add_crlf_to_string(data);
951 if (*sv->value) {
952 unsigned char *filename;
954 if (get_cmd_opt_bool("anonymous")) {
955 errno = EPERM;
956 goto encode_error;
959 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
960 filename = expand_tilde(sv->value);
961 if (!filename) goto encode_error;
963 fh = open(filename, O_RDONLY);
964 mem_free(filename);
966 if (fh == -1) goto encode_error;
967 set_bin(fh);
968 while (1) {
969 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
971 if (rd) {
972 if (rd == -1) {
973 close(fh);
974 goto encode_error;
977 add_bytes_to_string(data, buffer, rd);
979 } else {
980 break;
983 close(fh);
985 #undef F_BUFLEN
986 } else {
987 add_crlf_to_string(data);
988 add_crlf_to_string(data);
990 /* Convert back to original encoding (see
991 * html_form_control() for the original recoding). */
992 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
993 sv->type == FC_TEXTAREA) {
994 unsigned char *p;
996 if (!convert_table)
997 convert_table = get_translation_table(cp_from,
998 cp_to);
1000 p = convert_string(convert_table, sv->value,
1001 strlen(sv->value), -1, CSM_FORM, NULL,
1002 NULL, NULL);
1003 if (p) {
1004 add_to_string(data, p);
1005 mem_free(p);
1007 } else {
1008 add_to_string(data, sv->value);
1012 add_crlf_to_string(data);
1015 /* End-boundary */
1016 add_boundary(data, boundary);
1017 add_to_string(data, "--\r\n");
1019 check_boundary(data, boundary);
1021 mem_free_if(boundary->offsets);
1022 return;
1024 encode_error:
1025 mem_free_if(boundary->offsets);
1026 done_string(data);
1028 /* XXX: This error message should move elsewhere. --Zas */
1029 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1030 N_("Error while posting form"), ALIGN_CENTER,
1031 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
1032 sv->value, strerror(errno)));
1035 static void
1036 encode_newlines(struct string *string, unsigned char *data)
1038 for (; *data; data++) {
1039 if (*data == '\n' || *data == '\r') {
1040 unsigned char buffer[3];
1042 /* Hex it. */
1043 buffer[0] = '%';
1044 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
1045 buffer[2] = hx(((int) *data) & 0xF);
1046 add_bytes_to_string(string, buffer, 3);
1047 } else {
1048 add_char_to_string(string, *data);
1053 static void
1054 encode_text_plain(struct list_head *l, struct string *data,
1055 int cp_from, int cp_to)
1057 struct submitted_value *sv;
1058 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
1060 assert(l && data);
1061 if_assert_failed return;
1063 foreach (sv, *l) {
1064 unsigned char *area51 = NULL;
1065 unsigned char *value = sv->value;
1067 add_to_string(data, sv->name);
1068 add_char_to_string(data, '=');
1070 switch (sv->type) {
1071 case FC_TEXTAREA:
1072 value = area51 = encode_textarea(sv);
1073 if (!area51) break;
1074 /* Fall through */
1075 case FC_TEXT:
1076 case FC_PASSWORD:
1077 /* Convert back to original encoding (see
1078 * html_form_control() for the original recoding). */
1079 value = convert_string(convert_table, value,
1080 strlen(value), -1, CSM_FORM,
1081 NULL, NULL, NULL);
1082 default:
1083 /* Falling right through to free that textarea stuff */
1084 mem_free_if(area51);
1086 /* Did the conversion fail? */
1087 if (!value) break;
1089 encode_newlines(data, value);
1091 /* Free if we did convert something */
1092 if (value != sv->value) mem_free(value);
1095 add_crlf_to_string(data);
1099 void
1100 do_reset_form(struct document_view *doc_view, struct form *form)
1102 struct form_control *fc;
1104 assert(doc_view && doc_view->document);
1105 if_assert_failed return;
1107 foreach (fc, form->items) {
1108 struct form_state *fs = find_form_state(doc_view, fc);
1110 if (fs) init_form_state(fc, fs);
1114 enum frame_event_status
1115 reset_form(struct session *ses, struct document_view *doc_view, int a)
1117 struct link *link = get_current_link(doc_view);
1119 if (!link) return FRAME_EVENT_OK;
1121 do_reset_form(doc_view, get_link_form_control(link)->form);
1122 draw_forms(ses->tab->term, doc_view);
1124 /* Could be the refresh return value and then ditch the draw_forms()
1125 * call. */
1126 return FRAME_EVENT_OK;
1129 struct uri *
1130 get_form_uri(struct session *ses, struct document_view *doc_view,
1131 struct form_control *fc)
1133 struct boundary_info boundary;
1134 INIT_LIST_HEAD(submit);
1135 struct string data;
1136 struct string go;
1137 int cp_from, cp_to;
1138 struct uri *uri;
1139 struct form *form;
1141 assert(ses && ses->tab && ses->tab->term);
1142 if_assert_failed return NULL;
1143 assert(doc_view && doc_view->document && fc && fc->form);
1144 if_assert_failed return NULL;
1146 form = fc->form;
1148 if (fc->type == FC_RESET) {
1149 do_reset_form(doc_view, form);
1150 return NULL;
1153 if (!form->action
1154 || !init_string(&data))
1155 return NULL;
1157 get_successful_controls(doc_view, fc, &submit);
1159 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset");
1160 cp_to = doc_view->document->cp;
1161 switch (form->method) {
1162 case FORM_METHOD_GET:
1163 case FORM_METHOD_POST:
1164 encode_controls(&submit, &data, cp_from, cp_to);
1165 break;
1167 case FORM_METHOD_POST_MP:
1168 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1169 break;
1171 case FORM_METHOD_POST_TEXT_PLAIN:
1172 encode_text_plain(&submit, &data, cp_from, cp_to);
1175 #ifdef CONFIG_FORMHIST
1176 /* XXX: We check data.source here because a NULL value can indicate
1177 * not only a memory allocation failure, but also an error reading
1178 * a file that is to be uploaded. TODO: Distinguish between
1179 * these two classes of errors (is it worth it?). -- Miciah */
1180 if (data.source
1181 && get_opt_bool("document.browse.forms.show_formhist"))
1182 memorize_form(ses, &submit, form);
1183 #endif
1185 done_submitted_value_list(&submit);
1187 if (!data.source
1188 || !init_string(&go)) {
1189 done_string(&data);
1190 return NULL;
1193 switch (form->method) {
1194 case FORM_METHOD_GET:
1196 unsigned char *pos = strchr(form->action, '#');
1198 if (pos) {
1199 add_bytes_to_string(&go, form->action, pos - form->action);
1200 } else {
1201 add_to_string(&go, form->action);
1204 if (strchr(go.source, '?'))
1205 add_char_to_string(&go, '&');
1206 else
1207 add_char_to_string(&go, '?');
1209 add_string_to_string(&go, &data);
1211 if (pos) add_to_string(&go, pos);
1212 break;
1214 case FORM_METHOD_POST:
1215 case FORM_METHOD_POST_MP:
1216 case FORM_METHOD_POST_TEXT_PLAIN:
1218 /* Note that we end content type here by a simple '\n',
1219 * replaced later by correct '\r\n' in http_send_header(). */
1220 int i;
1222 add_to_string(&go, form->action);
1223 add_char_to_string(&go, POST_CHAR);
1224 if (form->method == FORM_METHOD_POST) {
1225 add_to_string(&go, "application/x-www-form-urlencoded\n");
1227 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1228 /* Dunno about this one but we don't want the full
1229 * hextcat thingy. --jonas */
1230 add_to_string(&go, "text/plain\n");
1231 add_to_string(&go, data.source);
1232 break;
1234 } else {
1235 add_to_string(&go, "multipart/form-data; boundary=");
1236 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1237 add_char_to_string(&go, '\n');
1240 for (i = 0; i < data.length; i++) {
1241 unsigned char p[3];
1243 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1244 add_to_string(&go, p);
1249 done_string(&data);
1251 uri = get_uri(go.source, 0);
1252 done_string(&go);
1253 if (uri) uri->form = 1;
1255 return uri;
1258 #undef BOUNDARY_LENGTH
1261 enum frame_event_status
1262 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1264 goto_current_link(ses, doc_view, do_reload);
1265 return FRAME_EVENT_OK;
1268 void
1269 submit_given_form(struct session *ses, struct document_view *doc_view,
1270 struct form *form, int do_reload)
1272 /* Added support for submitting forms in hidden
1273 * links in 1.285, commented code can safely be removed once we have made sure the new
1274 * code does the right thing. */
1275 #if 0
1277 struct document *document = doc_view->document;
1278 int link;
1280 for (link = 0; link < document->nlinks; link++) {
1281 struct form_control *fc = get_link_form_control(&document->links[link]);
1283 if (fc && fc->form == form) {
1284 doc_view->vs->current_link = link;
1285 submit_form(ses, doc_view, 0);
1286 return;
1289 #endif
1290 if (!list_empty(form->items)) {
1291 struct form_control *fc = (struct form_control *)form->items.next;
1292 struct uri *uri;
1293 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1295 if (!fc) return;
1296 uri = get_form_uri(ses, doc_view, fc);
1297 if (!uri) return;
1298 goto_uri_frame(ses, uri, form->target, mode);
1299 done_uri(uri);
1303 void
1304 auto_submit_form(struct session *ses)
1306 struct document *document = ses->doc_view->document;
1308 if (!list_empty(document->forms))
1309 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1313 /* menu_func_T */
1314 static void
1315 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1317 unsigned char *filename = filename_;
1318 struct form_state *fs = fs_;
1320 /* The menu code doesn't free the filename data */
1321 mem_free_set(&fs->value, filename);
1322 fs->state = strlen(filename);
1323 redraw_terminal(term);
1326 /* menu_func_T */
1327 static void
1328 file_form_menu(struct terminal *term, void *path_, void *fs_)
1330 unsigned char *path = path_;
1331 struct form_state *fs = fs_;
1333 /* FIXME: It doesn't work for ../../ */
1334 #if 0
1335 int valuelen = strlen(fs->value);
1336 int pathlen = strlen(path);
1337 int no_elevator = 0;
1339 /* Don't add elevators for subdirs menus */
1340 /* It is not perfect at all because fs->value is not updated for each
1341 * newly opened file menu. Maybe it should be dropped. */
1342 for (; valuelen < pathlen; valuelen++) {
1343 if (dir_sep(path[valuelen - 1])) {
1344 no_elevator = 1;
1345 break;
1348 #endif
1350 auto_complete_file(term, 0 /* no_elevator */, path,
1351 set_file_form_state,
1352 file_form_menu, fs);
1356 enum frame_event_status
1357 field_op(struct session *ses, struct document_view *doc_view,
1358 struct link *link, struct term_event *ev)
1360 struct form_control *fc;
1361 struct form_state *fs;
1362 enum edit_action action_id;
1363 unsigned char *text;
1364 int length;
1365 enum frame_event_status status = FRAME_EVENT_REFRESH;
1366 #ifdef CONFIG_UTF_8
1367 int utf8 = ses->tab->term->utf8;
1368 #endif /* CONFIG_UTF_8 */
1370 assert(ses && doc_view && link && ev);
1371 if_assert_failed return FRAME_EVENT_OK;
1373 fc = get_link_form_control(link);
1374 assertm(fc, "link has no form control");
1375 if_assert_failed return FRAME_EVENT_OK;
1377 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1378 || ses->insert_mode == INSERT_MODE_OFF)
1379 return FRAME_EVENT_IGNORED;
1381 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1383 fs = find_form_state(doc_view, fc);
1384 if (!fs || !fs->value) return FRAME_EVENT_OK;
1386 switch (action_id) {
1387 case ACT_EDIT_LEFT:
1388 #ifdef CONFIG_UTF_8
1389 if (fc->type == FC_TEXTAREA) {
1390 status = textarea_op_left(fs, fc, utf8);
1391 break;
1393 if (utf8) {
1394 int old_state = fs->state;
1395 unsigned char *new_value;
1396 int cells;
1398 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1399 fs->state = new_value - fs->value;
1401 if (old_state != fs->state) {
1402 if (fc->type == FC_PASSWORD)
1403 cells = 1;
1404 else
1405 cells = utf8_char2cells(new_value, NULL);
1406 fs->state_cell = int_max(fs->state_cell - cells, 0);
1408 } else
1409 #endif /* CONFIG_UTF_8 */
1410 fs->state = int_max(fs->state - 1, 0);
1411 break;
1412 case ACT_EDIT_RIGHT:
1413 #ifdef CONFIG_UTF_8
1414 if (fc->type == FC_TEXTAREA) {
1415 status = textarea_op_right(fs, fc, utf8);
1416 break;
1418 if (utf8) {
1419 unsigned char *text = fs->value + fs->state;
1420 unsigned char *end = strchr(text, '\0');
1421 int old_state = fs->state;
1422 unicode_val_T data = utf_8_to_unicode(&text, end);
1424 fs->state = (int)(text - fs->value);
1425 if (old_state != fs->state) {
1426 if (fc->type == FC_PASSWORD)
1427 fs->state_cell = int_min(fs->state_cell + 1,
1428 utf8_ptr2cells(fs->value, NULL));
1429 else
1430 fs->state_cell += unicode_to_cell(data);
1432 } else
1433 #endif /* CONFIG_UTF_8 */
1434 fs->state = int_min(fs->state + 1, strlen(fs->value));
1435 break;
1436 case ACT_EDIT_HOME:
1437 #ifdef CONFIG_UTF_8
1438 if (fc->type == FC_TEXTAREA) {
1439 status = textarea_op_home(fs, fc, utf8);
1440 } else {
1441 fs->state = 0;
1442 fs->state_cell = 0;
1444 #else
1445 if (fc->type == FC_TEXTAREA) {
1446 status = textarea_op_home(fs, fc);
1447 } else {
1448 fs->state = 0;
1451 #endif /* CONFIG_UTF_8 */
1452 break;
1453 case ACT_EDIT_UP:
1454 if (fc->type != FC_TEXTAREA)
1455 status = FRAME_EVENT_IGNORED;
1456 else
1457 #ifdef CONFIG_UTF_8
1458 status = textarea_op_up(fs, fc, utf8);
1459 #else
1460 status = textarea_op_up(fs, fc);
1461 #endif /* CONFIG_UTF_8 */
1462 break;
1463 case ACT_EDIT_DOWN:
1464 if (fc->type != FC_TEXTAREA)
1465 status = FRAME_EVENT_IGNORED;
1466 else
1467 #ifdef CONFIG_UTF_8
1468 status = textarea_op_down(fs, fc, utf8);
1469 #else
1470 status = textarea_op_down(fs, fc);
1471 #endif /* CONFIG_UTF_8 */
1472 break;
1473 case ACT_EDIT_END:
1474 if (fc->type == FC_TEXTAREA) {
1475 #ifdef CONFIG_UTF_8
1476 status = textarea_op_end(fs, fc, utf8);
1477 #else
1478 status = textarea_op_end(fs, fc);
1479 #endif /* CONFIG_UTF_8 */
1480 } else {
1481 fs->state = strlen(fs->value);
1482 #ifdef CONFIG_UTF_8
1483 if (utf8 && fc->type != FC_PASSWORD)
1484 fs->state_cell = utf8_ptr2cells(fs->value,
1485 fs->value + fs->state);
1486 else if(utf8)
1487 fs->state_cell = utf8_ptr2chars(fs->value,
1488 fs->value + fs->state);
1489 #endif /* CONFIG_UTF_8 */
1491 break;
1492 case ACT_EDIT_BEGINNING_OF_BUFFER:
1493 if (fc->type == FC_TEXTAREA) {
1494 #ifdef CONFIG_UTF_8
1495 status = textarea_op_bob(fs, fc, utf8);
1496 #else
1497 status = textarea_op_bob(fs, fc);
1498 #endif /* CONFIG_UTF_8 */
1499 } else {
1500 fs->state = 0;
1502 #ifdef CONFIG_UTF_8
1503 fs->state_cell = 0;
1504 #endif /* CONFIG_UTF_8 */
1505 break;
1506 case ACT_EDIT_END_OF_BUFFER:
1507 if (fc->type == FC_TEXTAREA) {
1508 #ifdef CONFIG_UTF_8
1509 status = textarea_op_eob(fs, fc, utf8);
1510 #else
1511 status = textarea_op_eob(fs, fc);
1512 #endif /* CONFIG_UTF_8 */
1513 } else {
1514 fs->state = strlen(fs->value);
1515 #ifdef CONFIG_UTF_8
1516 if (utf8 && fc->type != FC_PASSWORD)
1517 fs->state_cell = utf8_ptr2cells(fs->value,
1518 fs->value + fs->state);
1519 else if(utf8)
1520 fs->state_cell = utf8_ptr2chars(fs->value,
1521 fs->value + fs->state);
1523 #endif /* CONFIG_UTF_8 */
1525 break;
1526 case ACT_EDIT_OPEN_EXTERNAL:
1527 if (form_field_is_readonly(fc))
1528 status = FRAME_EVENT_IGNORED;
1529 else if (fc->type == FC_TEXTAREA)
1530 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1531 break;
1532 case ACT_EDIT_COPY_CLIPBOARD:
1533 set_clipboard_text(fs->value);
1534 status = FRAME_EVENT_OK;
1535 break;
1536 case ACT_EDIT_CUT_CLIPBOARD:
1537 set_clipboard_text(fs->value);
1538 if (!form_field_is_readonly(fc))
1539 fs->value[0] = 0;
1540 fs->state = 0;
1541 #ifdef CONFIG_UTF_8
1542 fs->state_cell = 0;
1543 #endif /* CONFIG_UTF_8 */
1544 break;
1545 case ACT_EDIT_PASTE_CLIPBOARD:
1546 if (form_field_is_readonly(fc)) break;
1548 text = get_clipboard_text();
1549 if (!text) break;
1551 length = strlen(text);
1552 if (length <= fc->maxlength) {
1553 unsigned char *v = mem_realloc(fs->value, length + 1);
1555 if (v) {
1556 fs->value = v;
1557 memmove(v, text, length + 1);
1558 fs->state = strlen(fs->value);
1559 #ifdef CONFIG_UTF_8
1560 if(utf8 && fc->type == FC_PASSWORD)
1561 fs->state_cell = utf8_ptr2chars(fs->value,
1562 fs->value + fs->state);
1563 else if (utf8 && fc->type == FC_TEXTAREA)
1564 fs->state_cell = 0;
1565 else if (utf8)
1566 fs->state_cell = utf8_ptr2cells(fs->value,
1567 fs->value + fs->state);
1568 #endif /* CONFIG_UTF_8 */
1571 mem_free(text);
1572 break;
1573 case ACT_EDIT_ENTER:
1574 if (fc->type == FC_TEXTAREA) {
1575 #ifdef CONFIG_UTF_8
1576 status = textarea_op_enter(fs, fc, utf8);
1577 #else
1578 status = textarea_op_enter(fs, fc);
1579 #endif /* CONFIG_UTF_8 */
1580 break;
1583 /* Set status to ok if either it is not possible to
1584 * submit the form or the posting fails. */
1585 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1586 if ((has_form_submit(fc->form)
1587 && !get_opt_bool("document.browse.forms.auto_submit"))
1588 || goto_current_link(ses, doc_view, 0)) {
1589 if (ses->insert_mode == INSERT_MODE_ON)
1590 ses->insert_mode = INSERT_MODE_OFF;
1591 status = FRAME_EVENT_OK;
1593 break;
1594 case ACT_EDIT_BACKSPACE:
1595 if (form_field_is_readonly(fc)) {
1596 status = FRAME_EVENT_IGNORED;
1597 break;
1600 if (!fs->state) {
1601 status = FRAME_EVENT_OK;
1602 break;
1604 #ifdef CONFIG_UTF_8
1605 if (utf8) {
1606 int old_state = fs->state;
1607 unsigned char *new_value;
1609 new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
1610 fs->state = new_value - fs->value;
1612 if (old_state != fs->state) {
1613 if (fc->type == FC_TEXTAREA)
1614 fs->state_cell = 0;
1615 else if (fc->type == FC_PASSWORD)
1616 fs->state_cell = int_max(fs->state_cell - 1, 0);
1617 else
1618 fs->state_cell -= utf8_char2cells(new_value, NULL);
1619 length = strlen(fs->value + old_state) + 1;
1620 memmove(new_value, fs->value + old_state, length);
1622 } else
1623 #endif /* CONFIG_UTF_8 */
1625 length = strlen(fs->value + fs->state) + 1;
1626 text = fs->value + fs->state;
1628 memmove(text - 1, text, length);
1629 fs->state--;
1631 break;
1632 case ACT_EDIT_DELETE:
1633 if (form_field_is_readonly(fc)) {
1634 status = FRAME_EVENT_IGNORED;
1635 break;
1638 length = strlen(fs->value);
1639 if (fs->state >= length) {
1640 status = FRAME_EVENT_OK;
1641 break;
1643 #ifdef CONFIG_UTF_8
1644 if (utf8) {
1645 unsigned char *end = fs->value + length;
1646 unsigned char *text = fs->value + fs->state;
1647 unsigned char *old = text;
1649 utf_8_to_unicode(&text, end);
1650 if (old != text) {
1651 memmove(old, text,
1652 (int)(end - text) + 1);
1654 break;
1656 #endif /* CONFIG_UTF_8 */
1657 text = fs->value + fs->state;
1659 memmove(text, text + 1, length - fs->state);
1660 break;
1661 case ACT_EDIT_KILL_TO_BOL:
1662 if (form_field_is_readonly(fc)) {
1663 status = FRAME_EVENT_IGNORED;
1664 break;
1667 if (fs->state <= 0) {
1668 status = FRAME_EVENT_OK;
1669 break;
1672 text = memrchr(fs->value, ASCII_LF, fs->state);
1673 if (text) {
1674 /* Leave the new-line character if it does not
1675 * immediately precede the cursor. */
1676 if (text != &fs->value[fs->state - 1])
1677 text++;
1678 } else {
1679 text = fs->value;
1682 length = strlen(fs->value + fs->state) + 1;
1683 memmove(text, fs->value + fs->state, length);
1685 fs->state = (int) (text - fs->value);
1686 #ifdef CONFIG_UTF_8
1687 if (utf8) {
1688 if(fc->type == FC_PASSWORD)
1689 fs->state_cell = utf8_ptr2cells(fs->value,
1690 fs->value + fs->state);
1691 else if (fc->type == FC_TEXTAREA)
1692 fs->state_cell = 0;
1693 else
1694 fs->state_cell = utf8_ptr2cells(fs->value,
1695 fs->value + fs->state);
1697 #endif /* CONFIG_UTF_8 */
1698 break;
1699 case ACT_EDIT_KILL_TO_EOL:
1700 if (form_field_is_readonly(fc)) {
1701 status = FRAME_EVENT_IGNORED;
1702 break;
1705 if (!fs->value[fs->state]) {
1706 status = FRAME_EVENT_OK;
1707 break;
1710 text = strchr(fs->value + fs->state, ASCII_LF);
1711 if (!text) {
1712 fs->value[fs->state] = '\0';
1713 break;
1716 if (fs->value[fs->state] == ASCII_LF)
1717 ++text;
1719 memmove(fs->value + fs->state, text, strlen(text) + 1);
1720 break;
1722 case ACT_EDIT_KILL_WORD_BACK:
1723 if (form_field_is_readonly(fc)) {
1724 status = FRAME_EVENT_IGNORED;
1725 break;
1728 if (fs->state <= 0) {
1729 status = FRAME_EVENT_OK;
1730 break;
1733 text = &fs->value[fs->state];
1734 while (text > fs->value && isspace(*(text - 1)))
1735 --text;
1736 while (text > fs->value && !isspace(*(text - 1)))
1737 --text;
1738 if (*text == ASCII_LF
1739 && text != &fs->value[fs->state - 1])
1740 text++;
1742 length = strlen(fs->value + fs->state) + 1;
1743 memmove(text, fs->value + fs->state, length);
1745 fs->state = (int) (text - fs->value);
1746 break;
1748 case ACT_EDIT_MOVE_BACKWARD_WORD:
1749 while (fs->state > 0
1750 && isspace(fs->value[fs->state - 1]))
1751 --fs->state;
1752 while (fs->state > 0
1753 && !isspace(fs->value[fs->state - 1]))
1754 --fs->state;
1755 break;
1757 case ACT_EDIT_MOVE_FORWARD_WORD:
1758 while (isspace(fs->value[fs->state]))
1759 ++fs->state;
1760 while (fs->value[fs->state]
1761 && !isspace(fs->value[fs->state]))
1762 ++fs->state;
1763 while (isspace(fs->value[fs->state]))
1764 ++fs->state;
1765 break;
1767 case ACT_EDIT_AUTO_COMPLETE:
1768 if (fc->type != FC_FILE
1769 || form_field_is_readonly(fc)) {
1770 status = FRAME_EVENT_IGNORED;
1771 break;
1774 file_form_menu(ses->tab->term, fs->value, fs);
1775 break;
1777 case ACT_EDIT_CANCEL:
1778 if (ses->insert_mode == INSERT_MODE_ON)
1779 ses->insert_mode = INSERT_MODE_OFF;
1780 else
1781 status = FRAME_EVENT_IGNORED;
1782 break;
1784 case ACT_EDIT_REDRAW:
1785 redraw_terminal_cls(ses->tab->term);
1786 status = FRAME_EVENT_OK;
1787 break;
1789 default:
1790 if (!check_kbd_textinput_key(ev)) {
1791 status = FRAME_EVENT_IGNORED;
1792 break;
1795 if (form_field_is_readonly(fc)
1796 #ifndef CONFIG_UTF_8
1797 || strlen(fs->value) >= fc->maxlength
1798 || !insert_in_string(&fs->value, fs->state, "?", 1)
1799 #endif /* CONFIG_UTF_8 */
1802 status = FRAME_EVENT_OK;
1803 break;
1806 #ifdef CONFIG_UTF_8
1808 /* The charset of the terminal; we assume
1809 * fs->value is in this charset.
1810 * (Is that OK?) */
1811 int cp = get_opt_codepage_tree(ses->tab->term->spec,
1812 "charset");
1814 text = u2cp_no_nbsp(get_kbd_key(ev), cp);
1815 length = strlen(text);
1817 if (strlen(fs->value) + length > fc->maxlength
1818 || !insert_in_string(&fs->value, fs->state, text, length)) {
1819 status = FRAME_EVENT_OK;
1820 break;
1823 fs->state += length;
1824 if (fc->type == FC_PASSWORD)
1825 fs->state_cell += (is_cp_utf8(cp) ? 1 : length);
1826 else if (fc->type == FC_TEXTAREA)
1827 fs->state_cell = 0;
1828 else
1829 fs->state_cell += (is_cp_utf8(cp) ? unicode_to_cell(get_kbd_key(ev)) : length);
1831 #else
1832 fs->value[fs->state++] = get_kbd_key(ev);
1833 #endif /* CONFIG_UTF_8 */
1834 break;
1837 return status;
1840 unsigned char *
1841 get_form_label(struct form_control *fc)
1843 assert(fc->form);
1844 switch (fc->type) {
1845 case FC_RESET:
1846 return N_("Reset form");
1847 case FC_BUTTON:
1848 return N_("Harmless button");
1849 case FC_HIDDEN:
1850 return NULL;
1851 case FC_SUBMIT:
1852 case FC_IMAGE:
1853 if (!fc->form->action) return NULL;
1855 if (fc->form->method == FORM_METHOD_GET)
1856 return N_("Submit form to");
1857 return N_("Post form to");
1858 case FC_RADIO:
1859 return N_("Radio button");
1860 case FC_CHECKBOX:
1861 return N_("Checkbox");
1862 case FC_SELECT:
1863 return N_("Select field");
1864 case FC_TEXT:
1865 return N_("Text field");
1866 case FC_TEXTAREA:
1867 return N_("Text area");
1868 case FC_FILE:
1869 return N_("File upload");
1870 case FC_PASSWORD:
1871 return N_("Password field");
1874 return NULL;
1877 static inline void
1878 add_form_attr_to_string(struct string *string, struct terminal *term,
1879 unsigned char *name, unsigned char *value)
1881 add_to_string(string, ", ");
1882 add_to_string(string, _(name, term));
1883 if (value) {
1884 add_char_to_string(string, ' ');
1885 add_to_string(string, value);
1889 unsigned char *
1890 get_form_info(struct session *ses, struct document_view *doc_view)
1892 struct terminal *term = ses->tab->term;
1893 struct link *link = get_current_link(doc_view);
1894 struct form_control *fc;
1895 unsigned char *label, *key;
1896 struct string str;
1898 assert(link);
1900 fc = get_link_form_control(link);
1901 label = get_form_label(fc);
1902 if (!label) return NULL;
1904 if (!init_string(&str)) return NULL;
1906 add_to_string(&str, _(label, term));
1908 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1909 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1912 switch (fc->type) {
1913 case FC_CHECKBOX:
1914 case FC_RADIO:
1916 struct form_state *fs = find_form_state(doc_view, fc);
1918 if (!fs->value || !fs->value[0])
1919 break;
1921 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1922 break;
1925 case FC_TEXT:
1926 case FC_PASSWORD:
1927 case FC_FILE:
1928 case FC_TEXTAREA:
1930 struct uri *uri;
1931 unsigned char *uristring;
1933 if (form_field_is_readonly(fc)) {
1934 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1937 /* Should we add info about entering insert mode or add info
1938 * about submitting the form? */
1939 if (ses->insert_mode == INSERT_MODE_OFF) {
1940 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1942 if (!key) break;
1944 if (form_field_is_readonly(fc))
1945 label = N_("press %s to navigate");
1946 else
1947 label = N_("press %s to edit");
1949 add_to_string(&str, " (");
1950 add_format_to_string(&str, _(label, term), key);
1951 add_char_to_string(&str, ')');
1952 mem_free(key);
1953 break;
1957 if (fc->type == FC_TEXTAREA)
1958 break;
1960 assert(fc->form);
1962 if (!fc->form->action
1963 || (has_form_submit(fc->form)
1964 && !get_opt_bool("document.browse.forms.auto_submit")))
1965 break;
1967 uri = get_uri(fc->form->action, 0);
1968 if (!uri) break;
1970 /* Add the uri with password and post info stripped */
1971 uristring = get_uri_string(uri, URI_PUBLIC);
1972 done_uri(uri);
1974 if (!uristring) break;
1976 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1977 if (!key) {
1978 mem_free(uristring);
1979 break;
1982 if (fc->form->method == FORM_METHOD_GET)
1983 label = N_("press %s to submit to %s");
1984 else
1985 label = N_("press %s to post to %s");
1987 add_to_string(&str, " (");
1988 add_format_to_string(&str, _(label, term), key, uristring);
1989 mem_free(uristring);
1990 mem_free(key);
1992 add_char_to_string(&str, ')');
1993 break;
1995 case FC_SUBMIT:
1996 case FC_IMAGE:
1997 add_char_to_string(&str, ' ');
1999 assert(fc->form);
2000 /* Add the uri with password and post info stripped */
2001 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
2002 break;
2004 case FC_HIDDEN:
2005 case FC_RESET:
2006 case FC_BUTTON:
2007 case FC_SELECT:
2008 break;
2011 if (link->accesskey
2012 && get_opt_bool("document.browse.accesskey.display")) {
2013 add_to_string(&str, " (");
2014 add_accesskey_to_string(&str, link->accesskey);
2015 add_char_to_string(&str, ')');
2018 return str.source;
2021 static void
2022 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
2024 struct session *ses = ses_;
2025 struct document_view *doc_view;
2026 int link_number = *(int *) link_number_;
2028 mem_free(link_number_);
2030 assert(term && ses);
2031 if_assert_failed return;
2033 doc_view = current_frame(ses);
2034 if (!doc_view) return;
2036 assert(doc_view->vs && doc_view->document);
2037 if_assert_failed return;
2039 jump_to_link_number(ses, doc_view, link_number);
2040 refresh_view(ses, doc_view, 0);
2043 void
2044 link_form_menu(struct session *ses)
2046 struct document_view *doc_view;
2047 struct link *link;
2048 struct menu_item *mi;
2049 struct form_control *fc;
2050 struct form *form;
2052 assert(ses);
2053 if_assert_failed return;
2055 doc_view = current_frame(ses);
2056 if (!doc_view) return;
2058 assert(doc_view->vs && doc_view->document);
2059 if_assert_failed return;
2061 link = get_current_link(doc_view);
2062 if (!link) return;
2064 assert(link_is_form(link));
2066 fc = get_link_form_control(link);
2067 if (!fc) return;
2069 form = fc->form;
2071 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
2072 if (!mi) return;
2074 foreach (fc, form->items) {
2075 unsigned char *text;
2076 unsigned char *rtext;
2077 int link_number;
2078 struct string str;
2080 switch (fc->type) {
2081 case FC_HIDDEN:
2082 continue;
2084 case FC_SUBMIT:
2085 case FC_IMAGE:
2086 if (!form->action)
2087 text = N_("Useless button");
2088 else
2089 text = N_("Submit button");
2090 break;
2092 default:
2093 text = get_form_label(fc);
2096 link_number = get_form_control_link(doc_view->document, fc);
2097 if (link_number < 0
2098 || !init_string(&str))
2099 continue;
2101 assert(text);
2102 add_to_string(&str, _(text, ses->tab->term));
2104 rtext = fc->name;
2105 if (!rtext) rtext = fc->alt;
2107 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
2108 link_form_menu_func, intdup(link_number),
2109 FREE_DATA);
2112 do_menu(ses->tab->term, mi, ses, 1);