Let html_select, html_textarea, and html_script call do_html_select,
[elinks.git] / src / document / html / parser / forms.c
blob8f08869d319e5fee82eb2e9276103e36a46afd79
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)
43 unsigned char *al;
44 struct form *form;
46 html_context->was_br = 1;
48 form = init_form();
49 if (!form) return;
51 form->method = FORM_METHOD_GET;
52 form->form_num = a - html_context->startf;
54 al = get_attr_val(a, "method", html_context->options);
55 if (al) {
56 if (!strcasecmp(al, "post")) {
57 unsigned char *enctype;
59 enctype = get_attr_val(a, "enctype",
60 html_context->options);
62 form->method = FORM_METHOD_POST;
63 if (enctype) {
64 if (!strcasecmp(enctype, "multipart/form-data"))
65 form->method = FORM_METHOD_POST_MP;
66 if (!strcasecmp(enctype, "text/plain"))
67 form->method = FORM_METHOD_POST_TEXT_PLAIN;
68 mem_free(enctype);
71 mem_free(al);
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 && fc->type == FC_SUBMIT) fc->default_value = stracpy("Submit");
173 if (!fc->default_value && fc->type == FC_RESET) fc->default_value = stracpy("Reset");
174 if (!fc->default_value && fc->type == FC_BUTTON) fc->default_value = stracpy("Button");
175 if (!fc->default_value) fc->default_value = stracpy("");
177 /* XXX: Does this make sense here? Where do we get FC_IMAGE? */
178 if (fc->type == FC_IMAGE) fc->alt = get_attr_val(a, "alt", html_context->options);
179 html_context->special_f(html_context, SP_CONTROL, fc);
180 format.form = fc;
181 format.style.attr |= AT_BOLD;
184 void
185 html_input(struct html_context *html_context, unsigned char *a,
186 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
188 int i;
189 unsigned char *al;
190 struct form_control *fc;
191 enum form_type type = FC_TEXT;
193 al = get_attr_val(a, "type", html_context->options);
194 if (!al) goto no_type_attr;
196 if (!strcasecmp(al, "text")) type = FC_TEXT;
197 else if (!strcasecmp(al, "password")) type = FC_PASSWORD;
198 else if (!strcasecmp(al, "checkbox")) type = FC_CHECKBOX;
199 else if (!strcasecmp(al, "radio")) type = FC_RADIO;
200 else if (!strcasecmp(al, "submit")) type = FC_SUBMIT;
201 else if (!strcasecmp(al, "reset")) type = FC_RESET;
202 else if (!strcasecmp(al, "button")) type = FC_BUTTON;
203 else if (!strcasecmp(al, "file")) type = FC_FILE;
204 else if (!strcasecmp(al, "hidden")) type = FC_HIDDEN;
205 else if (!strcasecmp(al, "image")) type = FC_IMAGE;
206 /* else unknown type, let it default to FC_TEXT. */
207 mem_free(al);
209 no_type_attr:
210 fc = init_form_control(type, a, html_context);
211 if (!fc) return;
213 fc->name = get_attr_val(a, "name", html_context->options);
214 if (fc->type != FC_FILE)
215 fc->default_value = get_attr_val(a, "value",
216 html_context->options);
217 if (!fc->default_value && fc->type == FC_CHECKBOX) fc->default_value = stracpy("on");
218 if (!fc->default_value && fc->type == FC_SUBMIT) fc->default_value = stracpy("Submit");
219 if (!fc->default_value && fc->type == FC_RESET) fc->default_value = stracpy("Reset");
220 if (!fc->default_value && fc->type == FC_BUTTON) fc->default_value = stracpy("Button");
221 if (!fc->default_value) fc->default_value = stracpy("");
223 fc->size = get_num(a, "size", html_context->options);
224 if (fc->size == -1)
225 fc->size = html_context->options->default_form_input_size;
226 fc->size++;
227 if (fc->size > html_context->options->box.width)
228 fc->size = html_context->options->box.width;
229 fc->maxlength = get_num(a, "maxlength", html_context->options);
230 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
231 if (fc->type == FC_CHECKBOX || fc->type == FC_RADIO)
232 fc->default_state = has_attr(a, "checked",
233 html_context->options);
234 if (fc->type == FC_IMAGE)
235 fc->alt = get_attr_val(a, "alt", html_context->options);
236 if (fc->type == FC_HIDDEN) goto hid;
238 put_chrs(html_context, " ", 1);
239 html_stack_dup(html_context, ELEMENT_KILLABLE);
240 html_focusable(html_context, a);
241 format.form = fc;
242 if (format.title) mem_free(format.title);
243 format.title = get_attr_val(a, "title", html_context->options);
244 switch (fc->type) {
245 case FC_TEXT:
246 case FC_PASSWORD:
247 case FC_FILE:
248 format.style.attr |= AT_BOLD;
249 for (i = 0; i < fc->size; i++)
250 put_chrs(html_context, "_", 1);
251 break;
252 case FC_CHECKBOX:
253 format.style.attr |= AT_BOLD;
254 put_chrs(html_context, "[&nbsp;]", 8);
255 break;
256 case FC_RADIO:
257 format.style.attr |= AT_BOLD;
258 put_chrs(html_context, "(&nbsp;)", 8);
259 break;
260 case FC_IMAGE:
261 mem_free_set(&format.image, NULL);
262 al = get_url_val(a, "src", html_context->options);
263 if (!al)
264 al = get_url_val(a, "dynsrc",
265 html_context->options);
266 if (al) {
267 format.image = join_urls(html_context->base_href, al);
268 mem_free(al);
270 format.style.attr |= AT_BOLD;
271 put_chrs(html_context, "[&nbsp;", 7);
272 if (fc->alt)
273 put_chrs(html_context, fc->alt, strlen(fc->alt));
274 else if (fc->name)
275 put_chrs(html_context, fc->name, strlen(fc->name));
276 else
277 put_chrs(html_context, "Submit", 6);
279 put_chrs(html_context, "&nbsp;]", 7);
280 break;
281 case FC_SUBMIT:
282 case FC_RESET:
283 case FC_BUTTON:
284 format.style.attr |= AT_BOLD;
285 put_chrs(html_context, "[&nbsp;", 7);
286 if (fc->default_value)
287 put_chrs(html_context, fc->default_value, strlen(fc->default_value));
288 put_chrs(html_context, "&nbsp;]", 7);
289 break;
290 case FC_TEXTAREA:
291 case FC_SELECT:
292 case FC_HIDDEN:
293 INTERNAL("bad control type");
295 kill_html_stack_item(html_context, &html_top);
296 put_chrs(html_context, " ", 1);
298 hid:
299 html_context->special_f(html_context, SP_CONTROL, fc);
302 void
303 html_select(struct html_context *html_context, unsigned char *a,
304 unsigned char *html, unsigned char *eof, unsigned char **end)
306 if (!do_html_select(a, html, eof, end, html_context))
307 return;
309 unsigned char *al = get_attr_val(a, "name", html_context->options);
311 if (!al) return;
312 html_focusable(html_context, a);
313 html_top.type = ELEMENT_DONT_KILL;
314 mem_free_set(&format.select, al);
315 format.select_disabled = has_attr(a, "disabled", html_context->options)
316 ? FORM_MODE_DISABLED
317 : FORM_MODE_NORMAL;
320 void
321 html_option(struct html_context *html_context, unsigned char *a,
322 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
324 struct form_control *fc;
325 unsigned char *val;
327 if (!format.select) return;
329 val = get_attr_val(a, "value", html_context->options);
330 if (!val) {
331 struct string str;
332 unsigned char *p, *r;
333 unsigned char *name;
334 int namelen;
336 for (p = a - 1; *p != '<'; p--);
338 if (!init_string(&str)) goto end_parse;
339 if (parse_element(p, html_context->eoff, NULL, NULL, NULL, &p)) {
340 INTERNAL("parse element failed");
341 val = str.source;
342 goto end_parse;
346 while (p < html_context->eoff && isspace(*p)) p++;
347 while (p < html_context->eoff && !isspace(*p) && *p != '<') {
350 add_char_to_string(&str, *p ? *p : ' '), p++;
353 r = p;
354 val = str.source; /* Has to be before the possible 'goto end_parse' */
356 while (r < html_context->eoff && isspace(*r)) r++;
357 if (r >= html_context->eoff) goto end_parse;
358 if (r - 2 <= html_context->eoff && (r[1] == '!' || r[1] == '?')) {
359 p = skip_comment(r, html_context->eoff);
360 goto se;
362 if (parse_element(r, html_context->eoff, &name, &namelen, NULL, &p)) goto sp;
363 if (strlcasecmp(name, namelen, "OPTION", 6)
364 && strlcasecmp(name, namelen, "/OPTION", 7)
365 && strlcasecmp(name, namelen, "SELECT", 6)
366 && strlcasecmp(name, namelen, "/SELECT", 7)
367 && strlcasecmp(name, namelen, "OPTGROUP", 8)
368 && strlcasecmp(name, namelen, "/OPTGROUP", 9))
369 goto se;
372 end_parse:
373 fc = init_form_control(FC_CHECKBOX, a, html_context);
374 if (!fc) {
375 mem_free_if(val);
376 return;
379 fc->name = null_or_stracpy(format.select);
380 fc->default_value = val;
381 fc->default_state = has_attr(a, "selected", html_context->options);
382 fc->mode = has_attr(a, "disabled", html_context->options)
383 ? FORM_MODE_DISABLED
384 : format.select_disabled;
386 put_chrs(html_context, " ", 1);
387 html_stack_dup(html_context, ELEMENT_KILLABLE);
388 format.form = fc;
389 format.style.attr |= AT_BOLD;
390 put_chrs(html_context, "[ ]", 3);
391 kill_html_stack_item(html_context, &html_top);
392 put_chrs(html_context, " ", 1);
393 html_context->special_f(html_context, SP_CONTROL, fc);
396 static struct list_menu lnk_menu;
399 do_html_select(unsigned char *attr, unsigned char *html,
400 unsigned char *eof, unsigned char **end,
401 struct html_context *html_context)
403 struct conv_table *ct = html_context->special_f(html_context, SP_TABLE, NULL);
404 struct form_control *fc;
405 struct string lbl = NULL_STRING, orig_lbl = NULL_STRING;
406 unsigned char **values = NULL;
407 unsigned char **labels;
408 unsigned char *t_name, *t_attr, *en;
409 int t_namelen;
410 int nnmi = 0;
411 int order = 0;
412 int preselect = -1;
413 int group = 0;
414 int i, max_width;
416 if (has_attr(attr, "multiple", html_context->options)) return 1;
417 html_focusable(html_context, attr);
418 init_menu(&lnk_menu);
421 en = html;
423 see:
424 html = en;
425 while (html < eof && *html != '<') html++;
427 if (html >= eof) {
429 abort:
430 *end = html;
431 if (lbl.source) done_string(&lbl);
432 if (orig_lbl.source) done_string(&orig_lbl);
433 if (values) {
434 int j;
436 for (j = 0; j < order; j++)
437 mem_free_if(values[j]);
438 mem_free(values);
440 destroy_menu(&lnk_menu);
441 *end = en;
442 return 0;
445 if (lbl.source) {
446 unsigned char *q, *s = en;
447 int l = html - en;
449 while (l && isspace(s[0])) s++, l--;
450 while (l && isspace(s[l-1])) l--;
451 q = convert_string(ct, s, l,
452 html_context->options->cp,
453 CSM_DEFAULT, NULL, NULL, NULL);
454 if (q) add_to_string(&lbl, q), mem_free(q);
455 add_bytes_to_string(&orig_lbl, s, l);
458 if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) {
459 html = skip_comment(html, eof);
460 goto se;
463 if (parse_element(html, eof, &t_name, &t_namelen, &t_attr, &en)) {
464 html++;
465 goto se;
468 if (!strlcasecmp(t_name, t_namelen, "/SELECT", 7)) {
469 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
470 goto end_parse;
473 if (!strlcasecmp(t_name, t_namelen, "/OPTION", 7)) {
474 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
475 goto see;
478 if (!strlcasecmp(t_name, t_namelen, "OPTION", 6)) {
479 unsigned char *value, *label;
481 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
483 if (has_attr(t_attr, "disabled", html_context->options))
484 goto see;
485 if (preselect == -1
486 && has_attr(t_attr, "selected", html_context->options))
487 preselect = order;
488 value = get_attr_val(t_attr, "value", html_context->options);
490 if (!mem_align_alloc(&values, order, order + 1, unsigned char *, 0xFF))
491 goto abort;
493 values[order++] = value;
494 label = get_attr_val(t_attr, "label", html_context->options);
495 if (label) new_menu_item(&lnk_menu, label, order - 1, 0);
496 if (!value || !label) {
497 init_string(&lbl);
498 init_string(&orig_lbl);
499 nnmi = !!label;
501 goto see;
504 if (!strlcasecmp(t_name, t_namelen, "OPTGROUP", 8)
505 || !strlcasecmp(t_name, t_namelen, "/OPTGROUP", 9)) {
506 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
508 if (group) new_menu_item(&lnk_menu, NULL, -1, 0), group = 0;
511 if (!strlcasecmp(t_name, t_namelen, "OPTGROUP", 8)) {
512 unsigned char *label;
514 label = get_attr_val(t_attr, "label", html_context->options);
516 if (!label) {
517 label = stracpy("");
518 if (!label) goto see;
520 new_menu_item(&lnk_menu, label, -1, 0);
521 group = 1;
523 goto see;
526 end_parse:
527 *end = en;
528 if (!order) goto abort;
530 labels = mem_calloc(order, sizeof(unsigned char *));
531 if (!labels) goto abort;
533 fc = init_form_control(FC_SELECT, attr, html_context);
534 if (!fc) {
535 mem_free(labels);
536 goto abort;
539 fc->name = get_attr_val(attr, "name", html_context->options);
540 fc->default_state = preselect < 0 ? 0 : preselect;
541 fc->default_value = order ? stracpy(values[fc->default_state]) : stracpy("");
542 fc->nvalues = order;
543 fc->values = values;
544 fc->menu = detach_menu(&lnk_menu);
545 fc->labels = labels;
547 menu_labels(fc->menu, "", labels);
548 put_chrs(html_context, "[", 1);
549 html_stack_dup(html_context, ELEMENT_KILLABLE);
550 format.form = fc;
551 format.style.attr |= AT_BOLD;
553 max_width = 0;
554 for (i = 0; i < order; i++) {
555 if (!labels[i]) continue;
556 int_lower_bound(&max_width, strlen(labels[i]));
559 for (i = 0; i < max_width; i++)
560 put_chrs(html_context, "_", 1);
562 kill_html_stack_item(html_context, &html_top);
563 put_chrs(html_context, "]", 1);
564 html_context->special_f(html_context, SP_CONTROL, fc);
566 return 0;
569 void
570 html_textarea(struct html_context *html_context, unsigned char *a,
571 unsigned char *html, unsigned char *eof, unsigned char **end)
573 do_html_textarea(a, html, eof, end, html_context);
576 void
577 do_html_textarea(unsigned char *attr, unsigned char *html, unsigned char *eof,
578 unsigned char **end, struct html_context *html_context)
580 struct form_control *fc;
581 unsigned char *p, *t_name, *wrap_attr;
582 int t_namelen;
583 int cols, rows;
584 int i;
586 html_focusable(html_context, attr);
587 while (html < eof && (*html == '\n' || *html == '\r')) html++;
588 p = html;
589 while (p < eof && *p != '<') {
592 p++;
594 if (p >= eof) {
595 *end = eof;
596 return;
598 if (parse_element(p, eof, &t_name, &t_namelen, NULL, end)) goto pp;
599 if (strlcasecmp(t_name, t_namelen, "/TEXTAREA", 9)) goto pp;
601 fc = init_form_control(FC_TEXTAREA, attr, html_context);
602 if (!fc) return;
604 fc->name = get_attr_val(attr, "name", html_context->options);
605 fc->default_value = memacpy(html, p - html);
606 for (p = fc->default_value; p && p[0]; p++) {
607 /* FIXME: We don't cope well with entities here. Bugzilla uses
608 * &#13; inside of textarea and we fail miserably upon that
609 * one. --pasky */
610 if (p[0] == '\r') {
611 if (p[1] == '\n'
612 || (p > fc->default_value && p[-1] == '\n')) {
613 memcpy(p, p + 1, strlen(p));
614 p--;
615 } else {
616 p[0] = '\n';
621 cols = get_num(attr, "cols", html_context->options);
622 if (cols <= 0)
623 cols = html_context->options->default_form_input_size;
624 cols++; /* Add 1 column, other browsers may have different
625 behavior here (mozilla adds 2) --Zas */
626 if (cols > html_context->options->box.width)
627 cols = html_context->options->box.width;
628 fc->cols = cols;
630 rows = get_num(attr, "rows", html_context->options);
631 if (rows <= 0) rows = 1;
632 if (rows > html_context->options->box.height)
633 rows = html_context->options->box.height;
634 fc->rows = rows;
635 html_context->options->needs_height = 1;
637 wrap_attr = get_attr_val(attr, "wrap", html_context->options);
638 if (wrap_attr) {
639 if (!strcasecmp(wrap_attr, "hard")
640 || !strcasecmp(wrap_attr, "physical")) {
641 fc->wrap = FORM_WRAP_HARD;
642 } else if (!strcasecmp(wrap_attr, "soft")
643 || !strcasecmp(wrap_attr, "virtual")) {
644 fc->wrap = FORM_WRAP_SOFT;
645 } else if (!strcasecmp(wrap_attr, "none")
646 || !strcasecmp(wrap_attr, "off")) {
647 fc->wrap = FORM_WRAP_NONE;
649 mem_free(wrap_attr);
651 } else if (has_attr(attr, "nowrap", html_context->options)) {
652 fc->wrap = FORM_WRAP_NONE;
654 } else {
655 fc->wrap = FORM_WRAP_SOFT;
658 fc->maxlength = get_num(attr, "maxlength", html_context->options);
659 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
661 if (rows > 1) ln_break(html_context, 1);
662 else put_chrs(html_context, " ", 1);
664 html_stack_dup(html_context, ELEMENT_KILLABLE);
665 format.form = fc;
666 format.style.attr |= AT_BOLD;
668 for (i = 0; i < rows; i++) {
669 int j;
671 for (j = 0; j < cols; j++)
672 put_chrs(html_context, "_", 1);
673 if (i < rows - 1)
674 ln_break(html_context, 1);
677 kill_html_stack_item(html_context, &html_top);
678 if (rows > 1)
679 ln_break(html_context, 1);
680 else
681 put_chrs(html_context, " ", 1);
682 html_context->special_f(html_context, SP_CONTROL, fc);