html_input(): drop local variable @type, use @fc->type directly.
[elinks.git] / src / document / html / parser / forms.c
blob394f9c73c69a510409db1ddf44a16369a6002ebd
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);
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 /* XXX: Does this make sense here? Where do we get FC_IMAGE? */
184 if (fc->type == FC_IMAGE) fc->alt = get_attr_val(a, "alt", html_context->options);
185 html_context->special_f(html_context, SP_CONTROL, fc);
186 format.form = fc;
187 format.style.attr |= AT_BOLD;
190 void
191 html_input(struct html_context *html_context, unsigned char *a,
192 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
194 int i;
195 unsigned char *al;
196 struct form_control *fc;
198 fc = init_form_control(FC_TEXT, a, html_context);
199 if (!fc) return;
201 al = get_attr_val(a, "type", html_context->options);
202 if (al) {
203 if (!strcasecmp(al, "text")) fc->type = FC_TEXT;
204 else if (!strcasecmp(al, "hidden")) fc->type = FC_HIDDEN;
205 else if (!strcasecmp(al, "button")) fc->type = FC_BUTTON;
206 else if (!strcasecmp(al, "checkbox")) fc->type = FC_CHECKBOX;
207 else if (!strcasecmp(al, "radio")) fc->type = FC_RADIO;
208 else if (!strcasecmp(al, "password")) fc->type = FC_PASSWORD;
209 else if (!strcasecmp(al, "submit")) fc->type = FC_SUBMIT;
210 else if (!strcasecmp(al, "reset")) fc->type = FC_RESET;
211 else if (!strcasecmp(al, "file")) fc->type = FC_FILE;
212 else if (!strcasecmp(al, "image")) fc->type = FC_IMAGE;
213 /* else unknown type, let it default to FC_TEXT. */
214 mem_free(al);
217 fc->name = get_attr_val(a, "name", html_context->options);
218 if (fc->type != FC_FILE)
219 fc->default_value = get_attr_val(a, "value",
220 html_context->options);
221 if (!fc->default_value) {
222 if (fc->type == FC_CHECKBOX)
223 fc->default_value = stracpy("on");
224 else if (fc->type == FC_SUBMIT)
225 fc->default_value = stracpy("Submit");
226 else if (fc->type == FC_RESET)
227 fc->default_value = stracpy("Reset");
228 else if (fc->type == FC_BUTTON)
229 fc->default_value = stracpy("Button");
231 if (!fc->default_value)
232 fc->default_value = stracpy("");
234 fc->size = get_num(a, "size", html_context->options);
235 if (fc->size == -1)
236 fc->size = html_context->options->default_form_input_size;
237 fc->size++;
238 if (fc->size > html_context->options->box.width)
239 fc->size = html_context->options->box.width;
240 fc->maxlength = get_num(a, "maxlength", html_context->options);
241 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
242 if (fc->type == FC_CHECKBOX || fc->type == FC_RADIO)
243 fc->default_state = has_attr(a, "checked",
244 html_context->options);
245 if (fc->type == FC_IMAGE)
246 fc->alt = get_attr_val(a, "alt", html_context->options);
247 if (fc->type == FC_HIDDEN) goto hid;
249 put_chrs(html_context, " ", 1);
250 html_stack_dup(html_context, ELEMENT_KILLABLE);
251 html_focusable(html_context, a);
252 format.form = fc;
253 if (format.title) mem_free(format.title);
254 format.title = get_attr_val(a, "title", html_context->options);
255 switch (fc->type) {
256 case FC_TEXT:
257 case FC_PASSWORD:
258 case FC_FILE:
259 format.style.attr |= AT_BOLD;
260 for (i = 0; i < fc->size; i++)
261 put_chrs(html_context, "_", 1);
262 break;
263 case FC_CHECKBOX:
264 format.style.attr |= AT_BOLD;
265 put_chrs(html_context, "[&nbsp;]", 8);
266 break;
267 case FC_RADIO:
268 format.style.attr |= AT_BOLD;
269 put_chrs(html_context, "(&nbsp;)", 8);
270 break;
271 case FC_IMAGE:
272 mem_free_set(&format.image, NULL);
273 al = get_url_val(a, "src", html_context->options);
274 if (!al)
275 al = get_url_val(a, "dynsrc",
276 html_context->options);
277 if (al) {
278 format.image = join_urls(html_context->base_href, al);
279 mem_free(al);
281 format.style.attr |= AT_BOLD;
282 put_chrs(html_context, "[&nbsp;", 7);
283 if (fc->alt)
284 put_chrs(html_context, fc->alt, strlen(fc->alt));
285 else if (fc->name)
286 put_chrs(html_context, fc->name, strlen(fc->name));
287 else
288 put_chrs(html_context, "Submit", 6);
290 put_chrs(html_context, "&nbsp;]", 7);
291 break;
292 case FC_SUBMIT:
293 case FC_RESET:
294 case FC_BUTTON:
295 format.style.attr |= AT_BOLD;
296 put_chrs(html_context, "[&nbsp;", 7);
297 if (fc->default_value)
298 put_chrs(html_context, fc->default_value, strlen(fc->default_value));
299 put_chrs(html_context, "&nbsp;]", 7);
300 break;
301 case FC_TEXTAREA:
302 case FC_SELECT:
303 case FC_HIDDEN:
304 INTERNAL("bad control type");
306 kill_html_stack_item(html_context, &html_top);
307 put_chrs(html_context, " ", 1);
309 hid:
310 html_context->special_f(html_context, SP_CONTROL, fc);
313 static struct list_menu lnk_menu;
315 static void
316 do_html_select(unsigned char *attr, unsigned char *html,
317 unsigned char *eof, unsigned char **end,
318 struct html_context *html_context)
320 struct conv_table *ct = html_context->special_f(html_context, SP_TABLE, NULL);
321 struct form_control *fc;
322 struct string lbl = NULL_STRING, orig_lbl = NULL_STRING;
323 unsigned char **values = NULL;
324 unsigned char **labels;
325 unsigned char *t_name, *t_attr, *en;
326 int t_namelen;
327 int nnmi = 0;
328 int order = 0;
329 int preselect = -1;
330 int group = 0;
331 int i, max_width;
333 html_focusable(html_context, attr);
334 init_menu(&lnk_menu);
337 en = html;
339 see:
340 html = en;
341 while (html < eof && *html != '<') html++;
343 if (html >= eof) {
345 abort:
346 *end = html;
347 if (lbl.source) done_string(&lbl);
348 if (orig_lbl.source) done_string(&orig_lbl);
349 if (values) {
350 int j;
352 for (j = 0; j < order; j++)
353 mem_free_if(values[j]);
354 mem_free(values);
356 destroy_menu(&lnk_menu);
357 *end = en;
358 return;
361 if (lbl.source) {
362 unsigned char *q, *s = en;
363 int l = html - en;
365 while (l && isspace(s[0])) s++, l--;
366 while (l && isspace(s[l-1])) l--;
367 q = convert_string(ct, s, l,
368 html_context->options->cp,
369 CSM_DEFAULT, NULL, NULL, NULL);
370 if (q) add_to_string(&lbl, q), mem_free(q);
371 add_bytes_to_string(&orig_lbl, s, l);
374 if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) {
375 html = skip_comment(html, eof);
376 goto se;
379 if (parse_element(html, eof, &t_name, &t_namelen, &t_attr, &en)) {
380 html++;
381 goto se;
384 if (!strlcasecmp(t_name, t_namelen, "/SELECT", 7)) {
385 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
386 goto end_parse;
389 if (!strlcasecmp(t_name, t_namelen, "/OPTION", 7)) {
390 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
391 goto see;
394 if (!strlcasecmp(t_name, t_namelen, "OPTION", 6)) {
395 unsigned char *value, *label;
397 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
399 if (has_attr(t_attr, "disabled", html_context->options))
400 goto see;
401 if (preselect == -1
402 && has_attr(t_attr, "selected", html_context->options))
403 preselect = order;
404 value = get_attr_val(t_attr, "value", html_context->options);
406 if (!mem_align_alloc(&values, order, order + 1, unsigned char *, 0xFF))
407 goto abort;
409 values[order++] = value;
410 label = get_attr_val(t_attr, "label", html_context->options);
411 if (label) new_menu_item(&lnk_menu, label, order - 1, 0);
412 if (!value || !label) {
413 init_string(&lbl);
414 init_string(&orig_lbl);
415 nnmi = !!label;
417 goto see;
420 if (!strlcasecmp(t_name, t_namelen, "OPTGROUP", 8)
421 || !strlcasecmp(t_name, t_namelen, "/OPTGROUP", 9)) {
422 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
424 if (group) new_menu_item(&lnk_menu, NULL, -1, 0), group = 0;
427 if (!strlcasecmp(t_name, t_namelen, "OPTGROUP", 8)) {
428 unsigned char *label;
430 label = get_attr_val(t_attr, "label", html_context->options);
432 if (!label) {
433 label = stracpy("");
434 if (!label) goto see;
436 new_menu_item(&lnk_menu, label, -1, 0);
437 group = 1;
439 goto see;
442 end_parse:
443 *end = en;
444 if (!order) goto abort;
446 labels = mem_calloc(order, sizeof(unsigned char *));
447 if (!labels) goto abort;
449 fc = init_form_control(FC_SELECT, attr, html_context);
450 if (!fc) {
451 mem_free(labels);
452 goto abort;
455 fc->name = get_attr_val(attr, "name", html_context->options);
456 fc->default_state = preselect < 0 ? 0 : preselect;
457 fc->default_value = order ? stracpy(values[fc->default_state]) : stracpy("");
458 fc->nvalues = order;
459 fc->values = values;
460 fc->menu = detach_menu(&lnk_menu);
461 fc->labels = labels;
463 menu_labels(fc->menu, "", labels);
464 put_chrs(html_context, "[", 1);
465 html_stack_dup(html_context, ELEMENT_KILLABLE);
466 format.form = fc;
467 format.style.attr |= AT_BOLD;
469 max_width = 0;
470 for (i = 0; i < order; i++) {
471 if (!labels[i]) continue;
472 int_lower_bound(&max_width, strlen(labels[i]));
475 for (i = 0; i < max_width; i++)
476 put_chrs(html_context, "_", 1);
478 kill_html_stack_item(html_context, &html_top);
479 put_chrs(html_context, "]", 1);
480 html_context->special_f(html_context, SP_CONTROL, fc);
484 static void
485 do_html_select_multiple(struct html_context *html_context, unsigned char *a,
486 unsigned char *html, unsigned char *eof,
487 unsigned char **end)
489 unsigned char *al = get_attr_val(a, "name", html_context->options);
491 if (!al) return;
492 html_focusable(html_context, a);
493 html_top.type = ELEMENT_DONT_KILL;
494 mem_free_set(&format.select, al);
495 format.select_disabled = has_attr(a, "disabled", html_context->options)
496 ? FORM_MODE_DISABLED
497 : FORM_MODE_NORMAL;
500 void
501 html_select(struct html_context *html_context, unsigned char *a,
502 unsigned char *html, unsigned char *eof, unsigned char **end)
504 if (has_attr(a, "multiple", html_context->options))
505 do_html_select_multiple(html_context, a, html, eof, end);
506 else
507 do_html_select(a, html, eof, end, html_context);
511 void
512 html_option(struct html_context *html_context, unsigned char *a,
513 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
515 struct form_control *fc;
516 unsigned char *val;
518 if (!format.select) return;
520 val = get_attr_val(a, "value", html_context->options);
521 if (!val) {
522 struct string str;
523 unsigned char *p, *r;
524 unsigned char *name;
525 int namelen;
527 for (p = a - 1; *p != '<'; p--);
529 if (!init_string(&str)) goto end_parse;
530 if (parse_element(p, html_context->eoff, NULL, NULL, NULL, &p)) {
531 INTERNAL("parse element failed");
532 val = str.source;
533 goto end_parse;
537 while (p < html_context->eoff && isspace(*p)) p++;
538 while (p < html_context->eoff && !isspace(*p) && *p != '<') {
541 add_char_to_string(&str, *p ? *p : ' '), p++;
544 r = p;
545 val = str.source; /* Has to be before the possible 'goto end_parse' */
547 while (r < html_context->eoff && isspace(*r)) r++;
548 if (r >= html_context->eoff) goto end_parse;
549 if (r - 2 <= html_context->eoff && (r[1] == '!' || r[1] == '?')) {
550 p = skip_comment(r, html_context->eoff);
551 goto se;
553 if (parse_element(r, html_context->eoff, &name, &namelen, NULL, &p)) goto sp;
554 if (strlcasecmp(name, namelen, "OPTION", 6)
555 && strlcasecmp(name, namelen, "/OPTION", 7)
556 && strlcasecmp(name, namelen, "SELECT", 6)
557 && strlcasecmp(name, namelen, "/SELECT", 7)
558 && strlcasecmp(name, namelen, "OPTGROUP", 8)
559 && strlcasecmp(name, namelen, "/OPTGROUP", 9))
560 goto se;
563 end_parse:
564 fc = init_form_control(FC_CHECKBOX, a, html_context);
565 if (!fc) {
566 mem_free_if(val);
567 return;
570 fc->name = null_or_stracpy(format.select);
571 fc->default_value = val;
572 fc->default_state = has_attr(a, "selected", html_context->options);
573 fc->mode = has_attr(a, "disabled", html_context->options)
574 ? FORM_MODE_DISABLED
575 : format.select_disabled;
577 put_chrs(html_context, " ", 1);
578 html_stack_dup(html_context, ELEMENT_KILLABLE);
579 format.form = fc;
580 format.style.attr |= AT_BOLD;
581 put_chrs(html_context, "[ ]", 3);
582 kill_html_stack_item(html_context, &html_top);
583 put_chrs(html_context, " ", 1);
584 html_context->special_f(html_context, SP_CONTROL, fc);
587 void
588 html_textarea(struct html_context *html_context, unsigned char *attr,
589 unsigned char *html, unsigned char *eof, unsigned char **end)
591 struct form_control *fc;
592 unsigned char *p, *t_name, *wrap_attr;
593 int t_namelen;
594 int cols, rows;
595 int i;
597 html_focusable(html_context, attr);
598 while (html < eof && (*html == '\n' || *html == '\r')) html++;
599 p = html;
600 while (p < eof && *p != '<') {
603 p++;
605 if (p >= eof) {
606 *end = eof;
607 return;
609 if (parse_element(p, eof, &t_name, &t_namelen, NULL, end)) goto pp;
610 if (strlcasecmp(t_name, t_namelen, "/TEXTAREA", 9)) goto pp;
612 fc = init_form_control(FC_TEXTAREA, attr, html_context);
613 if (!fc) return;
615 fc->name = get_attr_val(attr, "name", html_context->options);
616 fc->default_value = memacpy(html, p - html);
617 for (p = fc->default_value; p && p[0]; p++) {
618 /* FIXME: We don't cope well with entities here. Bugzilla uses
619 * &#13; inside of textarea and we fail miserably upon that
620 * one. --pasky */
621 if (p[0] == '\r') {
622 if (p[1] == '\n'
623 || (p > fc->default_value && p[-1] == '\n')) {
624 memcpy(p, p + 1, strlen(p));
625 p--;
626 } else {
627 p[0] = '\n';
632 cols = get_num(attr, "cols", html_context->options);
633 if (cols <= 0)
634 cols = html_context->options->default_form_input_size;
635 cols++; /* Add 1 column, other browsers may have different
636 behavior here (mozilla adds 2) --Zas */
637 if (cols > html_context->options->box.width)
638 cols = html_context->options->box.width;
639 fc->cols = cols;
641 rows = get_num(attr, "rows", html_context->options);
642 if (rows <= 0) rows = 1;
643 if (rows > html_context->options->box.height)
644 rows = html_context->options->box.height;
645 fc->rows = rows;
646 html_context->options->needs_height = 1;
648 wrap_attr = get_attr_val(attr, "wrap", html_context->options);
649 if (wrap_attr) {
650 if (!strcasecmp(wrap_attr, "hard")
651 || !strcasecmp(wrap_attr, "physical")) {
652 fc->wrap = FORM_WRAP_HARD;
653 } else if (!strcasecmp(wrap_attr, "soft")
654 || !strcasecmp(wrap_attr, "virtual")) {
655 fc->wrap = FORM_WRAP_SOFT;
656 } else if (!strcasecmp(wrap_attr, "none")
657 || !strcasecmp(wrap_attr, "off")) {
658 fc->wrap = FORM_WRAP_NONE;
660 mem_free(wrap_attr);
662 } else if (has_attr(attr, "nowrap", html_context->options)) {
663 fc->wrap = FORM_WRAP_NONE;
665 } else {
666 fc->wrap = FORM_WRAP_SOFT;
669 fc->maxlength = get_num(attr, "maxlength", html_context->options);
670 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
672 if (rows > 1) ln_break(html_context, 1);
673 else put_chrs(html_context, " ", 1);
675 html_stack_dup(html_context, ELEMENT_KILLABLE);
676 format.form = fc;
677 format.style.attr |= AT_BOLD;
679 for (i = 0; i < rows; i++) {
680 int j;
682 for (j = 0; j < cols; j++)
683 put_chrs(html_context, "_", 1);
684 if (i < rows - 1)
685 ln_break(html_context, 1);
688 kill_html_stack_item(html_context, &html_top);
689 if (rows > 1)
690 ln_break(html_context, 1);
691 else
692 put_chrs(html_context, " ", 1);
693 html_context->special_f(html_context, SP_CONTROL, fc);