Correct computing of cursor position in UTF-8 textarea.
[elinks.git] / src / viewer / text / form.c
blob82228c9b109b4955cbb3b4998d02868d79d558e5
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 #ifdef CONFIG_UTF_8
162 unsigned char *text;
163 #endif /* CONFIG_UTF_8 */
165 case FC_TEXT:
166 case FC_PASSWORD:
167 case FC_TEXTAREA:
168 fs->value = stracpy(fc->default_value);
169 fs->state = strlen(fc->default_value);
170 #ifdef CONFIG_UTF_8
171 text = fs->value;
172 fs->utf8_pos = strlen_utf8(&text);
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->utf8_pos = 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;
346 #endif /* CONFIG_UTF_8 */
347 int len;
348 int i, x, y;
350 case FC_TEXT:
351 case FC_PASSWORD:
352 case FC_FILE:
353 #ifdef CONFIG_UTF_8
354 if (term->utf8) goto utf_8;
355 #endif /* CONFIG_UTF_8 */
356 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
357 if (!link->npoints) break;
359 y = link->points[0].y + dy;
360 if (!row_is_in_box(box, y))
361 break;
363 len = strlen(fs->value) - fs->vpos;
364 x = link->points[0].x + dx;
366 for (i = 0; i < fc->size; i++, x++) {
367 unsigned char data;
369 if (!col_is_in_box(box, x)) continue;
371 if (fs->value && i >= -fs->vpos && i < len)
372 data = fc->type != FC_PASSWORD
373 ? fs->value[i + fs->vpos] : '*';
374 else
375 data = '_';
377 draw_char_data(term, x, y, data);
379 break;
380 #ifdef CONFIG_UTF_8
381 utf_8:
382 text = fs->value;
383 end = strchr(text, '\0');
384 int_bounds(&fs->vpos, fs->utf8_pos - fc->size + 1, fs->utf8_pos);
385 if (!link->npoints) break;
387 y = link->points[0].y + dy;
388 if (!row_is_in_box(box, y))
389 break;
390 for (i = 0; i < fs->vpos; i++) {
391 utf_8_to_unicode(&text, end);
393 s = text;
394 len = strlen_utf8(&s);
395 x = link->points[0].x + dx;
397 for (i = 0; i < fc->size; i++, x++) {
398 uint16_t data;
400 if (!col_is_in_box(box, x)) continue;
402 if (fs->value && i >= -fs->vpos && i < len)
403 data = fc->type != FC_PASSWORD
404 ? utf_8_to_unicode(&text, end) : '*';
405 else
406 data = '_';
408 draw_char_data(term, x, y, data);
410 break;
411 #endif /* CONFIG_UTF_8 */
412 case FC_TEXTAREA:
413 draw_textarea(term, fs, doc_view, link);
414 break;
415 case FC_CHECKBOX:
416 case FC_RADIO:
417 if (link->npoints < 2) break;
418 x = link->points[1].x + dx;
419 y = link->points[1].y + dy;
420 if (is_in_box(box, x, y))
421 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
422 break;
423 case FC_SELECT:
424 fixup_select_state(fc, fs);
425 if (fs->state < fc->nvalues)
426 s = fc->labels[fs->state];
427 else
428 /* XXX: when can this happen? --pasky */
429 s = "";
430 #ifdef CONFIG_UTF_8
431 if (term->utf8) goto utf_8_select;
432 #endif /* CONFIG_UTF_8 */
433 len = s ? strlen(s) : 0;
434 for (i = 0; i < link->npoints; i++) {
435 x = link->points[i].x + dx;
436 y = link->points[i].y + dy;
437 if (is_in_box(box, x, y))
438 draw_char_data(term, x, y, i < len ? s[i] : '_');
440 break;
441 #ifdef CONFIG_UTF_8
442 utf_8_select:
443 text = s;
444 end = strchr(s, '\0');
445 len = strlen_utf8(&text);
446 for (i = 0; i < link->npoints; i++) {
447 x = link->points[i].x + dx;
448 y = link->points[i].y + dy;
449 if (is_in_box(box, x, y))
450 draw_char_data(term, x, y, i < len
451 ? utf_8_to_unicode(&s, end) : '_');
453 break;
454 #endif /* CONFIG_UTF_8 */
455 case FC_SUBMIT:
456 case FC_IMAGE:
457 case FC_RESET:
458 case FC_BUTTON:
459 case FC_HIDDEN:
460 break;
464 void
465 draw_forms(struct terminal *term, struct document_view *doc_view)
467 struct link *l1, *l2;
469 assert(term && doc_view);
470 if_assert_failed return;
472 l1 = get_first_link(doc_view);
473 l2 = get_last_link(doc_view);
475 if (!l1 || !l2) {
476 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
477 /* Return path :-). */
478 return;
480 do {
481 struct form_control *fc = get_link_form_control(l1);
483 if (!fc) continue;
484 #ifdef CONFIG_FORMHIST
485 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD) {
486 unsigned char *value;
488 assert(fc->form);
489 value = get_form_history_value(fc->form->action, fc->name);
491 if (value)
492 mem_free_set(&fc->default_value,
493 stracpy(value));
495 #endif /* CONFIG_FORMHIST */
496 draw_form_entry(term, doc_view, l1);
498 } while (l1++ < l2);
502 void
503 done_submitted_value_list(struct list_head *list)
505 struct submitted_value *sv, *svtmp;
507 assert(list);
508 if_assert_failed return;
510 foreach (sv, *list) {
511 svtmp = sv;
512 sv = sv->prev;
513 del_from_list(svtmp);
514 done_submitted_value(svtmp);
518 static void
519 add_submitted_value_to_list(struct form_control *fc,
520 struct form_state *fs,
521 struct list_head *list)
523 struct submitted_value *sub;
524 unsigned char *name;
525 enum form_type type;
526 int position;
528 assert(fc && fs && list);
530 name = fc->name;
531 position = fc->position;
532 type = fc->type;
534 switch (fc->type) {
535 case FC_TEXT:
536 case FC_PASSWORD:
537 case FC_FILE:
538 case FC_TEXTAREA:
539 sub = init_submitted_value(name, fs->value, type, fc, position);
540 if (sub) add_to_list(*list, sub);
541 break;
543 case FC_CHECKBOX:
544 case FC_RADIO:
545 if (!fs->state) break;
546 /* fall through */
548 case FC_SUBMIT:
549 case FC_HIDDEN:
550 case FC_RESET:
551 case FC_BUTTON:
552 sub = init_submitted_value(name, fs->value, type, fc,
553 position);
554 if (sub) add_to_list(*list, sub);
555 break;
557 case FC_SELECT:
558 if (!fc->nvalues) break;
560 fixup_select_state(fc, fs);
561 sub = init_submitted_value(name, fs->value, type, fc, position);
562 if (sub) add_to_list(*list, sub);
563 break;
565 case FC_IMAGE:
566 name = straconcat(fc->name, ".x", NULL);
567 if (!name) break;
568 sub = init_submitted_value(name, "0", type, fc, position);
569 mem_free(name);
570 if (sub) add_to_list(*list, sub);
572 name = straconcat(fc->name, ".y", NULL);
573 if (!name) break;
574 sub = init_submitted_value(name, "0", type, fc, position);
575 mem_free(name);
576 if (sub) add_to_list(*list, sub);
578 break;
582 static void
583 sort_submitted_values(struct list_head *list)
585 while (1) {
586 struct submitted_value *sub;
587 int changed = 0;
589 foreach (sub, *list) if (list_has_next(*list, sub))
590 if (sub->next->position < sub->position) {
591 struct submitted_value *next = sub->next;
593 del_from_list(sub);
594 add_at_pos(next, sub);
595 sub = next;
596 changed = 1;
599 foreachback (sub, *list) if (list_has_next(*list, sub))
600 if (sub->next->position < sub->position) {
601 struct submitted_value *next = sub->next;
603 del_from_list(sub);
604 add_at_pos(next, sub);
605 sub = next;
606 changed = 1;
609 if (!changed) break;
613 static void
614 get_successful_controls(struct document_view *doc_view,
615 struct form_control *fc, struct list_head *list)
617 struct form_control *fc2;
619 assert(doc_view && fc && fc->form && list);
620 if_assert_failed return;
622 foreach (fc2, fc->form->items) {
623 if (((fc2->type != FC_SUBMIT &&
624 fc2->type != FC_IMAGE &&
625 fc2->type != FC_RESET &&
626 fc2->type != FC_BUTTON) || fc2 == fc)
627 && fc2->name && fc2->name[0]) {
628 struct form_state *fs = find_form_state(doc_view, fc2);
630 if (!fs) continue;
632 add_submitted_value_to_list(fc2, fs, list);
636 sort_submitted_values(list);
639 static void
640 encode_controls(struct list_head *l, struct string *data,
641 int cp_from, int cp_to)
643 struct submitted_value *sv;
644 struct conv_table *convert_table = NULL;
645 int lst = 0;
647 assert(l && data);
648 if_assert_failed return;
650 foreach (sv, *l) {
651 unsigned char *p2 = NULL;
653 if (lst)
654 add_char_to_string(data, '&');
655 else
656 lst = 1;
658 encode_uri_string(data, sv->name, strlen(sv->name), 1);
659 add_char_to_string(data, '=');
661 /* Convert back to original encoding (see html_form_control()
662 * for the original recoding). */
663 if (sv->type == FC_TEXTAREA) {
664 unsigned char *p;
666 p = encode_textarea(sv);
667 if (p) {
668 if (!convert_table)
669 convert_table = get_translation_table(cp_from, cp_to);
671 p2 = convert_string(convert_table, p,
672 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
673 mem_free(p);
675 } else if (sv->type == FC_TEXT ||
676 sv->type == FC_PASSWORD) {
677 if (!convert_table)
678 convert_table = get_translation_table(cp_from, cp_to);
680 p2 = convert_string(convert_table, sv->value,
681 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
682 } else {
683 p2 = stracpy(sv->value);
686 if (p2) {
687 encode_uri_string(data, p2, strlen(p2), 1);
688 mem_free(p2);
695 #define BOUNDARY_LENGTH 32
696 #define realloc_bound_ptrs(bptrs, bptrs_size) \
697 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
699 struct boundary_info {
700 int count;
701 int *offsets;
702 unsigned char string[BOUNDARY_LENGTH];
705 static inline void
706 init_boundary(struct boundary_info *boundary)
708 memset(boundary, 0, sizeof(*boundary));
709 memset(boundary->string, '0', BOUNDARY_LENGTH);
712 /* Add boundary to string and save the offset */
713 static inline void
714 add_boundary(struct string *data, struct boundary_info *boundary)
716 add_to_string(data, "--");
718 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
719 boundary->offsets[boundary->count++] = data->length;
721 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
724 static inline unsigned char *
725 increment_boundary_counter(struct boundary_info *boundary)
727 int j;
729 /* This is just a decimal string incrementation */
730 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
731 if (boundary->string[j]++ < '9')
732 return boundary->string;
734 boundary->string[j] = '0';
737 INTERNAL("Form data boundary counter overflow");
739 return NULL;
742 static inline void
743 check_boundary(struct string *data, struct boundary_info *boundary)
745 unsigned char *bound = boundary->string;
746 int i;
748 /* Search between all boundaries. There is a starting and an ending
749 * boundary so only check the range of chars after the current offset
750 * and before the next offset. If some string in the form data matches
751 * the boundary string it is changed. */
752 for (i = 0; i < boundary->count - 1; i++) {
753 /* Start after the boundary string and also jump past the
754 * "\r\nContent-Disposition: form-data; name=\"" string added
755 * before any form data. */
756 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
758 /* End so that there is atleast BOUNDARY_LENGTH chars to
759 * compare. Subtract 2 char because there is no need to also
760 * compare the '--' prefix that is part of the boundary. */
761 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
762 unsigned char *pos = data->source + start_offset;
763 unsigned char *end = data->source + end_offset;
765 for (; pos <= end; pos++) {
766 if (memcmp(pos, bound, BOUNDARY_LENGTH))
767 continue;
769 /* If incrementing causes overflow bail out. There is
770 * no need to reset the boundary string with '0' since
771 * that is already done when incrementing. */
772 if (!increment_boundary_counter(boundary))
773 return;
775 /* Else start checking all boundaries using the new
776 * boundary string */
777 i = 0;
778 break;
782 /* Now update all the boundaries with the unique boundary string */
783 for (i = 0; i < boundary->count; i++)
784 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
787 /* FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
788 static void
789 encode_multipart(struct session *ses, struct list_head *l, struct string *data,
790 struct boundary_info *boundary, int cp_from, int cp_to)
792 struct conv_table *convert_table = NULL;
793 struct submitted_value *sv;
795 assert(ses && l && data && boundary);
796 if_assert_failed return;
798 init_boundary(boundary);
800 foreach (sv, *l) {
801 add_boundary(data, boundary);
802 add_crlf_to_string(data);
804 /* FIXME: name is not encoded.
805 * from RFC 1867:
806 * multipart/form-data contains a series of parts.
807 * Each part is expected to contain a content-disposition
808 * header where the value is "form-data" and a name attribute
809 * specifies the field name within the form,
810 * e.g., 'content-disposition: form-data; name="xxxxx"',
811 * where xxxxx is the field name corresponding to that field.
812 * Field names originally in non-ASCII character sets may be
813 * encoded using the method outlined in RFC 1522. */
814 add_to_string(data, "Content-Disposition: form-data; name=\"");
815 add_to_string(data, sv->name);
816 add_char_to_string(data, '"');
818 if (sv->type == FC_FILE) {
819 #define F_BUFLEN 1024
820 int fh;
821 unsigned char buffer[F_BUFLEN];
822 unsigned char *extension;
824 add_to_string(data, "; filename=\"");
825 add_to_string(data, get_filename_position(sv->value));
826 /* It sends bad data if the file name contains ", but
827 Netscape does the same */
828 /* FIXME: We should follow RFCs 1522, 1867,
829 * 2047 (updated by rfc 2231), to provide correct support
830 * for non-ASCII and special characters in values. --Zas */
831 add_char_to_string(data, '"');
833 /* Add a Content-Type header if the type is configured */
834 extension = strrchr(sv->value, '.');
835 if (extension) {
836 unsigned char *type = get_extension_content_type(extension);
838 if (type) {
839 add_crlf_to_string(data);
840 add_to_string(data, "Content-Type: ");
841 add_to_string(data, type);
842 mem_free(type);
846 add_crlf_to_string(data);
847 add_crlf_to_string(data);
849 if (*sv->value) {
850 unsigned char *filename;
852 if (get_cmd_opt_bool("anonymous")) {
853 errno = EPERM;
854 goto encode_error;
857 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
858 filename = expand_tilde(sv->value);
859 if (!filename) goto encode_error;
861 fh = open(filename, O_RDONLY);
862 mem_free(filename);
864 if (fh == -1) goto encode_error;
865 set_bin(fh);
866 while (1) {
867 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
869 if (rd) {
870 if (rd == -1) {
871 close(fh);
872 goto encode_error;
875 add_bytes_to_string(data, buffer, rd);
877 } else {
878 break;
881 close(fh);
883 #undef F_BUFLEN
884 } else {
885 add_crlf_to_string(data);
886 add_crlf_to_string(data);
888 /* Convert back to original encoding (see
889 * html_form_control() for the original recoding). */
890 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
891 sv->type == FC_TEXTAREA) {
892 unsigned char *p;
894 if (!convert_table)
895 convert_table = get_translation_table(cp_from,
896 cp_to);
898 p = convert_string(convert_table, sv->value,
899 strlen(sv->value), -1, CSM_FORM, NULL,
900 NULL, NULL);
901 if (p) {
902 add_to_string(data, p);
903 mem_free(p);
905 } else {
906 add_to_string(data, sv->value);
910 add_crlf_to_string(data);
913 /* End-boundary */
914 add_boundary(data, boundary);
915 add_to_string(data, "--\r\n");
917 check_boundary(data, boundary);
919 mem_free_if(boundary->offsets);
920 return;
922 encode_error:
923 mem_free_if(boundary->offsets);
924 done_string(data);
926 /* XXX: This error message should move elsewhere. --Zas */
927 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
928 N_("Error while posting form"), ALIGN_CENTER,
929 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
930 sv->value, strerror(errno)));
933 static void
934 encode_newlines(struct string *string, unsigned char *data)
936 for (; *data; data++) {
937 if (*data == '\n' || *data == '\r') {
938 unsigned char buffer[3];
940 /* Hex it. */
941 buffer[0] = '%';
942 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
943 buffer[2] = hx(((int) *data) & 0xF);
944 add_bytes_to_string(string, buffer, 3);
945 } else {
946 add_char_to_string(string, *data);
951 static void
952 encode_text_plain(struct list_head *l, struct string *data,
953 int cp_from, int cp_to)
955 struct submitted_value *sv;
956 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
958 assert(l && data);
959 if_assert_failed return;
961 foreach (sv, *l) {
962 unsigned char *area51 = NULL;
963 unsigned char *value = sv->value;
965 add_to_string(data, sv->name);
966 add_char_to_string(data, '=');
968 switch (sv->type) {
969 case FC_TEXTAREA:
970 value = area51 = encode_textarea(sv);
971 if (!area51) break;
972 /* Fall through */
973 case FC_TEXT:
974 case FC_PASSWORD:
975 /* Convert back to original encoding (see
976 * html_form_control() for the original recoding). */
977 value = convert_string(convert_table, value,
978 strlen(value), -1, CSM_FORM,
979 NULL, NULL, NULL);
980 default:
981 /* Falling right through to free that textarea stuff */
982 mem_free_if(area51);
984 /* Did the conversion fail? */
985 if (!value) break;
987 encode_newlines(data, value);
989 /* Free if we did convert something */
990 if (value != sv->value) mem_free(value);
993 add_crlf_to_string(data);
997 void
998 do_reset_form(struct document_view *doc_view, struct form *form)
1000 struct form_control *fc;
1002 assert(doc_view && doc_view->document);
1003 if_assert_failed return;
1005 foreach (fc, form->items) {
1006 struct form_state *fs = find_form_state(doc_view, fc);
1008 if (fs) init_form_state(fc, fs);
1012 enum frame_event_status
1013 reset_form(struct session *ses, struct document_view *doc_view, int a)
1015 struct link *link = get_current_link(doc_view);
1017 if (!link) return FRAME_EVENT_OK;
1019 do_reset_form(doc_view, get_link_form_control(link)->form);
1020 draw_forms(ses->tab->term, doc_view);
1022 /* Could be the refresh return value and then ditch the draw_forms()
1023 * call. */
1024 return FRAME_EVENT_OK;
1027 struct uri *
1028 get_form_uri(struct session *ses, struct document_view *doc_view,
1029 struct form_control *fc)
1031 struct boundary_info boundary;
1032 INIT_LIST_HEAD(submit);
1033 struct string data;
1034 struct string go;
1035 int cp_from, cp_to;
1036 struct uri *uri;
1037 struct form *form;
1039 assert(ses && ses->tab && ses->tab->term);
1040 if_assert_failed return NULL;
1041 assert(doc_view && doc_view->document && fc && fc->form);
1042 if_assert_failed return NULL;
1044 form = fc->form;
1046 if (fc->type == FC_RESET) {
1047 do_reset_form(doc_view, form);
1048 return NULL;
1051 if (!form->action
1052 || !init_string(&data))
1053 return NULL;
1055 get_successful_controls(doc_view, fc, &submit);
1057 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset");
1058 cp_to = doc_view->document->cp;
1059 switch (form->method) {
1060 case FORM_METHOD_GET:
1061 case FORM_METHOD_POST:
1062 encode_controls(&submit, &data, cp_from, cp_to);
1063 break;
1065 case FORM_METHOD_POST_MP:
1066 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1067 break;
1069 case FORM_METHOD_POST_TEXT_PLAIN:
1070 encode_text_plain(&submit, &data, cp_from, cp_to);
1073 #ifdef CONFIG_FORMHIST
1074 /* XXX: We check data.source here because a NULL value can indicate
1075 * not only a memory allocation failure, but also an error reading
1076 * a file that is to be uploaded. TODO: Distinguish between
1077 * these two classes of errors (is it worth it?). -- Miciah */
1078 if (data.source
1079 && get_opt_bool("document.browse.forms.show_formhist"))
1080 memorize_form(ses, &submit, form);
1081 #endif
1083 done_submitted_value_list(&submit);
1085 if (!data.source
1086 || !init_string(&go)) {
1087 done_string(&data);
1088 return NULL;
1091 switch (form->method) {
1092 case FORM_METHOD_GET:
1094 unsigned char *pos = strchr(form->action, '#');
1096 if (pos) {
1097 add_bytes_to_string(&go, form->action, pos - form->action);
1098 } else {
1099 add_to_string(&go, form->action);
1102 if (strchr(go.source, '?'))
1103 add_char_to_string(&go, '&');
1104 else
1105 add_char_to_string(&go, '?');
1107 add_string_to_string(&go, &data);
1109 if (pos) add_to_string(&go, pos);
1110 break;
1112 case FORM_METHOD_POST:
1113 case FORM_METHOD_POST_MP:
1114 case FORM_METHOD_POST_TEXT_PLAIN:
1116 /* Note that we end content type here by a simple '\n',
1117 * replaced later by correct '\r\n' in http_send_header(). */
1118 int i;
1120 add_to_string(&go, form->action);
1121 add_char_to_string(&go, POST_CHAR);
1122 if (form->method == FORM_METHOD_POST) {
1123 add_to_string(&go, "application/x-www-form-urlencoded\n");
1125 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1126 /* Dunno about this one but we don't want the full
1127 * hextcat thingy. --jonas */
1128 add_to_string(&go, "text/plain\n");
1129 add_to_string(&go, data.source);
1130 break;
1132 } else {
1133 add_to_string(&go, "multipart/form-data; boundary=");
1134 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1135 add_char_to_string(&go, '\n');
1138 for (i = 0; i < data.length; i++) {
1139 unsigned char p[3];
1141 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1142 add_to_string(&go, p);
1147 done_string(&data);
1149 uri = get_uri(go.source, 0);
1150 done_string(&go);
1151 if (uri) uri->form = 1;
1153 return uri;
1156 #undef BOUNDARY_LENGTH
1159 enum frame_event_status
1160 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1162 goto_current_link(ses, doc_view, do_reload);
1163 return FRAME_EVENT_OK;
1166 void
1167 submit_given_form(struct session *ses, struct document_view *doc_view,
1168 struct form *form, int do_reload)
1170 /* Added support for submitting forms in hidden
1171 * links in 1.285, commented code can safely be removed once we have made sure the new
1172 * code does the right thing. */
1173 #if 0
1175 struct document *document = doc_view->document;
1176 int link;
1178 for (link = 0; link < document->nlinks; link++) {
1179 struct form_control *fc = get_link_form_control(&document->links[link]);
1181 if (fc && fc->form == form) {
1182 doc_view->vs->current_link = link;
1183 submit_form(ses, doc_view, 0);
1184 return;
1187 #endif
1188 if (!list_empty(form->items)) {
1189 struct form_control *fc = (struct form_control *)form->items.next;
1190 struct uri *uri;
1191 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1193 if (!fc) return;
1194 uri = get_form_uri(ses, doc_view, fc);
1195 if (!uri) return;
1196 goto_uri_frame(ses, uri, form->target, mode);
1197 done_uri(uri);
1201 void
1202 auto_submit_form(struct session *ses)
1204 struct document *document = ses->doc_view->document;
1206 if (!list_empty(document->forms))
1207 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1211 /* menu_func_T */
1212 static void
1213 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1215 unsigned char *filename = filename_;
1216 struct form_state *fs = fs_;
1218 /* The menu code doesn't free the filename data */
1219 mem_free_set(&fs->value, filename);
1220 fs->state = strlen(filename);
1221 redraw_terminal(term);
1224 /* menu_func_T */
1225 static void
1226 file_form_menu(struct terminal *term, void *path_, void *fs_)
1228 unsigned char *path = path_;
1229 struct form_state *fs = fs_;
1231 /* FIXME: It doesn't work for ../../ */
1232 #if 0
1233 int valuelen = strlen(fs->value);
1234 int pathlen = strlen(path);
1235 int no_elevator = 0;
1237 /* Don't add elevators for subdirs menus */
1238 /* It is not perfect at all because fs->value is not updated for each
1239 * newly opened file menu. Maybe it should be dropped. */
1240 for (; valuelen < pathlen; valuelen++) {
1241 if (dir_sep(path[valuelen - 1])) {
1242 no_elevator = 1;
1243 break;
1246 #endif
1248 auto_complete_file(term, 0 /* no_elevator */, path,
1249 set_file_form_state,
1250 file_form_menu, fs);
1254 enum frame_event_status
1255 field_op(struct session *ses, struct document_view *doc_view,
1256 struct link *link, struct term_event *ev)
1258 struct form_control *fc;
1259 struct form_state *fs;
1260 enum edit_action action_id;
1261 unsigned char *text;
1262 int length;
1263 enum frame_event_status status = FRAME_EVENT_REFRESH;
1264 #ifdef CONFIG_UTF_8
1265 int utf8 = ses->tab->term->utf8;
1266 #endif /* CONFIG_UTF_8 */
1268 assert(ses && doc_view && link && ev);
1269 if_assert_failed return FRAME_EVENT_OK;
1271 fc = get_link_form_control(link);
1272 assertm(fc, "link has no form control");
1273 if_assert_failed return FRAME_EVENT_OK;
1275 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1276 || ses->insert_mode == INSERT_MODE_OFF)
1277 return FRAME_EVENT_IGNORED;
1279 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1281 fs = find_form_state(doc_view, fc);
1282 if (!fs || !fs->value) return FRAME_EVENT_OK;
1284 switch (action_id) {
1285 case ACT_EDIT_LEFT:
1286 #ifdef CONFIG_UTF_8
1287 if (utf8) {
1288 unsigned char *text = fs->value;
1289 unsigned char *end = fs->value + fs->state - 1;
1290 int old = fs->state;
1292 while (utf_8_to_unicode(&text, end) != UCS_NO_CHAR);
1293 fs->state = (int)(text - fs->value);
1294 if (old != fs->state) fs->utf8_pos--;
1295 } else
1296 #endif /* CONFIG_UTF_8 */
1297 fs->state = int_max(fs->state - 1, 0);
1298 break;
1299 case ACT_EDIT_RIGHT:
1300 #ifdef CONFIG_UTF_8
1301 if (utf8) {
1302 unsigned char *text = fs->value + fs->state;
1303 unsigned char *end = strchr(text, '\0');
1304 int old = fs->state;
1306 utf_8_to_unicode(&text, end);
1307 fs->state = (int)(text - fs->value);
1308 if (old != fs->state) fs->utf8_pos++;
1309 } else
1310 #endif /* CONFIG_UTF_8 */
1311 fs->state = int_min(fs->state + 1, strlen(fs->value));
1312 break;
1313 case ACT_EDIT_HOME:
1314 if (fc->type == FC_TEXTAREA) {
1315 #ifdef CONFIG_UTF_8
1316 status = textarea_op_home(fs, fc, utf8);
1317 #else
1318 status = textarea_op_home(fs, fc);
1319 #endif /* CONFIG_UTF_8 */
1320 } else {
1321 fs->state = 0;
1322 #ifdef CONFIG_UTF_8
1323 fs->utf8_pos = 0;
1324 #endif /* CONFIG_UTF_8 */
1326 break;
1327 case ACT_EDIT_UP:
1328 if (fc->type != FC_TEXTAREA)
1329 status = FRAME_EVENT_IGNORED;
1330 else
1331 #ifdef CONFIG_UTF_8
1332 status = textarea_op_up(fs, fc, utf8);
1333 #else
1334 status = textarea_op_up(fs, fc);
1335 #endif /* CONFIG_UTF_8 */
1336 break;
1337 case ACT_EDIT_DOWN:
1338 if (fc->type != FC_TEXTAREA)
1339 status = FRAME_EVENT_IGNORED;
1340 else
1341 #ifdef CONFIG_UTF_8
1342 status = textarea_op_down(fs, fc, utf8);
1343 #else
1344 status = textarea_op_down(fs, fc);
1345 #endif /* CONFIG_UTF_8 */
1346 break;
1347 case ACT_EDIT_END:
1348 if (fc->type == FC_TEXTAREA) {
1349 #ifdef CONFIG_UTF_8
1350 status = textarea_op_end(fs, fc, utf8);
1351 #else
1352 status = textarea_op_end(fs, fc);
1353 #endif /* CONFIG_UTF_8 */
1354 } else {
1355 fs->state = strlen(fs->value);
1356 #ifdef CONFIG_UTF_8
1357 if (utf8) {
1358 unsigned char *text = fs->value;
1360 fs->utf8_pos = strlen_utf8(&text);
1362 #endif /* CONFIG_UTF_8 */
1364 break;
1365 case ACT_EDIT_BEGINNING_OF_BUFFER:
1366 if (fc->type == FC_TEXTAREA) {
1367 #ifdef CONFIG_UTF_8
1368 status = textarea_op_bob(fs, fc, utf8);
1369 #else
1370 status = textarea_op_bob(fs, fc);
1371 #endif /* CONFIG_UTF_8 */
1372 } else {
1373 fs->state = 0;
1374 #ifdef CONFIG_UTF_8
1375 fs->utf8_pos = 0;
1376 #endif /* CONFIG_UTF_8 */
1378 break;
1379 case ACT_EDIT_END_OF_BUFFER:
1380 if (fc->type == FC_TEXTAREA) {
1381 #ifdef CONFIG_UTF_8
1382 status = textarea_op_eob(fs, fc, utf8);
1383 #else
1384 status = textarea_op_eob(fs, fc);
1385 #endif /* CONFIG_UTF_8 */
1386 } else {
1387 fs->state = strlen(fs->value);
1388 #ifdef CONFIG_UTF_8
1389 if (utf8) {
1390 unsigned char *text = fs->value;
1392 fs->utf8_pos = strlen_utf8(&text);
1394 #endif /* CONFIG_UTF_8 */
1396 break;
1397 case ACT_EDIT_OPEN_EXTERNAL:
1398 if (form_field_is_readonly(fc))
1399 status = FRAME_EVENT_IGNORED;
1400 else if (fc->type == FC_TEXTAREA)
1401 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1402 break;
1403 case ACT_EDIT_COPY_CLIPBOARD:
1404 set_clipboard_text(fs->value);
1405 status = FRAME_EVENT_OK;
1406 break;
1407 case ACT_EDIT_CUT_CLIPBOARD:
1408 set_clipboard_text(fs->value);
1409 if (!form_field_is_readonly(fc))
1410 fs->value[0] = 0;
1411 fs->state = 0;
1412 #ifdef CONFIG_UTF_8
1413 fs->utf8_pos = 0;
1414 #endif /* CONFIG_UTF_8 */
1415 break;
1416 case ACT_EDIT_PASTE_CLIPBOARD:
1417 if (form_field_is_readonly(fc)) break;
1419 text = get_clipboard_text();
1420 if (!text) break;
1422 length = strlen(text);
1423 if (length <= fc->maxlength) {
1424 unsigned char *v = mem_realloc(fs->value, length + 1);
1426 if (v) {
1427 fs->value = v;
1428 memmove(v, text, length + 1);
1429 fs->state = strlen(fs->value);
1430 #ifdef CONFIG_UTF_8
1431 if (utf8 && fc->type != FC_TEXTAREA) {
1432 unsigned char *text = fs->value;
1434 fs->utf8_pos = strlen_utf8(&text);
1436 #endif /* CONFIG_UTF_8 */
1439 mem_free(text);
1440 break;
1441 case ACT_EDIT_ENTER:
1442 if (fc->type == FC_TEXTAREA) {
1443 #ifdef CONFIG_UTF_8
1444 status = textarea_op_enter(fs, fc, utf8);
1445 #else
1446 status = textarea_op_enter(fs, fc);
1447 #endif /* CONFIG_UTF_8 */
1448 break;
1451 /* Set status to ok if either it is not possible to
1452 * submit the form or the posting fails. */
1453 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1454 if ((has_form_submit(fc->form)
1455 && !get_opt_bool("document.browse.forms.auto_submit"))
1456 || goto_current_link(ses, doc_view, 0)) {
1457 if (ses->insert_mode == INSERT_MODE_ON)
1458 ses->insert_mode = INSERT_MODE_OFF;
1459 status = FRAME_EVENT_OK;
1461 break;
1462 case ACT_EDIT_BACKSPACE:
1463 if (form_field_is_readonly(fc)) {
1464 status = FRAME_EVENT_IGNORED;
1465 break;
1468 if (!fs->state) {
1469 status = FRAME_EVENT_OK;
1470 break;
1472 #ifdef CONFIG_UTF_8
1473 if (utf8) {
1474 int i;
1475 unsigned char *text = fs->value;
1476 unsigned char *end = fs->value + fs->state;
1478 for (i = 0; i < fs->utf8_pos - 1; i++)
1479 utf_8_to_unicode(&text, end);
1480 length = strlen(end) + 1;
1481 memmove(text, end, length);
1482 fs->state = (int)(text - fs->value);
1483 fs->utf8_pos--;
1484 break;
1486 #endif /* CONFIG_UTF_8 */
1487 length = strlen(fs->value + fs->state) + 1;
1488 text = fs->value + fs->state;
1490 memmove(text - 1, text, length);
1491 fs->state--;
1492 break;
1493 case ACT_EDIT_DELETE:
1494 if (form_field_is_readonly(fc)) {
1495 status = FRAME_EVENT_IGNORED;
1496 break;
1499 length = strlen(fs->value);
1500 if (fs->state >= length) {
1501 status = FRAME_EVENT_OK;
1502 break;
1504 #ifdef CONFIG_UTF_8
1505 if (utf8) {
1506 unsigned char *end = fs->value + length;
1507 unsigned char *text = fs->value + fs->state;
1508 unsigned char *old = text;
1510 utf_8_to_unicode(&text, end);
1511 if (old != text) {
1512 memmove(old, text,
1513 (int)(end - text) + 1);
1515 break;
1517 #endif /* CONFIG_UTF_8 */
1518 text = fs->value + fs->state;
1520 memmove(text, text + 1, length - fs->state);
1521 break;
1522 case ACT_EDIT_KILL_TO_BOL:
1523 if (form_field_is_readonly(fc)) {
1524 status = FRAME_EVENT_IGNORED;
1525 break;
1528 if (fs->state <= 0) {
1529 status = FRAME_EVENT_OK;
1530 break;
1533 text = memrchr(fs->value, ASCII_LF, fs->state);
1534 if (text) {
1535 /* Leave the new-line character if it does not
1536 * immediately precede the cursor. */
1537 if (text != &fs->value[fs->state - 1])
1538 text++;
1539 } else {
1540 text = fs->value;
1543 length = strlen(fs->value + fs->state) + 1;
1544 memmove(text, fs->value + fs->state, length);
1546 fs->state = (int) (text - fs->value);
1547 #ifdef CONFIG_UTF_8
1548 if (utf8 && fc->type != FC_TEXTAREA) {
1549 unsigned char *text = fs->value;
1551 fs->utf8_pos = strlen_utf8(&text);
1553 #endif /* CONFIG_UTF_8 */
1554 break;
1555 case ACT_EDIT_KILL_TO_EOL:
1556 if (form_field_is_readonly(fc)) {
1557 status = FRAME_EVENT_IGNORED;
1558 break;
1561 if (!fs->value[fs->state]) {
1562 status = FRAME_EVENT_OK;
1563 break;
1566 text = strchr(fs->value + fs->state, ASCII_LF);
1567 if (!text) {
1568 fs->value[fs->state] = '\0';
1569 break;
1572 if (fs->value[fs->state] == ASCII_LF)
1573 ++text;
1575 memmove(fs->value + fs->state, text, strlen(text) + 1);
1576 break;
1578 case ACT_EDIT_AUTO_COMPLETE:
1579 if (fc->type != FC_FILE
1580 || form_field_is_readonly(fc)) {
1581 status = FRAME_EVENT_IGNORED;
1582 break;
1585 file_form_menu(ses->tab->term, fs->value, fs);
1586 break;
1588 case ACT_EDIT_CANCEL:
1589 if (ses->insert_mode == INSERT_MODE_ON)
1590 ses->insert_mode = INSERT_MODE_OFF;
1591 else
1592 status = FRAME_EVENT_IGNORED;
1593 break;
1595 case ACT_EDIT_REDRAW:
1596 redraw_terminal_cls(ses->tab->term);
1597 status = FRAME_EVENT_OK;
1598 break;
1600 default:
1601 if (!check_kbd_textinput_key(ev)) {
1602 status = FRAME_EVENT_IGNORED;
1603 break;
1606 if (form_field_is_readonly(fc)
1607 || strlen(fs->value) >= fc->maxlength
1608 #ifndef CONFIG_UTF_8
1609 || !insert_in_string(&fs->value, fs->state, "?", 1)
1610 #endif /* CONFIG_UTF_8 */
1613 status = FRAME_EVENT_OK;
1614 break;
1616 #ifdef CONFIG_UTF_8
1617 if (utf8) {
1618 static unsigned char buf[7];
1619 static int i = 0;
1620 unicode_val_T data;
1621 unsigned char *t;
1623 t = buf;
1624 buf[i++] = get_kbd_key(ev);
1625 buf[i] = 0;
1626 data = utf_8_to_unicode(&t, buf + i);
1627 if (data != UCS_NO_CHAR) {
1628 if (!insert_in_string(&fs->value, fs->state, buf, i)) {
1629 i = 0;
1630 return FRAME_EVENT_OK;
1632 fs->state += i;
1633 fs->utf8_pos++;
1634 i = 0;
1635 break;
1638 if (i == 6) {
1639 i = 0;
1641 return FRAME_EVENT_OK;
1643 } else {
1644 if (!insert_in_string(&fs->value, fs->state, "?", 1))
1645 return FRAME_EVENT_OK;
1646 fs->value[fs->state++] = get_kbd_key(ev);
1648 #else
1649 fs->value[fs->state++] = get_kbd_key(ev);
1650 #endif /* CONFIG_UTF_8 */
1651 break;
1654 return status;
1657 unsigned char *
1658 get_form_label(struct form_control *fc)
1660 assert(fc->form);
1661 switch (fc->type) {
1662 case FC_RESET:
1663 return N_("Reset form");
1664 case FC_BUTTON:
1665 return N_("Harmless button");
1666 case FC_HIDDEN:
1667 return NULL;
1668 case FC_SUBMIT:
1669 case FC_IMAGE:
1670 if (!fc->form->action) return NULL;
1672 if (fc->form->method == FORM_METHOD_GET)
1673 return N_("Submit form to");
1674 return N_("Post form to");
1675 case FC_RADIO:
1676 return N_("Radio button");
1677 case FC_CHECKBOX:
1678 return N_("Checkbox");
1679 case FC_SELECT:
1680 return N_("Select field");
1681 case FC_TEXT:
1682 return N_("Text field");
1683 case FC_TEXTAREA:
1684 return N_("Text area");
1685 case FC_FILE:
1686 return N_("File upload");
1687 case FC_PASSWORD:
1688 return N_("Password field");
1691 return NULL;
1694 static inline void
1695 add_form_attr_to_string(struct string *string, struct terminal *term,
1696 unsigned char *name, unsigned char *value)
1698 add_to_string(string, ", ");
1699 add_to_string(string, _(name, term));
1700 if (value) {
1701 add_char_to_string(string, ' ');
1702 add_to_string(string, value);
1706 unsigned char *
1707 get_form_info(struct session *ses, struct document_view *doc_view)
1709 struct terminal *term = ses->tab->term;
1710 struct link *link = get_current_link(doc_view);
1711 struct form_control *fc;
1712 unsigned char *label, *key;
1713 struct string str;
1715 assert(link);
1717 fc = get_link_form_control(link);
1718 label = get_form_label(fc);
1719 if (!label) return NULL;
1721 if (!init_string(&str)) return NULL;
1723 add_to_string(&str, _(label, term));
1725 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1726 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1729 switch (fc->type) {
1730 case FC_CHECKBOX:
1731 case FC_RADIO:
1733 struct form_state *fs = find_form_state(doc_view, fc);
1735 if (!fs->value || !fs->value[0])
1736 break;
1738 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1739 break;
1742 case FC_TEXT:
1743 case FC_PASSWORD:
1744 case FC_FILE:
1745 case FC_TEXTAREA:
1747 struct uri *uri;
1748 unsigned char *uristring;
1750 if (form_field_is_readonly(fc)) {
1751 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1754 /* Should we add info about entering insert mode or add info
1755 * about submitting the form? */
1756 if (ses->insert_mode == INSERT_MODE_OFF) {
1757 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1759 if (!key) break;
1761 if (form_field_is_readonly(fc))
1762 label = N_("press %s to navigate");
1763 else
1764 label = N_("press %s to edit");
1766 add_to_string(&str, " (");
1767 add_format_to_string(&str, _(label, term), key);
1768 add_char_to_string(&str, ')');
1769 mem_free(key);
1770 break;
1774 if (fc->type == FC_TEXTAREA)
1775 break;
1777 assert(fc->form);
1779 if (!fc->form->action
1780 || (has_form_submit(fc->form)
1781 && !get_opt_bool("document.browse.forms.auto_submit")))
1782 break;
1784 uri = get_uri(fc->form->action, 0);
1785 if (!uri) break;
1787 /* Add the uri with password and post info stripped */
1788 uristring = get_uri_string(uri, URI_PUBLIC);
1789 done_uri(uri);
1791 if (!uristring) break;
1793 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1794 if (!key) {
1795 mem_free(uristring);
1796 break;
1799 if (fc->form->method == FORM_METHOD_GET)
1800 label = N_("press %s to submit to %s");
1801 else
1802 label = N_("press %s to post to %s");
1804 add_to_string(&str, " (");
1805 add_format_to_string(&str, _(label, term), key, uristring);
1806 mem_free(uristring);
1807 mem_free(key);
1809 add_char_to_string(&str, ')');
1810 break;
1812 case FC_SUBMIT:
1813 case FC_IMAGE:
1814 add_char_to_string(&str, ' ');
1816 assert(fc->form);
1817 /* Add the uri with password and post info stripped */
1818 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
1819 break;
1821 case FC_HIDDEN:
1822 case FC_RESET:
1823 case FC_BUTTON:
1824 case FC_SELECT:
1825 break;
1828 if (link->accesskey
1829 && get_opt_bool("document.browse.accesskey.display")) {
1830 add_to_string(&str, " (");
1831 add_accesskey_to_string(&str, link->accesskey);
1832 add_char_to_string(&str, ')');
1835 return str.source;
1838 static void
1839 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
1841 struct session *ses = ses_;
1842 struct document_view *doc_view;
1843 int link_number = *(int *) link_number_;
1845 mem_free(link_number_);
1847 assert(term && ses);
1848 if_assert_failed return;
1850 doc_view = current_frame(ses);
1851 if (!doc_view) return;
1853 assert(doc_view->vs && doc_view->document);
1854 if_assert_failed return;
1856 jump_to_link_number(ses, doc_view, link_number);
1857 refresh_view(ses, doc_view, 0);
1860 void
1861 link_form_menu(struct session *ses)
1863 struct document_view *doc_view;
1864 struct link *link;
1865 struct menu_item *mi;
1866 struct form_control *fc;
1867 struct form *form;
1869 assert(ses);
1870 if_assert_failed return;
1872 doc_view = current_frame(ses);
1873 if (!doc_view) return;
1875 assert(doc_view->vs && doc_view->document);
1876 if_assert_failed return;
1878 link = get_current_link(doc_view);
1879 if (!link) return;
1881 assert(link_is_form(link));
1883 fc = get_link_form_control(link);
1884 if (!fc) return;
1886 form = fc->form;
1888 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
1889 if (!mi) return;
1891 foreach (fc, form->items) {
1892 unsigned char *text;
1893 unsigned char *rtext;
1894 int link_number;
1895 struct string str;
1897 switch (fc->type) {
1898 case FC_HIDDEN:
1899 continue;
1901 case FC_SUBMIT:
1902 case FC_IMAGE:
1903 if (!form->action)
1904 text = N_("Useless button");
1905 else
1906 text = N_("Submit button");
1907 break;
1909 default:
1910 text = get_form_label(fc);
1913 link_number = get_form_control_link(doc_view->document, fc);
1914 if (link_number < 0
1915 || !init_string(&str))
1916 continue;
1918 assert(text);
1919 add_to_string(&str, _(text, ses->tab->term));
1921 rtext = fc->name;
1922 if (!rtext) rtext = fc->alt;
1924 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
1925 link_form_menu_func, intdup(link_number),
1926 FREE_DATA);
1929 do_menu(ses->tab->term, mi, ses, 1);