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
->options
);
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
;
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
;
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
) {
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
);
187 format
.style
.attr
|= AT_BOLD
;
191 html_input(struct html_context
*html_context
, unsigned char *a
,
192 unsigned char *xxx3
, unsigned char *xxx4
, unsigned char **xxx5
)
196 struct form_control
*fc
;
198 fc
= init_form_control(FC_TEXT
, a
, html_context
);
201 al
= get_attr_val(a
, "type", html_context
->options
);
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. */
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
);
236 fc
->size
= html_context
->options
->default_form_input_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
);
253 if (format
.title
) mem_free(format
.title
);
254 format
.title
= get_attr_val(a
, "title", html_context
->options
);
259 format
.style
.attr
|= AT_BOLD
;
260 for (i
= 0; i
< fc
->size
; i
++)
261 put_chrs(html_context
, "_", 1);
264 format
.style
.attr
|= AT_BOLD
;
265 put_chrs(html_context
, "[ ]", 8);
268 format
.style
.attr
|= AT_BOLD
;
269 put_chrs(html_context
, "( )", 8);
272 mem_free_set(&format
.image
, NULL
);
273 al
= get_url_val(a
, "src", html_context
->options
);
275 al
= get_url_val(a
, "dynsrc",
276 html_context
->options
);
278 format
.image
= join_urls(html_context
->base_href
, al
);
281 format
.style
.attr
|= AT_BOLD
;
282 put_chrs(html_context
, "[ ", 7);
284 put_chrs(html_context
, fc
->alt
, strlen(fc
->alt
));
286 put_chrs(html_context
, fc
->name
, strlen(fc
->name
));
288 put_chrs(html_context
, "Submit", 6);
290 put_chrs(html_context
, " ]", 7);
295 format
.style
.attr
|= AT_BOLD
;
296 put_chrs(html_context
, "[ ", 7);
297 if (fc
->default_value
)
298 put_chrs(html_context
, fc
->default_value
, strlen(fc
->default_value
));
299 put_chrs(html_context
, " ]", 7);
304 INTERNAL("bad control type");
306 kill_html_stack_item(html_context
, &html_top
);
307 put_chrs(html_context
, " ", 1);
310 html_context
->special_f(html_context
, SP_CONTROL
, fc
);
313 static struct list_menu lnk_menu
;
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
;
333 html_focusable(html_context
, attr
);
334 init_menu(&lnk_menu
);
341 while (html
< eof
&& *html
!= '<') html
++;
347 if (lbl
.source
) done_string(&lbl
);
348 if (orig_lbl
.source
) done_string(&orig_lbl
);
352 for (j
= 0; j
< order
; j
++)
353 mem_free_if(values
[j
]);
356 destroy_menu(&lnk_menu
);
362 unsigned char *q
, *s
= 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
);
379 if (parse_element(html
, eof
, &t_name
, &t_namelen
, &t_attr
, &en
)) {
384 if (!strlcasecmp(t_name
, t_namelen
, "/SELECT", 7)) {
385 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
389 if (!strlcasecmp(t_name
, t_namelen
, "/OPTION", 7)) {
390 add_select_item(&lnk_menu
, &lbl
, &orig_lbl
, values
, order
, nnmi
);
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
))
402 && has_attr(t_attr
, "selected", html_context
->options
))
404 value
= get_attr_val(t_attr
, "value", html_context
->options
);
406 if (!mem_align_alloc(&values
, order
, order
+ 1, unsigned char *, 0xFF))
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
) {
414 init_string(&orig_lbl
);
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
);
434 if (!label
) goto see
;
436 new_menu_item(&lnk_menu
, label
, -1, 0);
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
);
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("");
460 fc
->menu
= detach_menu(&lnk_menu
);
463 menu_labels(fc
->menu
, "", labels
);
464 put_chrs(html_context
, "[", 1);
465 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
467 format
.style
.attr
|= AT_BOLD
;
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
);
485 do_html_select_multiple(struct html_context
*html_context
, unsigned char *a
,
486 unsigned char *html
, unsigned char *eof
,
489 unsigned char *al
= get_attr_val(a
, "name", html_context
->options
);
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
)
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
);
507 do_html_select(a
, html
, eof
, end
, html_context
);
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
;
518 if (!format
.select
) return;
520 val
= get_attr_val(a
, "value", html_context
->options
);
523 unsigned char *p
, *r
;
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");
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
++;
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
);
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))
564 fc
= init_form_control(FC_CHECKBOX
, a
, html_context
);
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
)
575 : format
.select_disabled
;
577 put_chrs(html_context
, " ", 1);
578 html_stack_dup(html_context
, ELEMENT_KILLABLE
);
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
);
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
;
597 html_focusable(html_context
, attr
);
598 while (html
< eof
&& (*html
== '\n' || *html
== '\r')) html
++;
600 while (p
< eof
&& *p
!= '<') {
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
);
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 * inside of textarea and we fail miserably upon that
623 || (p
> fc
->default_value
&& p
[-1] == '\n')) {
624 memcpy(p
, p
+ 1, strlen(p
));
632 cols
= get_num(attr
, "cols", html_context
->options
);
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
;
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
;
646 html_context
->options
->needs_height
= 1;
648 wrap_attr
= get_attr_val(attr
, "wrap", html_context
->options
);
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
;
662 } else if (has_attr(attr
, "nowrap", html_context
->options
)) {
663 fc
->wrap
= FORM_WRAP_NONE
;
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
);
677 format
.style
.attr
|= AT_BOLD
;
679 for (i
= 0; i
< rows
; i
++) {
682 for (j
= 0; j
< cols
; j
++)
683 put_chrs(html_context
, "_", 1);
685 ln_break(html_context
, 1);
688 kill_html_stack_item(html_context
, &html_top
);
690 ln_break(html_context
, 1);
692 put_chrs(html_context
, " ", 1);
693 html_context
->special_f(html_context
, SP_CONTROL
, fc
);