Handling onsubmit
[elinks.git] / src / document / html / parser / forms.c
blob6e63f96d0fb8c486a7ddeb4b350bd8baec111c43
1 /* HTML forms parser */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
12 #include "elinks.h"
14 #include "bfu/listmenu.h"
15 #include "bfu/menu.h"
16 #include "document/html/parser/forms.h"
17 #include "document/html/parser/link.h"
18 #include "document/html/parser/stack.h"
19 #include "document/html/parser/parse.h"
20 #include "document/html/parser.h"
21 #include "document/html/renderer.h"
22 #include "document/forms.h"
23 #include "intl/charsets.h"
24 #include "protocol/protocol.h"
25 #include "protocol/uri.h"
26 #include "util/conv.h"
27 #include "util/error.h"
28 #include "util/memdebug.h"
29 #include "util/memlist.h"
30 #include "util/memory.h"
31 #include "util/string.h"
33 /* Unsafe macros */
34 #include "document/html/internal.h"
38 void
39 html_form(struct html_context *html_context, unsigned char *a,
40 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
42 unsigned char *al;
43 struct form *form;
45 html_context->was_br = 1;
47 form = init_form();
48 if (!form) return;
50 form->method = FORM_METHOD_GET;
51 form->form_num = a - html_context->startf;
53 al = get_attr_val(a, "method", html_context->options);
54 if (al) {
55 if (!strcasecmp(al, "post")) {
56 unsigned char *enctype;
58 enctype = get_attr_val(a, "enctype",
59 html_context->options);
61 form->method = FORM_METHOD_POST;
62 if (enctype) {
63 if (!strcasecmp(enctype, "multipart/form-data"))
64 form->method = FORM_METHOD_POST_MP;
65 else if (!strcasecmp(enctype, "text/plain"))
66 form->method = FORM_METHOD_POST_TEXT_PLAIN;
67 mem_free(enctype);
70 mem_free(al);
72 form->onsubmit = get_attr_val(a, "onsubmit", html_context->options);
73 al = get_attr_val(a, "name", html_context->options);
74 if (al) form->name = al;
76 al = get_attr_val(a, "action", html_context->options);
77 /* The HTML specification at
78 * http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.3 states
79 * that the behavior of an empty action attribute should be undefined.
80 * Mozilla handles action="" as action="<current-URI>" which seems
81 * reasonable. (bug 615) */
82 if (al && *al) {
83 form->action = join_urls(html_context->base_href, trim_chars(al, ' ', NULL));
84 mem_free(al);
86 } else {
87 enum uri_component components = URI_ORIGINAL;
89 mem_free_if(al);
91 /* We have to do following for GET method, because we would end
92 * up with two '?' otherwise. */
93 if (form->method == FORM_METHOD_GET)
94 components = URI_FORM_GET;
96 form->action = get_uri_string(html_context->base_href, components);
98 /* No action URI should contain post data */
99 assert(!form->action || !strchr(form->action, POST_CHAR));
101 /* GET method URIs should not have '?'. */
102 assert(!form->action
103 || form->method != FORM_METHOD_GET
104 || !strchr(form->action, '?'));
107 al = get_target(html_context->options, a);
108 form->target = al ? al : stracpy(html_context->base_target);
110 html_context->special_f(html_context, SP_FORM, form);
114 static int
115 get_form_mode(struct html_context *html_context, unsigned char *attr)
117 if (has_attr(attr, "disabled", html_context->options))
118 return FORM_MODE_DISABLED;
120 if (has_attr(attr, "readonly", html_context->options))
121 return FORM_MODE_READONLY;
123 return FORM_MODE_NORMAL;
126 static struct form_control *
127 init_form_control(enum form_type type, unsigned char *attr,
128 struct html_context *html_context)
130 struct form_control *fc;
132 fc = mem_calloc(1, sizeof(*fc));
133 if (!fc) return NULL;
135 fc->type = type;
136 fc->position = attr - html_context->startf;
137 fc->mode = get_form_mode(html_context, attr);
139 return fc;
142 void
143 html_button(struct html_context *html_context, unsigned char *a,
144 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
146 unsigned char *al;
147 struct form_control *fc;
148 enum form_type type = FC_SUBMIT;
150 html_focusable(html_context, a);
152 al = get_attr_val(a, "type", html_context->options);
153 if (!al) goto no_type_attr;
155 if (!strcasecmp(al, "button")) {
156 type = FC_BUTTON;
157 } else if (!strcasecmp(al, "reset")) {
158 type = FC_RESET;
159 } else if (strcasecmp(al, "submit")) {
160 /* unknown type */
161 mem_free(al);
162 return;
164 mem_free(al);
166 no_type_attr:
167 fc = init_form_control(type, a, html_context);
168 if (!fc) return;
170 fc->name = get_attr_val(a, "name", html_context->options);
171 fc->default_value = get_attr_val(a, "value", html_context->options);
172 if (!fc->default_value) {
173 if (fc->type == FC_SUBMIT)
174 fc->default_value = stracpy("Submit");
175 else if (fc->type == FC_RESET)
176 fc->default_value = stracpy("Reset");
177 else if (fc->type == FC_BUTTON)
178 fc->default_value = stracpy("Button");
180 if (!fc->default_value)
181 fc->default_value = stracpy("");
183 html_context->special_f(html_context, SP_CONTROL, fc);
184 format.form = fc;
185 format.style.attr |= AT_BOLD;
188 static void
189 html_input_format(struct html_context *html_context, unsigned char *a,
190 struct form_control *fc)
192 put_chrs(html_context, " ", 1);
193 html_stack_dup(html_context, ELEMENT_KILLABLE);
194 html_focusable(html_context, a);
195 format.form = fc;
196 if (format.title) mem_free(format.title);
197 format.title = get_attr_val(a, "title", html_context->options);
198 switch (fc->type) {
199 case FC_TEXT:
200 case FC_PASSWORD:
201 case FC_FILE:
203 int i;
205 format.style.attr |= AT_BOLD;
206 for (i = 0; i < fc->size; i++)
207 put_chrs(html_context, "_", 1);
208 break;
210 case FC_CHECKBOX:
211 format.style.attr |= AT_BOLD;
212 put_chrs(html_context, "[&nbsp;]", 8);
213 break;
214 case FC_RADIO:
215 format.style.attr |= AT_BOLD;
216 put_chrs(html_context, "(&nbsp;)", 8);
217 break;
218 case FC_IMAGE:
220 unsigned char *al;
222 mem_free_set(&format.image, NULL);
223 al = get_url_val(a, "src", html_context->options);
224 if (!al)
225 al = get_url_val(a, "dynsrc",
226 html_context->options);
227 if (al) {
228 format.image = join_urls(html_context->base_href, al);
229 mem_free(al);
231 format.style.attr |= AT_BOLD;
232 put_chrs(html_context, "[&nbsp;", 7);
233 if (fc->alt)
234 put_chrs(html_context, fc->alt, strlen(fc->alt));
235 else if (fc->name)
236 put_chrs(html_context, fc->name, strlen(fc->name));
237 else
238 put_chrs(html_context, "Submit", 6);
240 put_chrs(html_context, "&nbsp;]", 7);
241 break;
243 case FC_SUBMIT:
244 case FC_RESET:
245 case FC_BUTTON:
246 format.style.attr |= AT_BOLD;
247 put_chrs(html_context, "[&nbsp;", 7);
248 if (fc->default_value)
249 put_chrs(html_context, fc->default_value, strlen(fc->default_value));
250 put_chrs(html_context, "&nbsp;]", 7);
251 break;
252 case FC_TEXTAREA:
253 case FC_SELECT:
254 case FC_HIDDEN:
255 INTERNAL("bad control type");
257 pop_html_element(html_context);
258 put_chrs(html_context, " ", 1);
261 void
262 html_input(struct html_context *html_context, unsigned char *a,
263 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
265 unsigned char *al;
266 struct form_control *fc;
268 fc = init_form_control(FC_TEXT, a, html_context);
269 if (!fc) return;
271 al = get_attr_val(a, "type", html_context->options);
272 if (al) {
273 if (!strcasecmp(al, "text")) fc->type = FC_TEXT;
274 else if (!strcasecmp(al, "hidden")) fc->type = FC_HIDDEN;
275 else if (!strcasecmp(al, "button")) fc->type = FC_BUTTON;
276 else if (!strcasecmp(al, "checkbox")) fc->type = FC_CHECKBOX;
277 else if (!strcasecmp(al, "radio")) fc->type = FC_RADIO;
278 else if (!strcasecmp(al, "password")) fc->type = FC_PASSWORD;
279 else if (!strcasecmp(al, "submit")) fc->type = FC_SUBMIT;
280 else if (!strcasecmp(al, "reset")) fc->type = FC_RESET;
281 else if (!strcasecmp(al, "file")) fc->type = FC_FILE;
282 else if (!strcasecmp(al, "image")) fc->type = FC_IMAGE;
283 /* else unknown type, let it default to FC_TEXT. */
284 mem_free(al);
287 if (fc->type != FC_FILE)
288 fc->default_value = get_attr_val(a, "value",
289 html_context->options);
290 if (!fc->default_value) {
291 if (fc->type == FC_CHECKBOX)
292 fc->default_value = stracpy("on");
293 else if (fc->type == FC_SUBMIT)
294 fc->default_value = stracpy("Submit");
295 else if (fc->type == FC_RESET)
296 fc->default_value = stracpy("Reset");
297 else if (fc->type == FC_BUTTON)
298 fc->default_value = stracpy("Button");
300 if (!fc->default_value)
301 fc->default_value = stracpy("");
303 fc->name = get_attr_val(a, "name", html_context->options);
305 fc->size = get_num(a, "size", html_context->options);
306 if (fc->size == -1)
307 fc->size = html_context->options->default_form_input_size;
308 fc->size++;
309 if (fc->size > html_context->options->box.width)
310 fc->size = html_context->options->box.width;
311 fc->maxlength = get_num(a, "maxlength", html_context->options);
312 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
313 if (fc->type == FC_CHECKBOX || fc->type == FC_RADIO)
314 fc->default_state = has_attr(a, "checked",
315 html_context->options);
316 if (fc->type == FC_IMAGE)
317 fc->alt = get_attr_val(a, "alt", html_context->options);
319 if (fc->type != FC_HIDDEN) {
320 html_input_format(html_context, a, fc);
323 html_context->special_f(html_context, SP_CONTROL, fc);
326 static struct list_menu lnk_menu;
328 static void
329 do_html_select(unsigned char *attr, unsigned char *html,
330 unsigned char *eof, unsigned char **end,
331 struct html_context *html_context)
333 struct conv_table *ct = html_context->special_f(html_context, SP_TABLE, NULL);
334 struct form_control *fc;
335 struct string lbl = NULL_STRING, orig_lbl = NULL_STRING;
336 unsigned char **values = NULL;
337 unsigned char **labels;
338 unsigned char *name, *t_attr, *en;
339 int namelen;
340 int nnmi = 0;
341 int order = 0;
342 int preselect = -1;
343 int group = 0;
344 int i, max_width;
345 int closing_tag;
347 html_focusable(html_context, attr);
348 init_menu(&lnk_menu);
351 en = html;
353 see:
354 html = en;
355 while (html < eof && *html != '<') html++;
357 if (html >= eof) {
359 abort:
360 *end = html;
361 if (lbl.source) done_string(&lbl);
362 if (orig_lbl.source) done_string(&orig_lbl);
363 if (values) {
364 int j;
366 for (j = 0; j < order; j++)
367 mem_free_if(values[j]);
368 mem_free(values);
370 destroy_menu(&lnk_menu);
371 *end = en;
372 return;
375 if (lbl.source) {
376 unsigned char *q, *s = en;
377 int l = html - en;
379 while (l && isspace(s[0])) s++, l--;
380 while (l && isspace(s[l-1])) l--;
381 q = convert_string(ct, s, l,
382 html_context->options->cp,
383 CSM_DEFAULT, NULL, NULL, NULL);
384 if (q) add_to_string(&lbl, q), mem_free(q);
385 add_bytes_to_string(&orig_lbl, s, l);
388 if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) {
389 html = skip_comment(html, eof);
390 goto se;
393 if (parse_element(html, eof, &name, &namelen, &t_attr, &en)) {
394 html++;
395 goto se;
398 if (!namelen) goto see;
400 if (name[0] == '/') {
401 namelen--;
402 if (!namelen) goto see;
403 name++;
404 closing_tag = 1;
405 } else {
406 closing_tag = 0;
409 if (closing_tag && !strlcasecmp(name, namelen, "SELECT", 6)) {
410 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
411 goto end_parse;
414 if (!strlcasecmp(name, namelen, "OPTION", 6)) {
415 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
417 if (!closing_tag) {
418 unsigned char *value, *label;
420 if (has_attr(t_attr, "disabled", html_context->options))
421 goto see;
422 if (preselect == -1
423 && has_attr(t_attr, "selected", html_context->options))
424 preselect = order;
425 value = get_attr_val(t_attr, "value", html_context->options);
427 if (!mem_align_alloc(&values, order, order + 1, unsigned char *, 0xFF))
428 goto abort;
430 values[order++] = value;
431 label = get_attr_val(t_attr, "label", html_context->options);
432 if (label) new_menu_item(&lnk_menu, label, order - 1, 0);
433 if (!value || !label) {
434 init_string(&lbl);
435 init_string(&orig_lbl);
436 nnmi = !!label;
440 goto see;
443 if (!strlcasecmp(name, namelen, "OPTGROUP", 8)) {
444 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
446 if (group) new_menu_item(&lnk_menu, NULL, -1, 0), group = 0;
448 if (!closing_tag) {
449 unsigned char *label;
451 label = get_attr_val(t_attr, "label", html_context->options);
453 if (!label) {
454 label = stracpy("");
455 if (!label) goto see;
457 new_menu_item(&lnk_menu, label, -1, 0);
458 group = 1;
462 goto see;
465 end_parse:
466 *end = en;
467 if (!order) goto abort;
469 labels = mem_calloc(order, sizeof(unsigned char *));
470 if (!labels) goto abort;
472 fc = init_form_control(FC_SELECT, attr, html_context);
473 if (!fc) {
474 mem_free(labels);
475 goto abort;
478 fc->name = get_attr_val(attr, "name", html_context->options);
479 fc->default_state = preselect < 0 ? 0 : preselect;
480 fc->default_value = order ? stracpy(values[fc->default_state]) : stracpy("");
481 fc->nvalues = order;
482 fc->values = values;
483 fc->menu = detach_menu(&lnk_menu);
484 fc->labels = labels;
486 menu_labels(fc->menu, "", labels);
487 put_chrs(html_context, "[", 1);
488 html_stack_dup(html_context, ELEMENT_KILLABLE);
489 format.form = fc;
490 format.style.attr |= AT_BOLD;
492 max_width = 0;
493 for (i = 0; i < order; i++) {
494 if (!labels[i]) continue;
495 int_lower_bound(&max_width, strlen(labels[i]));
498 for (i = 0; i < max_width; i++)
499 put_chrs(html_context, "_", 1);
501 pop_html_element(html_context);
502 put_chrs(html_context, "]", 1);
503 html_context->special_f(html_context, SP_CONTROL, fc);
507 static void
508 do_html_select_multiple(struct html_context *html_context, unsigned char *a,
509 unsigned char *html, unsigned char *eof,
510 unsigned char **end)
512 unsigned char *al = get_attr_val(a, "name", html_context->options);
514 if (!al) return;
515 html_focusable(html_context, a);
516 html_top->type = ELEMENT_DONT_KILL;
517 mem_free_set(&format.select, al);
518 format.select_disabled = has_attr(a, "disabled", html_context->options)
519 ? FORM_MODE_DISABLED
520 : FORM_MODE_NORMAL;
523 void
524 html_select(struct html_context *html_context, unsigned char *a,
525 unsigned char *html, unsigned char *eof, unsigned char **end)
527 if (has_attr(a, "multiple", html_context->options))
528 do_html_select_multiple(html_context, a, html, eof, end);
529 else
530 do_html_select(a, html, eof, end, html_context);
534 void
535 html_option(struct html_context *html_context, unsigned char *a,
536 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
538 struct form_control *fc;
539 unsigned char *val;
541 if (!format.select) return;
543 val = get_attr_val(a, "value", html_context->options);
544 if (!val) {
545 struct string str;
546 unsigned char *p, *r;
547 unsigned char *name;
548 int namelen;
550 for (p = a - 1; *p != '<'; p--);
552 if (!init_string(&str)) goto end_parse;
553 if (parse_element(p, html_context->eoff, NULL, NULL, NULL, &p)) {
554 INTERNAL("parse element failed");
555 val = str.source;
556 goto end_parse;
560 while (p < html_context->eoff && isspace(*p)) p++;
561 while (p < html_context->eoff && !isspace(*p) && *p != '<') {
564 add_char_to_string(&str, *p ? *p : ' '), p++;
567 r = p;
568 val = str.source; /* Has to be before the possible 'goto end_parse' */
570 while (r < html_context->eoff && isspace(*r)) r++;
571 if (r >= html_context->eoff) goto end_parse;
572 if (r - 2 <= html_context->eoff && (r[1] == '!' || r[1] == '?')) {
573 p = skip_comment(r, html_context->eoff);
574 goto se;
576 if (parse_element(r, html_context->eoff, &name, &namelen, NULL, &p)) goto sp;
578 if (namelen < 6) goto se;
579 if (name[0] == '/') name++, namelen--;
581 if (strlcasecmp(name, namelen, "OPTION", 6)
582 && strlcasecmp(name, namelen, "SELECT", 6)
583 && strlcasecmp(name, namelen, "OPTGROUP", 8))
584 goto se;
587 end_parse:
588 fc = init_form_control(FC_CHECKBOX, a, html_context);
589 if (!fc) {
590 mem_free_if(val);
591 return;
594 fc->name = null_or_stracpy(format.select);
595 fc->default_value = val;
596 fc->default_state = has_attr(a, "selected", html_context->options);
597 fc->mode = has_attr(a, "disabled", html_context->options)
598 ? FORM_MODE_DISABLED
599 : format.select_disabled;
601 put_chrs(html_context, " ", 1);
602 html_stack_dup(html_context, ELEMENT_KILLABLE);
603 format.form = fc;
604 format.style.attr |= AT_BOLD;
605 put_chrs(html_context, "[ ]", 3);
606 pop_html_element(html_context);
607 put_chrs(html_context, " ", 1);
608 html_context->special_f(html_context, SP_CONTROL, fc);
611 void
612 html_textarea(struct html_context *html_context, unsigned char *attr,
613 unsigned char *html, unsigned char *eof, unsigned char **end)
615 struct form_control *fc;
616 unsigned char *p, *t_name, *wrap_attr;
617 int t_namelen;
618 int cols, rows;
619 int i;
621 html_focusable(html_context, attr);
622 while (html < eof && (*html == '\n' || *html == '\r')) html++;
623 p = html;
624 while (p < eof && *p != '<') {
627 p++;
629 if (p >= eof) {
630 *end = eof;
631 return;
633 if (parse_element(p, eof, &t_name, &t_namelen, NULL, end)) goto pp;
634 if (strlcasecmp(t_name, t_namelen, "/TEXTAREA", 9)) goto pp;
636 fc = init_form_control(FC_TEXTAREA, attr, html_context);
637 if (!fc) return;
639 fc->name = get_attr_val(attr, "name", html_context->options);
640 fc->default_value = memacpy(html, p - html);
641 for (p = fc->default_value; p && p[0]; p++) {
642 /* FIXME: We don't cope well with entities here. Bugzilla uses
643 * &#13; inside of textarea and we fail miserably upon that
644 * one. --pasky */
645 if (p[0] == '\r') {
646 if (p[1] == '\n'
647 || (p > fc->default_value && p[-1] == '\n')) {
648 memcpy(p, p + 1, strlen(p));
649 p--;
650 } else {
651 p[0] = '\n';
656 cols = get_num(attr, "cols", html_context->options);
657 if (cols <= 0)
658 cols = html_context->options->default_form_input_size;
659 cols++; /* Add 1 column, other browsers may have different
660 behavior here (mozilla adds 2) --Zas */
661 if (cols > html_context->options->box.width)
662 cols = html_context->options->box.width;
663 fc->cols = cols;
665 rows = get_num(attr, "rows", html_context->options);
666 if (rows <= 0) rows = 1;
667 if (rows > html_context->options->box.height)
668 rows = html_context->options->box.height;
669 fc->rows = rows;
670 html_context->options->needs_height = 1;
672 wrap_attr = get_attr_val(attr, "wrap", html_context->options);
673 if (wrap_attr) {
674 if (!strcasecmp(wrap_attr, "hard")
675 || !strcasecmp(wrap_attr, "physical")) {
676 fc->wrap = FORM_WRAP_HARD;
677 } else if (!strcasecmp(wrap_attr, "soft")
678 || !strcasecmp(wrap_attr, "virtual")) {
679 fc->wrap = FORM_WRAP_SOFT;
680 } else if (!strcasecmp(wrap_attr, "none")
681 || !strcasecmp(wrap_attr, "off")) {
682 fc->wrap = FORM_WRAP_NONE;
684 mem_free(wrap_attr);
686 } else if (has_attr(attr, "nowrap", html_context->options)) {
687 fc->wrap = FORM_WRAP_NONE;
689 } else {
690 fc->wrap = FORM_WRAP_SOFT;
693 fc->maxlength = get_num(attr, "maxlength", html_context->options);
694 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
696 if (rows > 1) ln_break(html_context, 1);
697 else put_chrs(html_context, " ", 1);
699 html_stack_dup(html_context, ELEMENT_KILLABLE);
700 format.form = fc;
701 format.style.attr |= AT_BOLD;
703 for (i = 0; i < rows; i++) {
704 int j;
706 for (j = 0; j < cols; j++)
707 put_chrs(html_context, "_", 1);
708 if (i < rows - 1)
709 ln_break(html_context, 1);
712 pop_html_element(html_context);
713 if (rows > 1)
714 ln_break(html_context, 1);
715 else
716 put_chrs(html_context, " ", 1);
717 html_context->special_f(html_context, SP_CONTROL, fc);