Handling onsubmit
[elinks.git] / src / viewer / text / form.c
blob7f4bf9e4ecb056123b94d0827c0c365394ee8990
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 fs->vpos = 0;
167 break;
168 case FC_FILE:
169 fs->value = stracpy("");
170 fs->state = 0;
171 fs->vpos = 0;
172 break;
173 case FC_SELECT:
174 fs->value = stracpy(fc->default_value);
175 fs->state = fc->default_state;
176 fixup_select_state(fc, fs);
177 break;
178 case FC_CHECKBOX:
179 case FC_RADIO:
180 fs->state = fc->default_state;
181 /* Fall-through */
182 case FC_SUBMIT:
183 case FC_IMAGE:
184 case FC_RESET:
185 case FC_BUTTON:
186 case FC_HIDDEN:
187 fs->value = stracpy(fc->default_value);
188 break;
193 struct form_state *
194 find_form_state(struct document_view *doc_view, struct form_control *fc)
196 struct view_state *vs;
197 struct form_state *fs;
198 int n;
200 assert(doc_view && doc_view->vs && fc);
201 if_assert_failed return NULL;
203 vs = doc_view->vs;
204 n = fc->g_ctrl_num;
206 if (n >= vs->form_info_len) {
207 int nn = n + 1;
209 fs = mem_realloc(vs->form_info, nn * sizeof(*fs));
210 if (!fs) return NULL;
211 memset(fs + vs->form_info_len, 0,
212 (nn - vs->form_info_len) * sizeof(*fs));
213 vs->form_info = fs;
214 vs->form_info_len = nn;
216 fs = &vs->form_info[n];
218 if (fs->form_view && fs->form_view->form_num == fc->form->form_num
219 && fs->g_ctrl_num == fc->g_ctrl_num
220 && fs->position == fc->position
221 && fs->type == fc->type)
222 return fs;
224 mem_free_if(fs->value);
225 memset(fs, 0, sizeof(*fs));
226 fs->form_view = find_form_view(doc_view, fc->form);
227 fs->g_ctrl_num = fc->g_ctrl_num;
228 fs->position = fc->position;
229 fs->type = fc->type;
230 init_form_state(fc, fs);
232 return fs;
235 struct form_control *
236 find_form_control(struct document *document, struct form_state *fs)
238 struct form *form = find_form_by_form_view(document, fs->form_view);
239 struct form_control *fc;
241 foreach (fc, form->items) {
242 if (fs->g_ctrl_num == fc->g_ctrl_num
243 && fs->position == fc->position
244 && fs->type == fc->type)
245 return fc;
248 return NULL;
251 struct form_view *
252 find_form_view_in_vs(struct view_state *vs, int form_num)
254 struct form_view *fv;
256 assert(vs);
258 foreach (fv, vs->forms)
259 if (fv->form_num == form_num)
260 return fv;
262 fv = mem_calloc(1, sizeof(*fv));
263 fv->form_num = form_num;
264 add_to_list(vs->forms, fv);
265 return fv;
268 struct form_view *
269 find_form_view(struct document_view *doc_view, struct form *form)
271 return find_form_view_in_vs(doc_view->vs, form->form_num);
274 struct form *
275 find_form_by_form_view(struct document *document, struct form_view *fv)
277 struct form *form;
279 foreach (form, document->forms) {
280 if (form->form_num == fv->form_num)
281 return form;
283 return NULL;
288 get_current_state(struct session *ses)
290 struct document_view *doc_view;
291 struct link *link;
292 struct form_state *fs;
294 assert(ses);
295 if_assert_failed return -1;
296 doc_view = current_frame(ses);
298 assert(doc_view && doc_view->vs && doc_view->document);
299 if_assert_failed return -1;
301 link = get_current_link(doc_view);
302 if (!link || link->type != LINK_SELECT) return -1;
304 fs = find_form_state(doc_view, get_link_form_control(link));
305 if (fs) return fs->state;
306 return -1;
309 void
310 draw_form_entry(struct terminal *term, struct document_view *doc_view,
311 struct link *link)
313 struct form_state *fs;
314 struct form_control *fc;
315 struct view_state *vs;
316 struct box *box;
317 int dx, dy;
319 assert(term && doc_view && doc_view->document && doc_view->vs && link);
320 if_assert_failed return;
322 fc = get_link_form_control(link);
323 assertm(fc, "link %d has no form control", (int) (link - doc_view->document->links));
324 if_assert_failed return;
326 fs = find_form_state(doc_view, fc);
327 if (!fs) return;
329 box = &doc_view->box;
330 vs = doc_view->vs;
331 dx = box->x - vs->x;
332 dy = box->y - vs->y;
333 switch (fc->type) {
334 unsigned char *s;
335 int len;
336 int i, x, y;
338 case FC_TEXT:
339 case FC_PASSWORD:
340 case FC_FILE:
341 int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
342 if (!link->npoints) break;
344 y = link->points[0].y + dy;
345 if (!row_is_in_box(box, y))
346 break;
348 len = strlen(fs->value) - fs->vpos;
349 x = link->points[0].x + dx;
351 for (i = 0; i < fc->size; i++, x++) {
352 unsigned char data;
354 if (!col_is_in_box(box, x)) continue;
356 if (fs->value && i >= -fs->vpos && i < len)
357 data = fc->type != FC_PASSWORD
358 ? fs->value[i + fs->vpos] : '*';
359 else
360 data = '_';
362 draw_char_data(term, x, y, data);
364 break;
365 case FC_TEXTAREA:
366 draw_textarea(term, fs, doc_view, link);
367 break;
368 case FC_CHECKBOX:
369 case FC_RADIO:
370 if (link->npoints < 2) break;
371 x = link->points[1].x + dx;
372 y = link->points[1].y + dy;
373 if (is_in_box(box, x, y))
374 draw_char_data(term, x, y, fs->state ? 'X' : ' ');
375 break;
376 case FC_SELECT:
377 fixup_select_state(fc, fs);
378 if (fs->state < fc->nvalues)
379 s = fc->labels[fs->state];
380 else
381 /* XXX: when can this happen? --pasky */
382 s = "";
383 len = s ? strlen(s) : 0;
384 for (i = 0; i < link->npoints; i++) {
385 x = link->points[i].x + dx;
386 y = link->points[i].y + dy;
387 if (is_in_box(box, x, y))
388 draw_char_data(term, x, y, i < len ? s[i] : '_');
390 break;
391 case FC_SUBMIT:
392 case FC_IMAGE:
393 case FC_RESET:
394 case FC_BUTTON:
395 case FC_HIDDEN:
396 break;
400 void
401 draw_forms(struct terminal *term, struct document_view *doc_view)
403 struct link *l1, *l2;
405 assert(term && doc_view);
406 if_assert_failed return;
408 l1 = get_first_link(doc_view);
409 l2 = get_last_link(doc_view);
411 if (!l1 || !l2) {
412 assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
413 /* Return path :-). */
414 return;
416 do {
417 struct form_control *fc = get_link_form_control(l1);
419 if (!fc) continue;
420 #ifdef CONFIG_FORMHIST
421 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD) {
422 unsigned char *value;
424 assert(fc->form);
425 value = get_form_history_value(fc->form->action, fc->name);
427 if (value)
428 mem_free_set(&fc->default_value,
429 stracpy(value));
431 #endif /* CONFIG_FORMHIST */
432 draw_form_entry(term, doc_view, l1);
434 } while (l1++ < l2);
438 void
439 done_submitted_value_list(struct list_head *list)
441 struct submitted_value *sv, *svtmp;
443 assert(list);
444 if_assert_failed return;
446 foreach (sv, *list) {
447 svtmp = sv;
448 sv = sv->prev;
449 del_from_list(svtmp);
450 done_submitted_value(svtmp);
454 static void
455 add_submitted_value_to_list(struct form_control *fc,
456 struct form_state *fs,
457 struct list_head *list)
459 struct submitted_value *sub;
460 unsigned char *name;
461 enum form_type type;
462 int position;
464 assert(fc && fs && list);
466 name = fc->name;
467 position = fc->position;
468 type = fc->type;
470 switch (fc->type) {
471 case FC_TEXT:
472 case FC_PASSWORD:
473 case FC_FILE:
474 case FC_TEXTAREA:
475 sub = init_submitted_value(name, fs->value, type, fc, position);
476 if (sub) add_to_list(*list, sub);
477 break;
479 case FC_CHECKBOX:
480 case FC_RADIO:
481 if (!fs->state) break;
482 /* fall through */
484 case FC_SUBMIT:
485 case FC_HIDDEN:
486 case FC_RESET:
487 case FC_BUTTON:
488 sub = init_submitted_value(name, fs->value, type, fc,
489 position);
490 if (sub) add_to_list(*list, sub);
491 break;
493 case FC_SELECT:
494 if (!fc->nvalues) break;
496 fixup_select_state(fc, fs);
497 sub = init_submitted_value(name, fs->value, type, fc, position);
498 if (sub) add_to_list(*list, sub);
499 break;
501 case FC_IMAGE:
502 name = straconcat(fc->name, ".x", NULL);
503 if (!name) break;
504 sub = init_submitted_value(name, "0", type, fc, position);
505 mem_free(name);
506 if (sub) add_to_list(*list, sub);
508 name = straconcat(fc->name, ".y", NULL);
509 if (!name) break;
510 sub = init_submitted_value(name, "0", type, fc, position);
511 mem_free(name);
512 if (sub) add_to_list(*list, sub);
514 break;
518 static void
519 sort_submitted_values(struct list_head *list)
521 while (1) {
522 struct submitted_value *sub;
523 int changed = 0;
525 foreach (sub, *list) if (list_has_next(*list, sub))
526 if (sub->next->position < sub->position) {
527 struct submitted_value *next = sub->next;
529 del_from_list(sub);
530 add_at_pos(next, sub);
531 sub = next;
532 changed = 1;
535 foreachback (sub, *list) if (list_has_next(*list, sub))
536 if (sub->next->position < sub->position) {
537 struct submitted_value *next = sub->next;
539 del_from_list(sub);
540 add_at_pos(next, sub);
541 sub = next;
542 changed = 1;
545 if (!changed) break;
549 static void
550 get_successful_controls(struct document_view *doc_view,
551 struct form_control *fc, struct list_head *list)
553 struct form_control *fc2;
555 assert(doc_view && fc && fc->form && list);
556 if_assert_failed return;
558 foreach (fc2, fc->form->items) {
559 if (((fc2->type != FC_SUBMIT &&
560 fc2->type != FC_IMAGE &&
561 fc2->type != FC_RESET &&
562 fc2->type != FC_BUTTON) || fc2 == fc)
563 && fc2->name && fc2->name[0]) {
564 struct form_state *fs = find_form_state(doc_view, fc2);
566 if (!fs) continue;
568 add_submitted_value_to_list(fc2, fs, list);
572 sort_submitted_values(list);
575 static void
576 encode_controls(struct list_head *l, struct string *data,
577 int cp_from, int cp_to)
579 struct submitted_value *sv;
580 struct conv_table *convert_table = NULL;
581 int lst = 0;
583 assert(l && data);
584 if_assert_failed return;
586 foreach (sv, *l) {
587 unsigned char *p2 = NULL;
589 if (lst)
590 add_char_to_string(data, '&');
591 else
592 lst = 1;
594 encode_uri_string(data, sv->name, strlen(sv->name), 1);
595 add_char_to_string(data, '=');
597 /* Convert back to original encoding (see html_form_control()
598 * for the original recoding). */
599 if (sv->type == FC_TEXTAREA) {
600 unsigned char *p;
602 p = encode_textarea(sv);
603 if (p) {
604 if (!convert_table)
605 convert_table = get_translation_table(cp_from, cp_to);
607 p2 = convert_string(convert_table, p,
608 strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
609 mem_free(p);
611 } else if (sv->type == FC_TEXT ||
612 sv->type == FC_PASSWORD) {
613 if (!convert_table)
614 convert_table = get_translation_table(cp_from, cp_to);
616 p2 = convert_string(convert_table, sv->value,
617 strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
618 } else {
619 p2 = stracpy(sv->value);
622 if (p2) {
623 encode_uri_string(data, p2, strlen(p2), 1);
624 mem_free(p2);
631 #define BOUNDARY_LENGTH 32
632 #define realloc_bound_ptrs(bptrs, bptrs_size) \
633 mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, int, 0xFF)
635 struct boundary_info {
636 int count;
637 int *offsets;
638 unsigned char string[BOUNDARY_LENGTH];
641 static inline void
642 init_boundary(struct boundary_info *boundary)
644 memset(boundary, 0, sizeof(*boundary));
645 memset(boundary->string, '0', BOUNDARY_LENGTH);
648 /* Add boundary to string and save the offset */
649 static inline void
650 add_boundary(struct string *data, struct boundary_info *boundary)
652 add_to_string(data, "--");
654 if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
655 boundary->offsets[boundary->count++] = data->length;
657 add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
660 static inline unsigned char *
661 increment_boundary_counter(struct boundary_info *boundary)
663 int j;
665 /* This is just a decimal string incrementation */
666 for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
667 if (boundary->string[j]++ < '9')
668 return boundary->string;
670 boundary->string[j] = '0';
673 INTERNAL("Form data boundary counter overflow");
675 return NULL;
678 static inline void
679 check_boundary(struct string *data, struct boundary_info *boundary)
681 unsigned char *bound = boundary->string;
682 int i;
684 /* Search between all boundaries. There is a starting and an ending
685 * boundary so only check the range of chars after the current offset
686 * and before the next offset. If some string in the form data matches
687 * the boundary string it is changed. */
688 for (i = 0; i < boundary->count - 1; i++) {
689 /* Start after the boundary string and also jump past the
690 * "\r\nContent-Disposition: form-data; name=\"" string added
691 * before any form data. */
692 int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
694 /* End so that there is atleast BOUNDARY_LENGTH chars to
695 * compare. Subtract 2 char because there is no need to also
696 * compare the '--' prefix that is part of the boundary. */
697 int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
698 unsigned char *pos = data->source + start_offset;
699 unsigned char *end = data->source + end_offset;
701 for (; pos <= end; pos++) {
702 if (memcmp(pos, bound, BOUNDARY_LENGTH))
703 continue;
705 /* If incrementing causes overflow bail out. There is
706 * no need to reset the boundary string with '0' since
707 * that is already done when incrementing. */
708 if (!increment_boundary_counter(boundary))
709 return;
711 /* Else start checking all boundaries using the new
712 * boundary string */
713 i = 0;
714 break;
718 /* Now update all the boundaries with the unique boundary string */
719 for (i = 0; i < boundary->count; i++)
720 memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
723 /* FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
724 static void
725 encode_multipart(struct session *ses, struct list_head *l, struct string *data,
726 struct boundary_info *boundary, int cp_from, int cp_to)
728 struct conv_table *convert_table = NULL;
729 struct submitted_value *sv;
731 assert(ses && l && data && boundary);
732 if_assert_failed return;
734 init_boundary(boundary);
736 foreach (sv, *l) {
737 add_boundary(data, boundary);
738 add_crlf_to_string(data);
740 /* FIXME: name is not encoded.
741 * from RFC 1867:
742 * multipart/form-data contains a series of parts.
743 * Each part is expected to contain a content-disposition
744 * header where the value is "form-data" and a name attribute
745 * specifies the field name within the form,
746 * e.g., 'content-disposition: form-data; name="xxxxx"',
747 * where xxxxx is the field name corresponding to that field.
748 * Field names originally in non-ASCII character sets may be
749 * encoded using the method outlined in RFC 1522. */
750 add_to_string(data, "Content-Disposition: form-data; name=\"");
751 add_to_string(data, sv->name);
752 add_char_to_string(data, '"');
754 if (sv->type == FC_FILE) {
755 #define F_BUFLEN 1024
756 int fh;
757 unsigned char buffer[F_BUFLEN];
758 unsigned char *extension;
760 add_to_string(data, "; filename=\"");
761 add_to_string(data, get_filename_position(sv->value));
762 /* It sends bad data if the file name contains ", but
763 Netscape does the same */
764 /* FIXME: We should follow RFCs 1522, 1867,
765 * 2047 (updated by rfc 2231), to provide correct support
766 * for non-ASCII and special characters in values. --Zas */
767 add_char_to_string(data, '"');
769 /* Add a Content-Type header if the type is configured */
770 extension = strrchr(sv->value, '.');
771 if (extension) {
772 unsigned char *type = get_extension_content_type(extension);
774 if (type) {
775 add_crlf_to_string(data);
776 add_to_string(data, "Content-Type: ");
777 add_to_string(data, type);
778 mem_free(type);
782 add_crlf_to_string(data);
783 add_crlf_to_string(data);
785 if (*sv->value) {
786 unsigned char *filename;
788 if (get_cmd_opt_bool("anonymous")) {
789 errno = EPERM;
790 goto encode_error;
793 /* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
794 filename = expand_tilde(sv->value);
795 if (!filename) goto encode_error;
797 fh = open(filename, O_RDONLY);
798 mem_free(filename);
800 if (fh == -1) goto encode_error;
801 set_bin(fh);
802 while (1) {
803 ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
805 if (rd) {
806 if (rd == -1) {
807 close(fh);
808 goto encode_error;
811 add_bytes_to_string(data, buffer, rd);
813 } else {
814 break;
817 close(fh);
819 #undef F_BUFLEN
820 } else {
821 add_crlf_to_string(data);
822 add_crlf_to_string(data);
824 /* Convert back to original encoding (see
825 * html_form_control() for the original recoding). */
826 if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
827 sv->type == FC_TEXTAREA) {
828 unsigned char *p;
830 if (!convert_table)
831 convert_table = get_translation_table(cp_from,
832 cp_to);
834 p = convert_string(convert_table, sv->value,
835 strlen(sv->value), -1, CSM_FORM, NULL,
836 NULL, NULL);
837 if (p) {
838 add_to_string(data, p);
839 mem_free(p);
841 } else {
842 add_to_string(data, sv->value);
846 add_crlf_to_string(data);
849 /* End-boundary */
850 add_boundary(data, boundary);
851 add_to_string(data, "--\r\n");
853 check_boundary(data, boundary);
855 mem_free_if(boundary->offsets);
856 return;
858 encode_error:
859 mem_free_if(boundary->offsets);
860 done_string(data);
862 /* XXX: This error message should move elsewhere. --Zas */
863 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
864 N_("Error while posting form"), ALIGN_CENTER,
865 msg_text(ses->tab->term, N_("Could not load file %s: %s"),
866 sv->value, strerror(errno)));
869 static void
870 encode_newlines(struct string *string, unsigned char *data)
872 for (; *data; data++) {
873 if (*data == '\n' || *data == '\r') {
874 unsigned char buffer[3];
876 /* Hex it. */
877 buffer[0] = '%';
878 buffer[1] = hx((((int) *data) & 0xF0) >> 4);
879 buffer[2] = hx(((int) *data) & 0xF);
880 add_bytes_to_string(string, buffer, 3);
881 } else {
882 add_char_to_string(string, *data);
887 static void
888 encode_text_plain(struct list_head *l, struct string *data,
889 int cp_from, int cp_to)
891 struct submitted_value *sv;
892 struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
894 assert(l && data);
895 if_assert_failed return;
897 foreach (sv, *l) {
898 unsigned char *area51 = NULL;
899 unsigned char *value = sv->value;
901 add_to_string(data, sv->name);
902 add_char_to_string(data, '=');
904 switch (sv->type) {
905 case FC_TEXTAREA:
906 value = area51 = encode_textarea(sv);
907 if (!area51) break;
908 /* Fall through */
909 case FC_TEXT:
910 case FC_PASSWORD:
911 /* Convert back to original encoding (see
912 * html_form_control() for the original recoding). */
913 value = convert_string(convert_table, value,
914 strlen(value), -1, CSM_FORM,
915 NULL, NULL, NULL);
916 default:
917 /* Falling right through to free that textarea stuff */
918 mem_free_if(area51);
920 /* Did the conversion fail? */
921 if (!value) break;
923 encode_newlines(data, value);
925 /* Free if we did convert something */
926 if (value != sv->value) mem_free(value);
929 add_crlf_to_string(data);
933 void
934 do_reset_form(struct document_view *doc_view, struct form *form)
936 struct form_control *fc;
938 assert(doc_view && doc_view->document);
939 if_assert_failed return;
941 foreach (fc, form->items) {
942 struct form_state *fs = find_form_state(doc_view, fc);
944 if (fs) init_form_state(fc, fs);
948 enum frame_event_status
949 reset_form(struct session *ses, struct document_view *doc_view, int a)
951 struct link *link = get_current_link(doc_view);
953 if (!link) return FRAME_EVENT_OK;
955 do_reset_form(doc_view, get_link_form_control(link)->form);
956 draw_forms(ses->tab->term, doc_view);
958 /* Could be the refresh return value and then ditch the draw_forms()
959 * call. */
960 return FRAME_EVENT_OK;
963 struct uri *
964 get_form_uri(struct session *ses, struct document_view *doc_view,
965 struct form_control *fc)
967 struct boundary_info boundary;
968 INIT_LIST_HEAD(submit);
969 struct string data;
970 struct string go;
971 int cp_from, cp_to;
972 struct uri *uri;
973 struct form *form;
975 assert(ses && ses->tab && ses->tab->term);
976 if_assert_failed return NULL;
977 assert(doc_view && doc_view->document && fc && fc->form);
978 if_assert_failed return NULL;
980 form = fc->form;
982 if (fc->type == FC_RESET) {
983 do_reset_form(doc_view, form);
984 return NULL;
985 } else if (fc->type == FC_BUTTON) {
986 return NULL;
989 if (!form->action
990 || !init_string(&data))
991 return NULL;
993 get_successful_controls(doc_view, fc, &submit);
995 cp_from = get_opt_codepage_tree(ses->tab->term->spec, "charset");
996 cp_to = doc_view->document->cp;
997 switch (form->method) {
998 case FORM_METHOD_GET:
999 case FORM_METHOD_POST:
1000 encode_controls(&submit, &data, cp_from, cp_to);
1001 break;
1003 case FORM_METHOD_POST_MP:
1004 encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
1005 break;
1007 case FORM_METHOD_POST_TEXT_PLAIN:
1008 encode_text_plain(&submit, &data, cp_from, cp_to);
1011 #ifdef CONFIG_FORMHIST
1012 /* XXX: We check data.source here because a NULL value can indicate
1013 * not only a memory allocation failure, but also an error reading
1014 * a file that is to be uploaded. TODO: Distinguish between
1015 * these two classes of errors (is it worth it?). -- Miciah */
1016 if (data.source
1017 && get_opt_bool("document.browse.forms.show_formhist"))
1018 memorize_form(ses, &submit, form);
1019 #endif
1021 done_submitted_value_list(&submit);
1023 if (!data.source
1024 || !init_string(&go)) {
1025 done_string(&data);
1026 return NULL;
1029 switch (form->method) {
1030 case FORM_METHOD_GET:
1032 unsigned char *pos = strchr(form->action, '#');
1034 if (pos) {
1035 add_bytes_to_string(&go, form->action, pos - form->action);
1036 } else {
1037 add_to_string(&go, form->action);
1040 if (strchr(go.source, '?'))
1041 add_char_to_string(&go, '&');
1042 else
1043 add_char_to_string(&go, '?');
1045 add_string_to_string(&go, &data);
1047 if (pos) add_to_string(&go, pos);
1048 break;
1050 case FORM_METHOD_POST:
1051 case FORM_METHOD_POST_MP:
1052 case FORM_METHOD_POST_TEXT_PLAIN:
1054 /* Note that we end content type here by a simple '\n',
1055 * replaced later by correct '\r\n' in http_send_header(). */
1056 int i;
1058 add_to_string(&go, form->action);
1059 add_char_to_string(&go, POST_CHAR);
1060 if (form->method == FORM_METHOD_POST) {
1061 add_to_string(&go, "application/x-www-form-urlencoded\n");
1063 } else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
1064 /* Dunno about this one but we don't want the full
1065 * hextcat thingy. --jonas */
1066 add_to_string(&go, "text/plain\n");
1067 add_to_string(&go, data.source);
1068 break;
1070 } else {
1071 add_to_string(&go, "multipart/form-data; boundary=");
1072 add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
1073 add_char_to_string(&go, '\n');
1076 for (i = 0; i < data.length; i++) {
1077 unsigned char p[3];
1079 ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
1080 add_to_string(&go, p);
1085 done_string(&data);
1087 uri = get_uri(go.source, 0);
1088 done_string(&go);
1089 if (uri) uri->form = 1;
1091 return uri;
1094 #undef BOUNDARY_LENGTH
1097 enum frame_event_status
1098 submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
1100 goto_current_link(ses, doc_view, do_reload);
1101 return FRAME_EVENT_OK;
1104 void
1105 submit_given_form(struct session *ses, struct document_view *doc_view,
1106 struct form *form, int do_reload)
1108 /* Added support for submitting forms in hidden
1109 * links in 1.285, commented code can safely be removed once we have made sure the new
1110 * code does the right thing. */
1111 #if 0
1113 struct document *document = doc_view->document;
1114 int link;
1116 for (link = 0; link < document->nlinks; link++) {
1117 struct form_control *fc = get_link_form_control(&document->links[link]);
1119 if (fc && fc->form == form) {
1120 doc_view->vs->current_link = link;
1121 submit_form(ses, doc_view, 0);
1122 return;
1125 #endif
1126 if (!list_empty(form->items)) {
1127 struct form_control *fc = (struct form_control *)form->items.next;
1128 struct uri *uri;
1129 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
1131 if (!fc) return;
1132 uri = get_form_uri(ses, doc_view, fc);
1133 if (!uri) return;
1134 goto_uri_frame(ses, uri, form->target, mode);
1135 done_uri(uri);
1139 void
1140 auto_submit_form(struct session *ses)
1142 struct document *document = ses->doc_view->document;
1144 if (!list_empty(document->forms))
1145 submit_given_form(ses, ses->doc_view, document->forms.next, 0);
1149 /* menu_func_T */
1150 static void
1151 set_file_form_state(struct terminal *term, void *filename_, void *fs_)
1153 unsigned char *filename = filename_;
1154 struct form_state *fs = fs_;
1156 /* The menu code doesn't free the filename data */
1157 mem_free_set(&fs->value, filename);
1158 fs->state = strlen(filename);
1159 redraw_terminal(term);
1162 /* menu_func_T */
1163 static void
1164 file_form_menu(struct terminal *term, void *path_, void *fs_)
1166 unsigned char *path = path_;
1167 struct form_state *fs = fs_;
1169 /* FIXME: It doesn't work for ../../ */
1170 #if 0
1171 int valuelen = strlen(fs->value);
1172 int pathlen = strlen(path);
1173 int no_elevator = 0;
1175 /* Don't add elevators for subdirs menus */
1176 /* It is not perfect at all because fs->value is not updated for each
1177 * newly opened file menu. Maybe it should be dropped. */
1178 for (; valuelen < pathlen; valuelen++) {
1179 if (dir_sep(path[valuelen - 1])) {
1180 no_elevator = 1;
1181 break;
1184 #endif
1186 auto_complete_file(term, 0 /* no_elevator */, path,
1187 set_file_form_state,
1188 file_form_menu, fs);
1192 enum frame_event_status
1193 field_op(struct session *ses, struct document_view *doc_view,
1194 struct link *link, struct term_event *ev)
1196 struct form_control *fc;
1197 struct form_state *fs;
1198 enum edit_action action_id;
1199 unsigned char *text;
1200 int length;
1201 enum frame_event_status status = FRAME_EVENT_REFRESH;
1203 assert(ses && doc_view && link && ev);
1204 if_assert_failed return FRAME_EVENT_OK;
1206 fc = get_link_form_control(link);
1207 assertm(fc, "link has no form control");
1208 if_assert_failed return FRAME_EVENT_OK;
1210 if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
1211 || ses->insert_mode == INSERT_MODE_OFF)
1212 return FRAME_EVENT_IGNORED;
1214 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
1216 fs = find_form_state(doc_view, fc);
1217 if (!fs || !fs->value) return FRAME_EVENT_OK;
1219 switch (action_id) {
1220 case ACT_EDIT_LEFT:
1221 fs->state = int_max(fs->state - 1, 0);
1222 break;
1223 case ACT_EDIT_RIGHT:
1224 fs->state = int_min(fs->state + 1, strlen(fs->value));
1225 break;
1226 case ACT_EDIT_HOME:
1227 if (fc->type == FC_TEXTAREA) {
1228 status = textarea_op_home(fs, fc);
1229 } else {
1230 fs->state = 0;
1232 break;
1233 case ACT_EDIT_UP:
1234 if (fc->type != FC_TEXTAREA)
1235 status = FRAME_EVENT_IGNORED;
1236 else
1237 status = textarea_op_up(fs, fc);
1238 break;
1239 case ACT_EDIT_DOWN:
1240 if (fc->type != FC_TEXTAREA)
1241 status = FRAME_EVENT_IGNORED;
1242 else
1243 status = textarea_op_down(fs, fc);
1244 break;
1245 case ACT_EDIT_END:
1246 if (fc->type == FC_TEXTAREA) {
1247 status = textarea_op_end(fs, fc);
1248 } else {
1249 fs->state = strlen(fs->value);
1251 break;
1252 case ACT_EDIT_BEGINNING_OF_BUFFER:
1253 if (fc->type == FC_TEXTAREA) {
1254 status = textarea_op_bob(fs, fc);
1255 } else {
1256 fs->state = 0;
1258 break;
1259 case ACT_EDIT_END_OF_BUFFER:
1260 if (fc->type == FC_TEXTAREA) {
1261 status = textarea_op_eob(fs, fc);
1262 } else {
1263 fs->state = strlen(fs->value);
1265 break;
1266 case ACT_EDIT_OPEN_EXTERNAL:
1267 if (form_field_is_readonly(fc))
1268 status = FRAME_EVENT_IGNORED;
1269 else if (fc->type == FC_TEXTAREA)
1270 textarea_edit(0, ses->tab->term, fs, doc_view, link);
1271 break;
1272 case ACT_EDIT_COPY_CLIPBOARD:
1273 set_clipboard_text(fs->value);
1274 status = FRAME_EVENT_OK;
1275 break;
1276 case ACT_EDIT_CUT_CLIPBOARD:
1277 set_clipboard_text(fs->value);
1278 if (!form_field_is_readonly(fc))
1279 fs->value[0] = 0;
1280 fs->state = 0;
1281 break;
1282 case ACT_EDIT_PASTE_CLIPBOARD:
1283 if (form_field_is_readonly(fc)) break;
1285 text = get_clipboard_text();
1286 if (!text) break;
1288 length = strlen(text);
1289 if (length <= fc->maxlength) {
1290 unsigned char *v = mem_realloc(fs->value, length + 1);
1292 if (v) {
1293 fs->value = v;
1294 memmove(v, text, length + 1);
1295 fs->state = strlen(fs->value);
1298 mem_free(text);
1299 break;
1300 case ACT_EDIT_ENTER:
1301 if (fc->type == FC_TEXTAREA) {
1302 status = textarea_op_enter(fs, fc);
1303 break;
1306 /* Set status to ok if either it is not possible to
1307 * submit the form or the posting fails. */
1308 /* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
1309 if ((has_form_submit(fc->form)
1310 && !get_opt_bool("document.browse.forms.auto_submit"))
1311 || goto_current_link(ses, doc_view, 0)) {
1312 if (ses->insert_mode == INSERT_MODE_ON)
1313 ses->insert_mode = INSERT_MODE_OFF;
1314 status = FRAME_EVENT_OK;
1316 break;
1317 case ACT_EDIT_BACKSPACE:
1318 if (form_field_is_readonly(fc)) {
1319 status = FRAME_EVENT_IGNORED;
1320 break;
1323 if (!fs->state) {
1324 status = FRAME_EVENT_OK;
1325 break;
1328 length = strlen(fs->value + fs->state) + 1;
1329 text = fs->value + fs->state;
1331 memmove(text - 1, text, length);
1332 fs->state--;
1333 break;
1334 case ACT_EDIT_DELETE:
1335 if (form_field_is_readonly(fc)) {
1336 status = FRAME_EVENT_IGNORED;
1337 break;
1340 length = strlen(fs->value);
1341 if (fs->state >= length) {
1342 status = FRAME_EVENT_OK;
1343 break;
1346 text = fs->value + fs->state;
1348 memmove(text, text + 1, length - fs->state);
1349 break;
1350 case ACT_EDIT_KILL_TO_BOL:
1351 if (form_field_is_readonly(fc)) {
1352 status = FRAME_EVENT_IGNORED;
1353 break;
1356 if (fs->state <= 0) {
1357 status = FRAME_EVENT_OK;
1358 break;
1361 text = memrchr(fs->value, ASCII_LF, fs->state);
1362 if (text) {
1363 /* Leave the new-line character if it does not
1364 * immediately precede the cursor. */
1365 if (text != &fs->value[fs->state - 1])
1366 text++;
1367 } else {
1368 text = fs->value;
1371 length = strlen(fs->value + fs->state) + 1;
1372 memmove(text, fs->value + fs->state, length);
1374 fs->state = (int) (text - fs->value);
1375 break;
1376 case ACT_EDIT_KILL_TO_EOL:
1377 if (form_field_is_readonly(fc)) {
1378 status = FRAME_EVENT_IGNORED;
1379 break;
1382 if (!fs->value[fs->state]) {
1383 status = FRAME_EVENT_OK;
1384 break;
1387 text = strchr(fs->value + fs->state, ASCII_LF);
1388 if (!text) {
1389 fs->value[fs->state] = '\0';
1390 break;
1393 if (fs->value[fs->state] == ASCII_LF)
1394 ++text;
1396 memmove(fs->value + fs->state, text, strlen(text) + 1);
1397 break;
1399 case ACT_EDIT_AUTO_COMPLETE:
1400 if (fc->type != FC_FILE
1401 || form_field_is_readonly(fc)) {
1402 status = FRAME_EVENT_IGNORED;
1403 break;
1406 file_form_menu(ses->tab->term, fs->value, fs);
1407 break;
1409 case ACT_EDIT_CANCEL:
1410 if (ses->insert_mode == INSERT_MODE_ON)
1411 ses->insert_mode = INSERT_MODE_OFF;
1412 else
1413 status = FRAME_EVENT_IGNORED;
1414 break;
1416 case ACT_EDIT_REDRAW:
1417 redraw_terminal_cls(ses->tab->term);
1418 status = FRAME_EVENT_OK;
1419 break;
1421 default:
1422 if (!check_kbd_textinput_key(ev)) {
1423 status = FRAME_EVENT_IGNORED;
1424 break;
1427 if (form_field_is_readonly(fc)
1428 || strlen(fs->value) >= fc->maxlength
1429 || !insert_in_string(&fs->value, fs->state, "?", 1)) {
1430 status = FRAME_EVENT_OK;
1431 break;
1434 fs->value[fs->state++] = get_kbd_key(ev);
1435 break;
1438 return status;
1441 unsigned char *
1442 get_form_label(struct form_control *fc)
1444 assert(fc->form);
1445 switch (fc->type) {
1446 case FC_RESET:
1447 return N_("Reset form");
1448 case FC_BUTTON:
1449 return N_("Harmless button");
1450 case FC_HIDDEN:
1451 return NULL;
1452 case FC_SUBMIT:
1453 case FC_IMAGE:
1454 if (!fc->form->action) return NULL;
1456 if (fc->form->method == FORM_METHOD_GET)
1457 return N_("Submit form to");
1458 return N_("Post form to");
1459 case FC_RADIO:
1460 return N_("Radio button");
1461 case FC_CHECKBOX:
1462 return N_("Checkbox");
1463 case FC_SELECT:
1464 return N_("Select field");
1465 case FC_TEXT:
1466 return N_("Text field");
1467 case FC_TEXTAREA:
1468 return N_("Text area");
1469 case FC_FILE:
1470 return N_("File upload");
1471 case FC_PASSWORD:
1472 return N_("Password field");
1475 return NULL;
1478 static inline void
1479 add_form_attr_to_string(struct string *string, struct terminal *term,
1480 unsigned char *name, unsigned char *value)
1482 add_to_string(string, ", ");
1483 add_to_string(string, _(name, term));
1484 if (value) {
1485 add_char_to_string(string, ' ');
1486 add_to_string(string, value);
1490 unsigned char *
1491 get_form_info(struct session *ses, struct document_view *doc_view)
1493 struct terminal *term = ses->tab->term;
1494 struct link *link = get_current_link(doc_view);
1495 struct form_control *fc;
1496 unsigned char *label, *key;
1497 struct string str;
1499 assert(link);
1501 fc = get_link_form_control(link);
1502 label = get_form_label(fc);
1503 if (!label) return NULL;
1505 if (!init_string(&str)) return NULL;
1507 add_to_string(&str, _(label, term));
1509 if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
1510 add_form_attr_to_string(&str, term, N_("name"), fc->name);
1513 switch (fc->type) {
1514 case FC_CHECKBOX:
1515 case FC_RADIO:
1517 struct form_state *fs = find_form_state(doc_view, fc);
1519 if (!fs->value || !fs->value[0])
1520 break;
1522 add_form_attr_to_string(&str, term, N_("value"), fs->value);
1523 break;
1526 case FC_TEXT:
1527 case FC_PASSWORD:
1528 case FC_FILE:
1529 case FC_TEXTAREA:
1531 struct uri *uri;
1532 unsigned char *uristring;
1534 if (form_field_is_readonly(fc)) {
1535 add_form_attr_to_string(&str, term, N_("read only"), NULL);
1538 /* Should we add info about entering insert mode or add info
1539 * about submitting the form? */
1540 if (ses->insert_mode == INSERT_MODE_OFF) {
1541 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1543 if (!key) break;
1545 if (form_field_is_readonly(fc))
1546 label = N_("press %s to navigate");
1547 else
1548 label = N_("press %s to edit");
1550 add_to_string(&str, " (");
1551 add_format_to_string(&str, _(label, term), key);
1552 add_char_to_string(&str, ')');
1553 mem_free(key);
1554 break;
1558 if (fc->type == FC_TEXTAREA)
1559 break;
1561 assert(fc->form);
1563 if (!fc->form->action
1564 || (has_form_submit(fc->form)
1565 && !get_opt_bool("document.browse.forms.auto_submit")))
1566 break;
1568 uri = get_uri(fc->form->action, 0);
1569 if (!uri) break;
1571 /* Add the uri with password and post info stripped */
1572 uristring = get_uri_string(uri, URI_PUBLIC);
1573 done_uri(uri);
1575 if (!uristring) break;
1577 key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
1578 if (!key) {
1579 mem_free(uristring);
1580 break;
1583 if (fc->form->method == FORM_METHOD_GET)
1584 label = N_("press %s to submit to %s");
1585 else
1586 label = N_("press %s to post to %s");
1588 add_to_string(&str, " (");
1589 add_format_to_string(&str, _(label, term), key, uristring);
1590 mem_free(uristring);
1591 mem_free(key);
1593 add_char_to_string(&str, ')');
1594 break;
1596 case FC_SUBMIT:
1597 case FC_IMAGE:
1598 add_char_to_string(&str, ' ');
1600 assert(fc->form);
1601 /* Add the uri with password and post info stripped */
1602 add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
1603 break;
1605 case FC_HIDDEN:
1606 case FC_RESET:
1607 case FC_BUTTON:
1608 case FC_SELECT:
1609 break;
1612 if (link->accesskey
1613 && get_opt_bool("document.browse.accesskey.display")) {
1614 add_to_string(&str, " (");
1615 add_accesskey_to_string(&str, link->accesskey);
1616 add_char_to_string(&str, ')');
1619 return str.source;
1622 static void
1623 link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
1625 struct session *ses = ses_;
1626 struct document_view *doc_view;
1627 int link_number = *(int *) link_number_;
1629 mem_free(link_number_);
1631 assert(term && ses);
1632 if_assert_failed return;
1634 doc_view = current_frame(ses);
1635 if (!doc_view) return;
1637 assert(doc_view->vs && doc_view->document);
1638 if_assert_failed return;
1640 jump_to_link_number(ses, doc_view, link_number);
1641 refresh_view(ses, doc_view, 0);
1644 void
1645 link_form_menu(struct session *ses)
1647 struct document_view *doc_view;
1648 struct link *link;
1649 struct menu_item *mi;
1650 struct form_control *fc;
1651 struct form *form;
1653 assert(ses);
1654 if_assert_failed return;
1656 doc_view = current_frame(ses);
1657 if (!doc_view) return;
1659 assert(doc_view->vs && doc_view->document);
1660 if_assert_failed return;
1662 link = get_current_link(doc_view);
1663 if (!link) return;
1665 assert(link_is_form(link));
1667 fc = get_link_form_control(link);
1668 if (!fc) return;
1670 form = fc->form;
1672 mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
1673 if (!mi) return;
1675 foreach (fc, form->items) {
1676 unsigned char *text;
1677 unsigned char *rtext;
1678 int link_number;
1679 struct string str;
1681 switch (fc->type) {
1682 case FC_HIDDEN:
1683 continue;
1685 case FC_SUBMIT:
1686 case FC_IMAGE:
1687 if (!form->action)
1688 text = N_("Useless button");
1689 else
1690 text = N_("Submit button");
1691 break;
1693 default:
1694 text = get_form_label(fc);
1697 link_number = get_form_control_link(doc_view->document, fc);
1698 if (link_number < 0
1699 || !init_string(&str))
1700 continue;
1702 assert(text);
1703 add_to_string(&str, _(text, ses->tab->term));
1705 rtext = fc->name;
1706 if (!rtext) rtext = fc->alt;
1708 add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
1709 link_form_menu_func, intdup(link_number),
1710 FREE_DATA);
1713 do_menu(ses->tab->term, mi, ses, 1);