1 /* HTML forms parser */
14 #include "bfu/listmenu.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"
34 #include "document/html/internal.h"
39 html_form(struct html_context
*html_context
, unsigned char *a
,
40 unsigned char *xxx3
, unsigned char *xxx4
, unsigned char **xxx5
)
45 html_context
->was_br
= 1;
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
);
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
;
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
;
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) */
83 form
->action
= join_urls(html_context
->base_href
, trim_chars(al
, ' ', NULL
));
87 enum uri_component components
= URI_ORIGINAL
;
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 '?'. */
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
);
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
;
136 fc
->position
= attr
- html_context
->startf
;
137 fc
->mode
= get_form_mode(html_context
, attr
);
143 html_button(struct html_context
*html_context
, unsigned char *a
,
144 unsigned char *xxx3
, unsigned char *xxx4
, unsigned char **xxx5
)
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")) {
158 } else if (!strcasecmp(al
, "reset")) {
160 } else if (strcasecmp(al
, "submit")) {
168 fc
= init_form_control(type
, a
, html_context
);
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
);
187 format
.style
.attr
|= AT_BOLD
;
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
);
198 if (format
.title
) mem_free(format
.title
);
199 format
.title
= get_attr_val(a
, "title", html_context
->doc_cp
);
207 format
.style
.attr
|= AT_BOLD
;
208 for (i
= 0; i
< fc
->size
; i
++)
209 put_chrs(html_context
, "_", 1);
213 format
.style
.attr
|= AT_BOLD
;
214 put_chrs(html_context
, "[ ]", 8);
217 format
.style
.attr
|= AT_BOLD
;
218 put_chrs(html_context
, "( )", 8);
224 mem_free_set(&format
.image
, NULL
);
225 al
= get_url_val(a
, "src", html_context
->doc_cp
);
227 al
= get_url_val(a
, "dynsrc",
228 html_context
->doc_cp
);
230 format
.image
= join_urls(html_context
->base_href
, al
);
233 format
.style
.attr
|= AT_BOLD
;
234 put_chrs(html_context
, "[ ", 7);
236 put_chrs(html_context
, fc
->alt
, strlen(fc
->alt
));
238 put_chrs(html_context
, fc
->name
, strlen(fc
->name
));
240 put_chrs(html_context
, "Submit", 6);
242 put_chrs(html_context
, " ]", 7);
248 format
.style
.attr
|= AT_BOLD
;
249 put_chrs(html_context
, "[ ", 7);
250 if (fc
->default_value
)
251 put_chrs(html_context
, fc
->default_value
, strlen(fc
->default_value
));
252 put_chrs(html_context
, " ]", 7);
257 INTERNAL("bad control type");
259 pop_html_element(html_context
);
260 put_chrs(html_context
, " ", 1);
264 html_input(struct html_context
*html_context
, unsigned char *a
,
265 unsigned char *xxx3
, unsigned char *xxx4
, unsigned char **xxx5
)
268 struct form_control
*fc
;
269 int cp
= html_context
->doc_cp
;
271 fc
= init_form_control(FC_TEXT
, a
, html_context
);
274 al
= get_attr_val(a
, "type", cp
);
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. */
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
);
310 fc
->size
= html_context
->options
->default_form_input_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
;
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
;
349 html_focusable(html_context
, attr
);
350 init_menu(&lnk_menu
);
357 while (html
< eof
&& *html
!= '<') html
++;
363 if (lbl
.source
) done_string(&lbl
);
364 if (orig_lbl
.source
) done_string(&orig_lbl
);
368 for (j
= 0; j
< order
; j
++)
369 mem_free_if(values
[j
]);
372 destroy_menu(&lnk_menu
);
378 unsigned char *q
, *s
= 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
);
395 if (parse_element(html
, eof
, &name
, &namelen
, &t_attr
, &en
)) {
400 if (!namelen
) goto see
;
402 if (name
[0] == '/') {
404 if (!namelen
) goto see
;
411 if (closing_tag
&& !strlcasecmp(name
, namelen
, "SELECT", 6)) {
412 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
416 if (!strlcasecmp(name
, namelen
, "OPTION", 6)) {
417 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
420 unsigned char *value
, *label
;
422 if (has_attr(t_attr
, "disabled", html_context
->doc_cp
))
425 && has_attr(t_attr
, "selected", html_context
->doc_cp
))
427 value
= get_attr_val(t_attr
, "value", html_context
->doc_cp
);
429 if (!mem_align_alloc(&values
, order
, order
+ 1, 0xFF))
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
) {
437 init_string(&orig_lbl
);
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;
451 unsigned char *label
;
453 label
= get_attr_val(t_attr
, "label", html_context
->doc_cp
);
457 if (!label
) goto see
;
459 new_menu_item(&lnk_menu
, label
, -1, 0);
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
);
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("");
486 fc
->menu
= detach_menu(&lnk_menu
);
489 menu_labels(fc
->menu
, "", labels
);
490 put_chrs(html_context
, "[", 1);
491 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
493 format
.style
.attr
|= AT_BOLD
;
496 for (i
= 0; i
< order
; i
++) {
497 if (!labels
[i
]) continue;
499 if (html_context
->options
->utf8
)
500 int_lower_bound(&max_width
,
501 utf8_ptr2cells(labels
[i
], NULL
));
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
);
517 do_html_select_multiple(struct html_context
*html_context
, unsigned char *a
,
518 unsigned char *html
, unsigned char *eof
,
521 unsigned char *al
= get_attr_val(a
, "name", html_context
->doc_cp
);
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
)
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
);
539 do_html_select(a
, html
, eof
, end
, html_context
);
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
;
550 if (!format
.select
) return;
552 val
= get_attr_val(a
, "value", html_context
->doc_cp
);
555 unsigned char *p
, *r
;
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");
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
++;
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
);
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))
597 fc
= init_form_control(FC_CHECKBOX
, a
, html_context
);
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
)
609 : format
.select_disabled
;
611 put_chrs(html_context
, " ", 1);
612 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
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
);
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
;
631 html_focusable(html_context
, attr
);
632 while (html
< eof
&& (*html
== '\n' || *html
== '\r')) html
++;
634 while (p
< eof
&& *p
!= '<') {
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
);
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 * inside of textarea and we fail miserably upon that
658 || (p
> fc
->default_value
&& p
[-1] == '\n')) {
659 memcpy(p
, p
+ 1, strlen(p
));
667 cols
= get_num(attr
, "cols", html_context
->doc_cp
);
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
;
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
;
681 html_context
->options
->needs_height
= 1;
683 wrap_attr
= get_attr_val(attr
, "wrap", html_context
->doc_cp
);
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
;
697 } else if (has_attr(attr
, "nowrap", html_context
->doc_cp
)) {
698 fc
->wrap
= FORM_WRAP_NONE
;
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
);
712 format
.style
.attr
|= AT_BOLD
;
714 for (i
= 0; i
< rows
; i
++) {
717 for (j
= 0; j
< cols
; j
++)
718 put_chrs(html_context
, "_", 1);
720 ln_break(html_context
, 1);
723 pop_html_element(html_context
);
725 ln_break(html_context
, 1);
727 put_chrs(html_context
, " ", 1);
728 html_context
->special_f(html_context
, SP_CONTROL
, fc
);