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
)
46 html_context
->was_br
= 1;
51 form
->method
= FORM_METHOD_GET
;
52 form
->form_num
= a
- html_context
->startf
;
54 al
= get_attr_val(a
, "method", html_context
->options
);
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
;
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
;
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) */
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
->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
;
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
;
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")) {
157 } else if (!strcasecmp(al
, "reset")) {
159 } else if (strcasecmp(al
, "submit")) {
167 fc
= init_form_control(type
, a
, html_context
);
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
);
181 format
.style
.attr
|= AT_BOLD
;
185 html_input(struct html_context
*html_context
, unsigned char *a
,
186 unsigned char *xxx3
, unsigned char *xxx4
, unsigned char **xxx5
)
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. */
210 fc
= init_form_control(type
, a
, html_context
);
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
);
225 fc
->size
= html_context
->options
->default_form_input_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
);
242 if (format
.title
) mem_free(format
.title
);
243 format
.title
= get_attr_val(a
, "title", html_context
->options
);
248 format
.style
.attr
|= AT_BOLD
;
249 for (i
= 0; i
< fc
->size
; i
++)
250 put_chrs(html_context
, "_", 1);
253 format
.style
.attr
|= AT_BOLD
;
254 put_chrs(html_context
, "[ ]", 8);
257 format
.style
.attr
|= AT_BOLD
;
258 put_chrs(html_context
, "( )", 8);
261 mem_free_set(&format
.image
, NULL
);
262 al
= get_url_val(a
, "src", html_context
->options
);
264 al
= get_url_val(a
, "dynsrc",
265 html_context
->options
);
267 format
.image
= join_urls(html_context
->base_href
, al
);
270 format
.style
.attr
|= AT_BOLD
;
271 put_chrs(html_context
, "[ ", 7);
273 put_chrs(html_context
, fc
->alt
, strlen(fc
->alt
));
275 put_chrs(html_context
, fc
->name
, strlen(fc
->name
));
277 put_chrs(html_context
, "Submit", 6);
279 put_chrs(html_context
, " ]", 7);
284 format
.style
.attr
|= AT_BOLD
;
285 put_chrs(html_context
, "[ ", 7);
286 if (fc
->default_value
)
287 put_chrs(html_context
, fc
->default_value
, strlen(fc
->default_value
));
288 put_chrs(html_context
, " ]", 7);
293 INTERNAL("bad control type");
295 kill_html_stack_item(html_context
, &html_top
);
296 put_chrs(html_context
, " ", 1);
299 html_context
->special_f(html_context
, SP_CONTROL
, fc
);
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
))
309 unsigned char *al
= get_attr_val(a
, "name", html_context
->options
);
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
)
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
;
327 if (!format
.select
) return;
329 val
= get_attr_val(a
, "value", html_context
->options
);
332 unsigned char *p
, *r
;
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");
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
++;
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
);
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))
373 fc
= init_form_control(FC_CHECKBOX
, a
, html_context
);
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
)
384 : format
.select_disabled
;
386 put_chrs(html_context
, " ", 1);
387 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
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
;
416 if (has_attr(attr
, "multiple", html_context
->options
)) return 1;
417 html_focusable(html_context
, attr
);
418 init_menu(&lnk_menu
);
425 while (html
< eof
&& *html
!= '<') html
++;
431 if (lbl
.source
) done_string(&lbl
);
432 if (orig_lbl
.source
) done_string(&orig_lbl
);
436 for (j
= 0; j
< order
; j
++)
437 mem_free_if(values
[j
]);
440 destroy_menu(&lnk_menu
);
446 unsigned char *q
, *s
= 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
);
463 if (parse_element(html
, eof
, &t_name
, &t_namelen
, &t_attr
, &en
)) {
468 if (!strlcasecmp(t_name
, t_namelen
, "/SELECT", 7)) {
469 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
473 if (!strlcasecmp(t_name
, t_namelen
, "/OPTION", 7)) {
474 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
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
))
486 && has_attr(t_attr
, "selected", html_context
->options
))
488 value
= get_attr_val(t_attr
, "value", html_context
->options
);
490 if (!mem_align_alloc(&values
, order
, order
+ 1, unsigned char *, 0xFF))
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
) {
498 init_string(&orig_lbl
);
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
);
518 if (!label
) goto see
;
520 new_menu_item(&lnk_menu
, label
, -1, 0);
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
);
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("");
544 fc
->menu
= detach_menu(&lnk_menu
);
547 menu_labels(fc
->menu
, "", labels
);
548 put_chrs(html_context
, "[", 1);
549 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
551 format
.style
.attr
|= AT_BOLD
;
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
);
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
);
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
;
586 html_focusable(html_context
, attr
);
587 while (html
< eof
&& (*html
== '\n' || *html
== '\r')) html
++;
589 while (p
< eof
&& *p
!= '<') {
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
);
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 * inside of textarea and we fail miserably upon that
612 || (p
> fc
->default_value
&& p
[-1] == '\n')) {
613 memcpy(p
, p
+ 1, strlen(p
));
621 cols
= get_num(attr
, "cols", html_context
->options
);
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
;
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
;
635 html_context
->options
->needs_height
= 1;
637 wrap_attr
= get_attr_val(attr
, "wrap", html_context
->options
);
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
;
651 } else if (has_attr(attr
, "nowrap", html_context
->options
)) {
652 fc
->wrap
= FORM_WRAP_NONE
;
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
);
666 format
.style
.attr
|= AT_BOLD
;
668 for (i
= 0; i
< rows
; i
++) {
671 for (j
= 0; j
< cols
; j
++)
672 put_chrs(html_context
, "_", 1);
674 ln_break(html_context
, 1);
677 kill_html_stack_item(html_context
, &html_top
);
679 ln_break(html_context
, 1);
681 put_chrs(html_context
, " ", 1);
682 html_context
->special_f(html_context
, SP_CONTROL
, fc
);