Bug 784: Add html_context->doc_cp and parse attributes with it.
[elinks/kon.git] / src / document / html / parser / forms.c
blob7bfff63f2267dadf298eefaba2f5f6a7b6abc4fd
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->doc_cp);
54 if (al) {
55 if (!strcasecmp(al, "post")) {
56 unsigned char *enctype;
58 enctype = get_attr_val(a, "enctype",
59 html_context->doc_cp);
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->doc_cp);
73 al = get_attr_val(a, "name", html_context->doc_cp);
74 if (al) form->name = al;
76 al = get_attr_val(a, "action", html_context->doc_cp);
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->doc_cp))
118 return FORM_MODE_DISABLED;
120 if (has_attr(attr, "readonly", html_context->doc_cp))
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;
149 int cp = html_context->doc_cp;
151 html_focusable(html_context, a);
153 al = get_attr_val(a, "type", cp);
154 if (!al) goto no_type_attr;
156 if (!strcasecmp(al, "button")) {
157 type = FC_BUTTON;
158 } else if (!strcasecmp(al, "reset")) {
159 type = FC_RESET;
160 } else if (strcasecmp(al, "submit")) {
161 /* unknown type */
162 mem_free(al);
163 return;
165 mem_free(al);
167 no_type_attr:
168 fc = init_form_control(type, a, html_context);
169 if (!fc) return;
171 fc->id = get_attr_val(a, "id", cp);
172 fc->name = get_attr_val(a, "name", cp);
173 fc->default_value = get_attr_val(a, "value", cp);
174 if (!fc->default_value) {
175 if (fc->type == FC_SUBMIT)
176 fc->default_value = stracpy("Submit");
177 else if (fc->type == FC_RESET)
178 fc->default_value = stracpy("Reset");
179 else if (fc->type == FC_BUTTON)
180 fc->default_value = stracpy("Button");
182 if (!fc->default_value)
183 fc->default_value = stracpy("");
185 html_context->special_f(html_context, SP_CONTROL, fc);
186 format.form = fc;
187 format.style.attr |= AT_BOLD;
190 static void
191 html_input_format(struct html_context *html_context, unsigned char *a,
192 struct form_control *fc)
194 put_chrs(html_context, " ", 1);
195 html_stack_dup(html_context, ELEMENT_KILLABLE);
196 html_focusable(html_context, a);
197 format.form = fc;
198 if (format.title) mem_free(format.title);
199 format.title = get_attr_val(a, "title", html_context->doc_cp);
200 switch (fc->type) {
201 case FC_TEXT:
202 case FC_PASSWORD:
203 case FC_FILE:
205 int i;
207 format.style.attr |= AT_BOLD;
208 for (i = 0; i < fc->size; i++)
209 put_chrs(html_context, "_", 1);
210 break;
212 case FC_CHECKBOX:
213 format.style.attr |= AT_BOLD;
214 put_chrs(html_context, "[&nbsp;]", 8);
215 break;
216 case FC_RADIO:
217 format.style.attr |= AT_BOLD;
218 put_chrs(html_context, "(&nbsp;)", 8);
219 break;
220 case FC_IMAGE:
222 unsigned char *al;
224 mem_free_set(&format.image, NULL);
225 al = get_url_val(a, "src", html_context->doc_cp);
226 if (!al)
227 al = get_url_val(a, "dynsrc",
228 html_context->doc_cp);
229 if (al) {
230 format.image = join_urls(html_context->base_href, al);
231 mem_free(al);
233 format.style.attr |= AT_BOLD;
234 put_chrs(html_context, "[&nbsp;", 7);
235 if (fc->alt)
236 put_chrs(html_context, fc->alt, strlen(fc->alt));
237 else if (fc->name)
238 put_chrs(html_context, fc->name, strlen(fc->name));
239 else
240 put_chrs(html_context, "Submit", 6);
242 put_chrs(html_context, "&nbsp;]", 7);
243 break;
245 case FC_SUBMIT:
246 case FC_RESET:
247 case FC_BUTTON:
248 format.style.attr |= AT_BOLD;
249 put_chrs(html_context, "[&nbsp;", 7);
250 if (fc->default_value)
251 put_chrs(html_context, fc->default_value, strlen(fc->default_value));
252 put_chrs(html_context, "&nbsp;]", 7);
253 break;
254 case FC_TEXTAREA:
255 case FC_SELECT:
256 case FC_HIDDEN:
257 INTERNAL("bad control type");
259 pop_html_element(html_context);
260 put_chrs(html_context, " ", 1);
263 void
264 html_input(struct html_context *html_context, unsigned char *a,
265 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
267 unsigned char *al;
268 struct form_control *fc;
269 int cp = html_context->doc_cp;
271 fc = init_form_control(FC_TEXT, a, html_context);
272 if (!fc) return;
274 al = get_attr_val(a, "type", cp);
275 if (al) {
276 if (!strcasecmp(al, "text")) fc->type = FC_TEXT;
277 else if (!strcasecmp(al, "hidden")) fc->type = FC_HIDDEN;
278 else if (!strcasecmp(al, "button")) fc->type = FC_BUTTON;
279 else if (!strcasecmp(al, "checkbox")) fc->type = FC_CHECKBOX;
280 else if (!strcasecmp(al, "radio")) fc->type = FC_RADIO;
281 else if (!strcasecmp(al, "password")) fc->type = FC_PASSWORD;
282 else if (!strcasecmp(al, "submit")) fc->type = FC_SUBMIT;
283 else if (!strcasecmp(al, "reset")) fc->type = FC_RESET;
284 else if (!strcasecmp(al, "file")) fc->type = FC_FILE;
285 else if (!strcasecmp(al, "image")) fc->type = FC_IMAGE;
286 /* else unknown type, let it default to FC_TEXT. */
287 mem_free(al);
290 if (fc->type != FC_FILE)
291 fc->default_value = get_attr_val(a, "value", cp);
292 if (!fc->default_value) {
293 if (fc->type == FC_CHECKBOX)
294 fc->default_value = stracpy("on");
295 else if (fc->type == FC_SUBMIT)
296 fc->default_value = stracpy("Submit");
297 else if (fc->type == FC_RESET)
298 fc->default_value = stracpy("Reset");
299 else if (fc->type == FC_BUTTON)
300 fc->default_value = stracpy("Button");
302 if (!fc->default_value)
303 fc->default_value = stracpy("");
305 fc->id = get_attr_val(a, "id", cp);
306 fc->name = get_attr_val(a, "name", cp);
308 fc->size = get_num(a, "size", cp);
309 if (fc->size == -1)
310 fc->size = html_context->options->default_form_input_size;
311 fc->size++;
312 if (fc->size > html_context->options->box.width)
313 fc->size = html_context->options->box.width;
314 fc->maxlength = get_num(a, "maxlength", cp);
315 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
316 if (fc->type == FC_CHECKBOX || fc->type == FC_RADIO)
317 fc->default_state = has_attr(a, "checked", cp);
318 if (fc->type == FC_IMAGE)
319 fc->alt = get_attr_val(a, "alt", cp);
321 if (fc->type != FC_HIDDEN) {
322 html_input_format(html_context, a, fc);
325 html_context->special_f(html_context, SP_CONTROL, fc);
328 static struct list_menu lnk_menu;
330 static void
331 do_html_select(unsigned char *attr, unsigned char *html,
332 unsigned char *eof, unsigned char **end,
333 struct html_context *html_context)
335 struct conv_table *ct = html_context->special_f(html_context, SP_TABLE, NULL);
336 struct form_control *fc;
337 struct string lbl = NULL_STRING, orig_lbl = NULL_STRING;
338 unsigned char **values = NULL;
339 unsigned char **labels;
340 unsigned char *name, *t_attr, *en;
341 int namelen;
342 int nnmi = 0;
343 int order = 0;
344 int preselect = -1;
345 int group = 0;
346 int i, max_width;
347 int closing_tag;
349 html_focusable(html_context, attr);
350 init_menu(&lnk_menu);
353 en = html;
355 see:
356 html = en;
357 while (html < eof && *html != '<') html++;
359 if (html >= eof) {
361 abort:
362 *end = html;
363 if (lbl.source) done_string(&lbl);
364 if (orig_lbl.source) done_string(&orig_lbl);
365 if (values) {
366 int j;
368 for (j = 0; j < order; j++)
369 mem_free_if(values[j]);
370 mem_free(values);
372 destroy_menu(&lnk_menu);
373 *end = en;
374 return;
377 if (lbl.source) {
378 unsigned char *q, *s = en;
379 int l = html - en;
381 while (l && isspace(s[0])) s++, l--;
382 while (l && isspace(s[l-1])) l--;
383 q = convert_string(ct, s, l,
384 html_context->options->cp,
385 CSM_DEFAULT, NULL, NULL, NULL);
386 if (q) add_to_string(&lbl, q), mem_free(q);
387 add_bytes_to_string(&orig_lbl, s, l);
390 if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) {
391 html = skip_comment(html, eof);
392 goto se;
395 if (parse_element(html, eof, &name, &namelen, &t_attr, &en)) {
396 html++;
397 goto se;
400 if (!namelen) goto see;
402 if (name[0] == '/') {
403 namelen--;
404 if (!namelen) goto see;
405 name++;
406 closing_tag = 1;
407 } else {
408 closing_tag = 0;
411 if (closing_tag && !strlcasecmp(name, namelen, "SELECT", 6)) {
412 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
413 goto end_parse;
416 if (!strlcasecmp(name, namelen, "OPTION", 6)) {
417 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
419 if (!closing_tag) {
420 unsigned char *value, *label;
422 if (has_attr(t_attr, "disabled", html_context->doc_cp))
423 goto see;
424 if (preselect == -1
425 && has_attr(t_attr, "selected", html_context->doc_cp))
426 preselect = order;
427 value = get_attr_val(t_attr, "value", html_context->doc_cp);
429 if (!mem_align_alloc(&values, order, order + 1, 0xFF))
430 goto abort;
432 values[order++] = value;
433 label = get_attr_val(t_attr, "label", html_context->doc_cp);
434 if (label) new_menu_item(&lnk_menu, label, order - 1, 0);
435 if (!value || !label) {
436 init_string(&lbl);
437 init_string(&orig_lbl);
438 nnmi = !!label;
442 goto see;
445 if (!strlcasecmp(name, namelen, "OPTGROUP", 8)) {
446 add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi);
448 if (group) new_menu_item(&lnk_menu, NULL, -1, 0), group = 0;
450 if (!closing_tag) {
451 unsigned char *label;
453 label = get_attr_val(t_attr, "label", html_context->doc_cp);
455 if (!label) {
456 label = stracpy("");
457 if (!label) goto see;
459 new_menu_item(&lnk_menu, label, -1, 0);
460 group = 1;
464 goto see;
467 end_parse:
468 *end = en;
469 if (!order) goto abort;
471 labels = mem_calloc(order, sizeof(unsigned char *));
472 if (!labels) goto abort;
474 fc = init_form_control(FC_SELECT, attr, html_context);
475 if (!fc) {
476 mem_free(labels);
477 goto abort;
480 fc->id = get_attr_val(attr, "id", html_context->doc_cp);
481 fc->name = get_attr_val(attr, "name", html_context->doc_cp);
482 fc->default_state = preselect < 0 ? 0 : preselect;
483 fc->default_value = order ? stracpy(values[fc->default_state]) : stracpy("");
484 fc->nvalues = order;
485 fc->values = values;
486 fc->menu = detach_menu(&lnk_menu);
487 fc->labels = labels;
489 menu_labels(fc->menu, "", labels);
490 put_chrs(html_context, "[", 1);
491 html_stack_dup(html_context, ELEMENT_KILLABLE);
492 format.form = fc;
493 format.style.attr |= AT_BOLD;
495 max_width = 0;
496 for (i = 0; i < order; i++) {
497 if (!labels[i]) continue;
498 #ifdef CONFIG_UTF8
499 if (html_context->options->utf8)
500 int_lower_bound(&max_width,
501 utf8_ptr2cells(labels[i], NULL));
502 else
503 #endif /* CONFIG_UTF8 */
504 int_lower_bound(&max_width, strlen(labels[i]));
507 for (i = 0; i < max_width; i++)
508 put_chrs(html_context, "_", 1);
510 pop_html_element(html_context);
511 put_chrs(html_context, "]", 1);
512 html_context->special_f(html_context, SP_CONTROL, fc);
516 static void
517 do_html_select_multiple(struct html_context *html_context, unsigned char *a,
518 unsigned char *html, unsigned char *eof,
519 unsigned char **end)
521 unsigned char *al = get_attr_val(a, "name", html_context->doc_cp);
523 if (!al) return;
524 html_focusable(html_context, a);
525 html_top->type = ELEMENT_DONT_KILL;
526 mem_free_set(&format.select, al);
527 format.select_disabled = has_attr(a, "disabled", html_context->doc_cp)
528 ? FORM_MODE_DISABLED
529 : FORM_MODE_NORMAL;
532 void
533 html_select(struct html_context *html_context, unsigned char *a,
534 unsigned char *html, unsigned char *eof, unsigned char **end)
536 if (has_attr(a, "multiple", html_context->doc_cp))
537 do_html_select_multiple(html_context, a, html, eof, end);
538 else
539 do_html_select(a, html, eof, end, html_context);
543 void
544 html_option(struct html_context *html_context, unsigned char *a,
545 unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5)
547 struct form_control *fc;
548 unsigned char *val;
550 if (!format.select) return;
552 val = get_attr_val(a, "value", html_context->doc_cp);
553 if (!val) {
554 struct string str;
555 unsigned char *p, *r;
556 unsigned char *name;
557 int namelen;
559 for (p = a - 1; *p != '<'; p--);
561 if (!init_string(&str)) goto end_parse;
562 if (parse_element(p, html_context->eoff, NULL, NULL, NULL, &p)) {
563 INTERNAL("parse element failed");
564 val = str.source;
565 goto end_parse;
569 while (p < html_context->eoff && isspace(*p)) p++;
570 while (p < html_context->eoff && !isspace(*p) && *p != '<') {
573 add_char_to_string(&str, *p ? *p : ' '), p++;
576 r = p;
577 val = str.source; /* Has to be before the possible 'goto end_parse' */
579 while (r < html_context->eoff && isspace(*r)) r++;
580 if (r >= html_context->eoff) goto end_parse;
581 if (r - 2 <= html_context->eoff && (r[1] == '!' || r[1] == '?')) {
582 p = skip_comment(r, html_context->eoff);
583 goto se;
585 if (parse_element(r, html_context->eoff, &name, &namelen, NULL, &p)) goto sp;
587 if (namelen < 6) goto se;
588 if (name[0] == '/') name++, namelen--;
590 if (strlcasecmp(name, namelen, "OPTION", 6)
591 && strlcasecmp(name, namelen, "SELECT", 6)
592 && strlcasecmp(name, namelen, "OPTGROUP", 8))
593 goto se;
596 end_parse:
597 fc = init_form_control(FC_CHECKBOX, a, html_context);
598 if (!fc) {
599 mem_free_if(val);
600 return;
603 fc->id = get_attr_val(a, "id", html_context->doc_cp);
604 fc->name = null_or_stracpy(format.select);
605 fc->default_value = val;
606 fc->default_state = has_attr(a, "selected", html_context->doc_cp);
607 fc->mode = has_attr(a, "disabled", html_context->doc_cp)
608 ? FORM_MODE_DISABLED
609 : format.select_disabled;
611 put_chrs(html_context, " ", 1);
612 html_stack_dup(html_context, ELEMENT_KILLABLE);
613 format.form = fc;
614 format.style.attr |= AT_BOLD;
615 put_chrs(html_context, "[ ]", 3);
616 pop_html_element(html_context);
617 put_chrs(html_context, " ", 1);
618 html_context->special_f(html_context, SP_CONTROL, fc);
621 void
622 html_textarea(struct html_context *html_context, unsigned char *attr,
623 unsigned char *html, unsigned char *eof, unsigned char **end)
625 struct form_control *fc;
626 unsigned char *p, *t_name, *wrap_attr;
627 int t_namelen;
628 int cols, rows;
629 int i;
631 html_focusable(html_context, attr);
632 while (html < eof && (*html == '\n' || *html == '\r')) html++;
633 p = html;
634 while (p < eof && *p != '<') {
637 p++;
639 if (p >= eof) {
640 *end = eof;
641 return;
643 if (parse_element(p, eof, &t_name, &t_namelen, NULL, end)) goto pp;
644 if (strlcasecmp(t_name, t_namelen, "/TEXTAREA", 9)) goto pp;
646 fc = init_form_control(FC_TEXTAREA, attr, html_context);
647 if (!fc) return;
649 fc->id = get_attr_val(attr, "id", html_context->doc_cp);
650 fc->name = get_attr_val(attr, "name", html_context->doc_cp);
651 fc->default_value = memacpy(html, p - html);
652 for (p = fc->default_value; p && p[0]; p++) {
653 /* FIXME: We don't cope well with entities here. Bugzilla uses
654 * &#13; inside of textarea and we fail miserably upon that
655 * one. --pasky */
656 if (p[0] == '\r') {
657 if (p[1] == '\n'
658 || (p > fc->default_value && p[-1] == '\n')) {
659 memcpy(p, p + 1, strlen(p));
660 p--;
661 } else {
662 p[0] = '\n';
667 cols = get_num(attr, "cols", html_context->doc_cp);
668 if (cols <= 0)
669 cols = html_context->options->default_form_input_size;
670 cols++; /* Add 1 column, other browsers may have different
671 behavior here (mozilla adds 2) --Zas */
672 if (cols > html_context->options->box.width)
673 cols = html_context->options->box.width;
674 fc->cols = cols;
676 rows = get_num(attr, "rows", html_context->doc_cp);
677 if (rows <= 0) rows = 1;
678 if (rows > html_context->options->box.height)
679 rows = html_context->options->box.height;
680 fc->rows = rows;
681 html_context->options->needs_height = 1;
683 wrap_attr = get_attr_val(attr, "wrap", html_context->doc_cp);
684 if (wrap_attr) {
685 if (!strcasecmp(wrap_attr, "hard")
686 || !strcasecmp(wrap_attr, "physical")) {
687 fc->wrap = FORM_WRAP_HARD;
688 } else if (!strcasecmp(wrap_attr, "soft")
689 || !strcasecmp(wrap_attr, "virtual")) {
690 fc->wrap = FORM_WRAP_SOFT;
691 } else if (!strcasecmp(wrap_attr, "none")
692 || !strcasecmp(wrap_attr, "off")) {
693 fc->wrap = FORM_WRAP_NONE;
695 mem_free(wrap_attr);
697 } else if (has_attr(attr, "nowrap", html_context->doc_cp)) {
698 fc->wrap = FORM_WRAP_NONE;
700 } else {
701 fc->wrap = FORM_WRAP_SOFT;
704 fc->maxlength = get_num(attr, "maxlength", html_context->doc_cp);
705 if (fc->maxlength == -1) fc->maxlength = INT_MAX;
707 if (rows > 1) ln_break(html_context, 1);
708 else put_chrs(html_context, " ", 1);
710 html_stack_dup(html_context, ELEMENT_KILLABLE);
711 format.form = fc;
712 format.style.attr |= AT_BOLD;
714 for (i = 0; i < rows; i++) {
715 int j;
717 for (j = 0; j < cols; j++)
718 put_chrs(html_context, "_", 1);
719 if (i < rows - 1)
720 ln_break(html_context, 1);
723 pop_html_element(html_context);
724 if (rows > 1)
725 ln_break(html_context, 1);
726 else
727 put_chrs(html_context, " ", 1);
728 html_context->special_f(html_context, SP_CONTROL, fc);