From cc1094f0ac62db0d736b6e41e219fe628d22a69e Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Fri, 20 Feb 2009 11:39:25 +0000 Subject: Utilise hubbub's form association callback. Please can we dispense with the libxml binding? It's causing much #ifdef mess. Fix encoding of names -- previously were output as raw utf-8, rather than in the submission charset. Actually bother to destroy forms in a document, and the controls associated with them. We still leak non form-associated controls, but that's too much effort to fix right now. svn path=/trunk/netsurf/; revision=6573 --- desktop/browser.c | 3 + render/box_construct.c | 197 +++++++++++++++++----------- render/form.c | 161 ++++++++++++++++------- render/form.h | 58 ++++++--- render/html.c | 39 +++++- render/hubbub_binding.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++- render/parser_binding.h | 8 ++ 7 files changed, 650 insertions(+), 154 deletions(-) diff --git a/desktop/browser.c b/desktop/browser.c index 30d7e3490..f22ac6702 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -1574,6 +1574,9 @@ void browser_window_mouse_action_html(struct browser_window *bw, case GADGET_FILE: status = messages_get("FormFile"); break; + case GADGET_BUTTON: + /* Do nothing, as this gadget cannot be activated */ + break; } } else if (object && (mouse & BROWSER_MOUSE_MOD_2)) { diff --git a/render/box_construct.c b/render/box_construct.c index 94ca2ef57..4f9bdfd32 100644 --- a/render/box_construct.c +++ b/render/box_construct.c @@ -140,7 +140,9 @@ static bool box_a(BOX_SPECIAL_PARAMS); static bool box_body(BOX_SPECIAL_PARAMS); static bool box_br(BOX_SPECIAL_PARAMS); static bool box_image(BOX_SPECIAL_PARAMS); +#ifndef WITH_HUBBUB static bool box_form(BOX_SPECIAL_PARAMS); +#endif static bool box_textarea(BOX_SPECIAL_PARAMS); static bool box_select(BOX_SPECIAL_PARAMS); static bool box_input(BOX_SPECIAL_PARAMS); @@ -174,7 +176,9 @@ static const struct element_entry element_table[] = { {"br", box_br}, {"button", box_button}, {"embed", box_embed}, +#ifndef WITH_HUBBUB {"form", box_form}, +#endif {"frameset", box_frameset}, {"iframe", box_iframe}, {"image", box_image}, @@ -2158,13 +2162,14 @@ bool box_iframe(BOX_SPECIAL_PARAMS) } +#ifndef WITH_HUBBUB /** * Interactive form [17.3]. */ bool box_form(BOX_SPECIAL_PARAMS) { - char *xmlaction, *action, *faction, *method, *enctype, *charset, *target; + char *xmlaction, *action, *method, *enctype, *charset, *target; form_method fmethod; struct form *form; url_func_result result; @@ -2173,26 +2178,22 @@ bool box_form(BOX_SPECIAL_PARAMS) xmlGetProp(n, (const xmlChar *) "action"))) { /* the action attribute is required, but many forms fail to * specify it. In the case where it is _not_ specified, - * follow other browsers and make the form action the - * URI of the page the form is contained in. */ - action = strdup(""); + * follow other browsers and make the form action the URI of + * the page the form is contained in. */ + action = strdup(content->data.html.base_url); } else { - action = strdup(xmlaction); + result = url_join(xmlaction, content->data.html.base_url, + &action); + xmlFree(xmlaction); + + if (result != URL_FUNC_OK) + return false; } if (!action) return false; - result = url_join(action, content->data.html.base_url, &faction); - if (result != URL_FUNC_OK) { - free(action); - return false; - } - - /* No longer needed */ - free(action); - fmethod = method_GET; if ((method = (char *) xmlGetProp(n, (const xmlChar *) "method"))) { if (strcasecmp(method, "post") == 0) { @@ -2214,20 +2215,24 @@ bool box_form(BOX_SPECIAL_PARAMS) /* target for form data */ target = (char *) xmlGetProp(n, (const xmlChar *) "target"); - form = form_new(faction, target, fmethod, charset, - content->data.html.encoding); - if (!form) { - free(faction); - xmlFree(target); + form = form_new(n, action, target, fmethod, charset, + content->data.html.encoding); + + free(action); + if (charset) xmlFree(charset); + if (target) + xmlFree(target); + + if (!form) return false; - } + form->prev = content->data.html.forms; content->data.html.forms = form; return true; } - +#endif /** * Form control [17.4]. @@ -2241,25 +2246,36 @@ bool box_input(BOX_SPECIAL_PARAMS) type = (char *) xmlGetProp(n, (const xmlChar *) "type"); +#ifdef WITH_HUBBUB + gadget = binding_get_control_for_node(content->data.html.parser_binding, + n); + if (!gadget) + goto no_memory; + box->gadget = gadget; + gadget->box = box; +#endif + if (type && strcasecmp(type, "password") == 0) { if (!box_input_text(n, content, box, 0, markup_track, author, true)) goto no_memory; +#ifndef WITH_HUBBUB gadget = box->gadget; gadget->box = box; - +#endif } else if (type && strcasecmp(type, "file") == 0) { box->type = BOX_INLINE_BLOCK; - box->gadget = gadget = form_new_control(GADGET_FILE); +#ifndef WITH_HUBBUB + box->gadget = gadget = form_new_control(n, GADGET_FILE); if (!gadget) goto no_memory; gadget->box = box; - +#endif } else if (type && strcasecmp(type, "hidden") == 0) { /* no box for hidden inputs */ box->style->display = CSS_DISPLAY_NONE; - - gadget = form_new_control(GADGET_HIDDEN); +#ifndef WITH_HUBBUB + gadget = form_new_control(n, GADGET_HIDDEN); if (!gadget) goto no_memory; @@ -2270,11 +2286,12 @@ bool box_input(BOX_SPECIAL_PARAMS) goto no_memory; gadget->length = strlen(gadget->value); } - +#endif } else if (type && (strcasecmp(type, "checkbox") == 0 || strcasecmp(type, "radio") == 0)) { - box->gadget = gadget = form_new_control(type[0] == 'c' || - type[0] == 'C' ? GADGET_CHECKBOX : +#ifndef WITH_HUBBUB + box->gadget = gadget = form_new_control(n, type[0] == 'c' || + type[0] == 'C' ? GADGET_CHECKBOX : GADGET_RADIO); if (!gadget) goto no_memory; @@ -2289,9 +2306,10 @@ bool box_input(BOX_SPECIAL_PARAMS) goto no_memory; gadget->length = strlen(gadget->value); } - +#endif } else if (type && (strcasecmp(type, "submit") == 0 || - strcasecmp(type, "reset") == 0)) { + strcasecmp(type, "reset") == 0 || + strcasecmp(type, "button") == 0)) { struct box *inline_container, *inline_box; if (!box_button(n, content, box, 0, markup_track, author)) goto no_memory; @@ -2310,30 +2328,9 @@ bool box_input(BOX_SPECIAL_PARAMS) else if (box->gadget->type == GADGET_SUBMIT) inline_box->text = talloc_strdup(content, messages_get("Form_Submit")); - else + else if (box->gadget->type == GADGET_RESET) inline_box->text = talloc_strdup(content, messages_get("Form_Reset")); - if (!inline_box->text) - goto no_memory; - inline_box->length = strlen(inline_box->text); - box_add_child(inline_container, inline_box); - box_add_child(box, inline_container); - - } else if (type && strcasecmp(type, "button") == 0) { - struct box *inline_container, *inline_box; - if (!box_button(n, content, box, 0, markup_track, author)) - goto no_memory; - inline_container = box_create(0, 0, 0, 0, 0, content); - if (!inline_container) - goto no_memory; - inline_container->type = BOX_INLINE_CONTAINER; - inline_box = box_create(box->style, 0, 0, box->title, 0, - content); - if (!inline_box) - goto no_memory; - inline_box->type = BOX_TEXT; - if ((s = (char *) xmlGetProp(n, (const xmlChar *) "value"))) - inline_box->text = talloc_strdup(content, s); else inline_box->text = talloc_strdup(content, "Button"); if (!inline_box->text) @@ -2341,12 +2338,13 @@ bool box_input(BOX_SPECIAL_PARAMS) inline_box->length = strlen(inline_box->text); box_add_child(inline_container, inline_box); box_add_child(box, inline_container); - } else if (type && strcasecmp(type, "image") == 0) { - box->gadget = gadget = form_new_control(GADGET_IMAGE); +#ifndef WITH_HUBBUB + box->gadget = gadget = form_new_control(n, GADGET_IMAGE); if (!gadget) goto no_memory; gadget->box = box; +#endif gadget->type = GADGET_IMAGE; if (box->style && box->style->display != CSS_DISPLAY_NONE) { @@ -2375,19 +2373,21 @@ bool box_input(BOX_SPECIAL_PARAMS) free(url); } } - } else { /* the default type is "text" */ if (!box_input_text(n, content, box, 0, markup_track, author, false)) goto no_memory; +#ifndef WITH_HUBBUB gadget = box->gadget; gadget->box = box; +#endif } if (type) xmlFree(type); +#ifndef WITH_HUBBUB if (gadget) { if (content->data.html.forms) form_add_control(content->data.html.forms, gadget); @@ -2399,6 +2399,7 @@ bool box_input(BOX_SPECIAL_PARAMS) goto no_memory; } } +#endif *convert_children = false; return true; @@ -2406,9 +2407,10 @@ bool box_input(BOX_SPECIAL_PARAMS) no_memory: if (type) xmlFree(type); +#ifndef WITH_HUBBUB if (gadget) form_free_control(gadget); - +#endif return false; } @@ -2419,14 +2421,17 @@ no_memory: bool box_input_text(BOX_SPECIAL_PARAMS, bool password) { +#ifndef WITH_HUBBUB char *s; +#endif struct box *inline_container, *inline_box; box->type = BOX_INLINE_BLOCK; - box->gadget = form_new_control((password) ? GADGET_PASSWORD : +#ifndef WITH_HUBBUB + box->gadget = form_new_control(n, (password) ? GADGET_PASSWORD : GADGET_TEXTBOX); if (!box->gadget) - return 0; + return false; box->gadget->box = box; if ((s = (char *) xmlGetProp(n, (const xmlChar *) "maxlength"))) { @@ -2446,6 +2451,7 @@ bool box_input_text(BOX_SPECIAL_PARAMS, bool password) return NULL; } box->gadget->length = strlen(box->gadget->value); +#endif inline_container = box_create(0, 0, 0, 0, 0, content); if (!inline_container) @@ -2488,19 +2494,16 @@ bool box_input_text(BOX_SPECIAL_PARAMS, bool password) bool box_button(BOX_SPECIAL_PARAMS) { +#ifndef WITH_HUBBUB xmlChar *s; char *type = (char *) xmlGetProp(n, (const xmlChar *) "type"); - box->type = BOX_INLINE_BLOCK; - if (!type || strcasecmp(type, "submit") == 0) { - box->gadget = form_new_control(GADGET_SUBMIT); + box->gadget = form_new_control(n, GADGET_SUBMIT); } else if (strcasecmp(type, "reset") == 0) { - box->gadget = form_new_control(GADGET_RESET); + box->gadget = form_new_control(n, GADGET_RESET); } else { - /* type="button" or unknown: just render the contents */ - xmlFree(type); - return true; + box->gadget = form_new_control(n, GADGET_BUTTON); } if (type) @@ -2512,6 +2515,7 @@ bool box_button(BOX_SPECIAL_PARAMS) if (content->data.html.forms) form_add_control(content->data.html.forms, box->gadget); box->gadget->box = box; + if ((s = xmlGetProp(n, (const xmlChar *) "name")) != NULL) { box->gadget->name = strdup((char *) s); xmlFree(s); @@ -2524,6 +2528,20 @@ bool box_button(BOX_SPECIAL_PARAMS) if (!box->gadget->value) return false; } +#else + struct form_control *gadget; + + gadget = binding_get_control_for_node(content->data.html.parser_binding, + n); + if (!gadget) + return false; + + box->gadget = gadget; + gadget->box = box; +#endif + box->type = BOX_INLINE_BLOCK; + + /* Just render the contents */ return true; } @@ -2538,23 +2556,24 @@ bool box_select(BOX_SPECIAL_PARAMS) struct box *inline_container; struct box *inline_box; struct form_control *gadget; - char* s; +#ifndef WITH_HUBBUB + char *s; +#endif xmlNode *c, *c2; - gadget = form_new_control(GADGET_SELECT); +#ifndef WITH_HUBBUB + gadget = form_new_control(n, GADGET_SELECT); +#else + gadget = binding_get_control_for_node(content->data.html.parser_binding, + n); +#endif if (!gadget) return false; - gadget->data.select.multiple = false; - if ((s = (char *) xmlGetProp(n, (const xmlChar *) "multiple"))) { - gadget->data.select.multiple = true; - xmlFree(s); - } - - gadget->data.select.items = NULL; - gadget->data.select.last_item = NULL; - gadget->data.select.num_items = 0; - gadget->data.select.num_selected = 0; +#ifndef WITH_HUBBUB + gadget->data.select.multiple = + xmlHasProp(n, (const xmlChar *) "multiple"); +#endif for (c = n->children; c; c = c->next) { if (strcmp((const char *) c->name, "option") == 0) { @@ -2573,16 +2592,20 @@ bool box_select(BOX_SPECIAL_PARAMS) if (gadget->data.select.num_items == 0) { /* no options: ignore entire select */ +#ifndef WITH_HUBBUB form_free_control(gadget); +#endif return true; } +#ifndef WITH_HUBBUB if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name"))) { gadget->name = strdup(s); xmlFree(s); if (!gadget->name) goto no_memory; } +#endif box->type = BOX_INLINE_BLOCK; box->gadget = gadget; @@ -2621,14 +2644,18 @@ bool box_select(BOX_SPECIAL_PARAMS) inline_box->length = strlen(inline_box->text); +#ifndef WITH_HUBBUB if (content->data.html.forms) form_add_control(content->data.html.forms, box->gadget); +#endif *convert_children = false; return true; no_memory: +#ifndef WITH_HUBBUB form_free_control(gadget); +#endif return false; } @@ -2709,17 +2736,24 @@ bool box_textarea(BOX_SPECIAL_PARAMS) size_t len; box->type = BOX_INLINE_BLOCK; - box->gadget = form_new_control(GADGET_TEXTAREA); +#ifndef WITH_HUBBUB + box->gadget = form_new_control(n, GADGET_TEXTAREA); +#else + box->gadget = binding_get_control_for_node( + content->data.html.parser_binding, n); +#endif if (!box->gadget) return false; box->gadget->box = box; +#ifndef WITH_HUBBUB if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name"))) { box->gadget->name = strdup(s); xmlFree(s); if (!box->gadget->name) return false; } +#endif inline_container = box_create(0, 0, 0, box->title, 0, content); if (!inline_container) @@ -2727,6 +2761,9 @@ bool box_textarea(BOX_SPECIAL_PARAMS) inline_container->type = BOX_INLINE_CONTAINER; box_add_child(box, inline_container); + /** \todo Is it really necessary to reparse the content of a + * textarea element to remove entities? Hubbub will do that for us. + */ n2 = n->children; buf = xmlBufferCreate(); while(n2) { @@ -2806,8 +2843,10 @@ bool box_textarea(BOX_SPECIAL_PARAMS) xmlFree(string); xmlBufferFree(buf); +#ifndef WITH_HUBBUB if (content->data.html.forms) form_add_control(content->data.html.forms, box->gadget); +#endif *convert_children = false; return true; diff --git a/render/form.c b/render/form.c index e2dac6d1b..096d5d3dd 100644 --- a/render/form.c +++ b/render/form.c @@ -1,7 +1,7 @@ /* * Copyright 2004 James Bursa * Copyright 2003 Phil Mellor - * Copyright 2005-7 John M Bell + * Copyright 2005-9 John-Mark Bell * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -45,61 +45,113 @@ static char *form_encode_item(const char *item, const char *charset, /** * Create a struct form. * - * \param action URL to submit form to, used directly (not copied) + * \param node DOM node associated with form + * \param action URL to submit form to, or NULL for default + * \param target Target frame of form, or NULL for default * \param method method and enctype - * \param charset acceptable charactersets for form submission (not copied) - * \param doc_charset characterset of containing document (not copied) - * \return a new structure, or 0 on memory exhaustion + * \param charset acceptable encodings for form submission, or NULL + * \param doc_charset encoding of containing document + * \return a new structure, or NULL on memory exhaustion */ - -struct form *form_new(char *action, char *target, form_method method, - char *charset, char *doc_charset) +struct form *form_new(void *node, const char *action, const char *target, + form_method method, const char *charset, + const char *doc_charset) { struct form *form; - form = malloc(sizeof *form); + assert(doc_charset != NULL); + + form = calloc(1, sizeof *form); if (!form) - return 0; - form->action = action; - form->target = target; + return NULL; + + form->action = strdup(action != NULL ? action : ""); + if (form->action == NULL) { + free(form); + return NULL; + } + + form->target = target != NULL ? strdup(target) : NULL; + if (target != NULL && form->target == NULL) { + free(form->action); + free(form); + return NULL; + } + form->method = method; - form->accept_charsets = charset; - form->document_charset = doc_charset; - form->controls = 0; - form->last_control = 0; - form->prev = 0; + + form->accept_charsets = charset != NULL ? strdup(charset) : NULL; + if (charset != NULL && form->accept_charsets == NULL) { + free(form->target); + free(form->action); + free(form); + return NULL; + } + + form->document_charset = strdup(doc_charset); + if (form->document_charset == NULL) { + free(form->accept_charsets); + free(form->target); + free(form->action); + free(form); + return NULL; + } + + form->node = node; + return form; } +/** + * Free a form, and any controls it owns. + * + * \param form The form to free + * + * \note There may exist controls attached to box tree nodes which are not + * associated with any form. These will leak at present. Ideally, they will + * be cleaned up when the box tree is destroyed. As that currently happens + * via talloc, this won't happen. These controls are distinguishable, as their + * form field will be NULL. + */ +void form_free(struct form *form) +{ + struct form_control *c, *d; + + for (c = form->controls; c != NULL; c = d) { + d = c->next; + + form_free_control(c); + } + + free(form->action); + free(form->target); + free(form->accept_charsets); + free(form->document_charset); + + free(form); +} /** * Create a struct form_control. * + * \param node Associated DOM node * \param type control type - * \return a new structure, or 0 on memory exhaustion + * \return a new structure, or NULL on memory exhaustion */ - -struct form_control *form_new_control(form_control_type type) +struct form_control *form_new_control(void *node, form_control_type type) { struct form_control *control; - if ((control = malloc(sizeof *control)) == NULL) + control = calloc(1, sizeof *control); + if (control == NULL) return NULL; + + control->node = node; control->type = type; - control->name = NULL; - control->value = NULL; - control->initial_value = NULL; - control->disabled = false; - control->form = NULL; - control->box = NULL; - control->caret_inline_container = NULL; - control->caret_text_box = NULL; - control->caret_box_offset = control->caret_form_offset = 0; - control->length = 0; + + /* Default max length of input to something insane */ control->maxlength = UINT_MAX; - control->selected = false; - control->prev = NULL; - control->next = NULL; + return control; } @@ -110,12 +162,13 @@ struct form_control *form_new_control(form_control_type type) * \param form The form to add the control to * \param control The control to add */ - void form_add_control(struct form *form, struct form_control *control) { control->form = form; + if (form->controls != NULL) { assert(form->last_control); + form->last_control->next = control; control->prev = form->last_control; control->next = NULL; @@ -131,14 +184,15 @@ void form_add_control(struct form *form, struct form_control *control) * * \param control structure to free */ - void form_free_control(struct form_control *control) { free(control->name); free(control->value); free(control->initial_value); + if (control->type == GADGET_SELECT) { struct form_option *option, *next; + for (option = control->data.select.items; option; option = next) { next = option->next; @@ -147,6 +201,7 @@ void form_free_control(struct form_control *control) free(option); } } + free(control); } @@ -160,7 +215,6 @@ void form_free_control(struct form_control *control) * \param selected this option is selected * \return true on success, false on memory exhaustion */ - bool form_add_option(struct form_control *control, char *value, char *text, bool selected) { @@ -169,13 +223,12 @@ bool form_add_option(struct form_control *control, char *value, char *text, assert(control); assert(control->type == GADGET_SELECT); - option = malloc(sizeof *option); + option = calloc(1, sizeof *option); if (!option) return false; - option->selected = option->initial_selected = false; + option->value = value; option->text = text; - option->next = 0; /* add to linked list */ if (control->data.select.items == 0) @@ -216,7 +269,6 @@ bool form_add_option(struct form_control *control, char *value, char *text, * * See HTML 4.01 section 17.13.2. */ - bool form_successful_controls(struct form *form, struct form_control *submit_button, struct form_successful_control **successful_controls) @@ -344,17 +396,23 @@ bool form_successful_controls(struct form *form, case GADGET_IMAGE: { /* image */ size_t len; + char *name; if (control != submit_button) /* only the activated submit button * is successful */ continue; - len = strlen(control->name) + 3; + name = ENCODE_ITEM(control->name); + if (name == NULL) + goto no_memory; + + len = strlen(name) + 3; /* x */ success_new = malloc(sizeof(*success_new)); if (!success_new) { + free(name); LOG(("malloc failed")); goto no_memory; } @@ -366,11 +424,11 @@ bool form_successful_controls(struct form *form, free(success_new->name); free(success_new->value); free(success_new); + free(name); LOG(("malloc failed")); goto no_memory; } - sprintf(success_new->name, "%s.x", - control->name); + sprintf(success_new->name, "%s.x", name); sprintf(success_new->value, "%i", control->data.image.mx); success_new->next = 0; @@ -380,6 +438,7 @@ bool form_successful_controls(struct form *form, /* y */ success_new = malloc(sizeof(*success_new)); if (!success_new) { + free(name); LOG(("malloc failed")); goto no_memory; } @@ -391,17 +450,19 @@ bool form_successful_controls(struct form *form, free(success_new->name); free(success_new->value); free(success_new); + free(name); LOG(("malloc failed")); goto no_memory; } - sprintf(success_new->name, "%s.y", - control->name); + sprintf(success_new->name, "%s.y", name); sprintf(success_new->value, "%i", control->data.image.my); success_new->next = 0; last_success->next = success_new; last_success = success_new; + free(name); + continue; break; } @@ -450,7 +511,8 @@ bool form_successful_controls(struct form *form, } success_new->file = true; success_new->name = ENCODE_ITEM(control->name); - success_new->value = ENCODE_ITEM(control->value ? + success_new->value = + ENCODE_ITEM(control->value ? control->value : ""); success_new->next = 0; last_success->next = success_new; @@ -464,6 +526,10 @@ bool form_successful_controls(struct form *form, continue; break; + case GADGET_BUTTON: + /* Ignore it */ + break; + default: assert(0); break; @@ -505,7 +571,6 @@ no_memory: * \param textarea control of type GADGET_TEXTAREA * \return the value as a UTF-8 string on heap, or 0 on memory exhaustion */ - char *form_textarea_value(struct form_control *textarea) { unsigned int len = 0; diff --git a/render/form.h b/render/form.h index c69bd467f..eaecf3411 100644 --- a/render/form.h +++ b/render/form.h @@ -40,14 +40,17 @@ typedef enum { /** HTML form. */ struct form { - char *action; /**< Absolute URL to submit to. */ - char *target; /**< Target to submit to. */ - form_method method; /**< Method and enctype. */ - char *accept_charsets; /**< Charset to submit form in */ - char *document_charset; /**< Charset of document containing form */ - struct form_control *controls; /**< Linked list of controls. */ + void *node; /**< Corresponding DOM node */ + + char *action; /**< Absolute URL to submit to. */ + char *target; /**< Target to submit to. */ + form_method method; /**< Method and enctype. */ + char *accept_charsets; /**< Charset to submit form in */ + char *document_charset; /**< Charset of document containing form */ + struct form_control *controls; /**< Linked list of controls. */ struct form_control *last_control; /**< Last control in list. */ - struct form *prev; /**< Previous form in doc. */ + + struct form *prev; /**< Previous form in doc. */ }; /** Type of a struct form_control. */ @@ -62,25 +65,35 @@ typedef enum { GADGET_PASSWORD, GADGET_SUBMIT, GADGET_RESET, - GADGET_FILE + GADGET_FILE, + GADGET_BUTTON } form_control_type; /** Form control. */ struct form_control { - form_control_type type; - char *name; - char *value; - char *initial_value; - bool disabled; - struct form *form; - struct box *box; + void *node; /**< Corresponding DOM node */ + + form_control_type type; /**< Type of control */ + + struct form *form; /**< Containing form */ + + char *name; /**< Control name */ + char *value; /**< Current value of control */ + char *initial_value; /**< Initial value of control */ + bool disabled; /**< Whether control is disabled */ + + struct box *box; /**< Box for control */ + /** Caret details. */ struct box *caret_inline_container; struct box *caret_text_box; size_t caret_box_offset, caret_form_offset; - unsigned int length; int caret_pixel_offset; - unsigned int maxlength; - bool selected; + + unsigned int length; /**< Number of characters in control */ + unsigned int maxlength; /**< Maximum characters permitted */ + + bool selected; /**< Whether control is selected */ + union { struct { int mx, my; @@ -94,6 +107,7 @@ struct form_control { struct form_option *current; } select; } data; + struct form_control *prev; /**< Previous control in this form */ struct form_control *next; /**< Next control in this form. */ }; @@ -115,9 +129,11 @@ struct form_successful_control { struct form_successful_control *next; /**< Next in linked list. */ }; -struct form *form_new(char *action, char *target, form_method method, char *charset, - char *doc_charset); -struct form_control *form_new_control(form_control_type type); +struct form *form_new(void *node, const char *action, const char *target, + form_method method, const char *charset, + const char *doc_charset); +void form_free(struct form *form); +struct form_control *form_new_control(void *node, form_control_type type); void form_add_control(struct form *form, struct form_control *control); void form_free_control(struct form_control *control); bool form_add_option(struct form_control *control, char *value, char *text, diff --git a/render/html.c b/render/html.c index 4063f67ad..91f55b145 100644 --- a/render/html.c +++ b/render/html.c @@ -38,6 +38,7 @@ #include "image/bitmap.h" #include "render/box.h" #include "render/font.h" +#include "render/form.h" #include "render/html.h" #include "render/imagemap.h" #include "render/layout.h" @@ -281,6 +282,9 @@ bool html_convert(struct content *c, int width, int height) xmlNode *html, *head; union content_msg_data msg_data; unsigned int time_before, time_taken; +#ifdef WITH_HUBBUB + struct form *f; +#endif /* finish parsing */ if (c->source_size == 0) { @@ -321,8 +325,6 @@ bool html_convert(struct content *c, int width, int height) c->data.html.document = binding_get_document(c->data.html.parser_binding); /*xmlDebugDumpDocument(stderr, c->data.html.document);*/ - binding_destroy_tree(c->data.html.parser_binding); - c->data.html.parser_binding = NULL; if (!c->data.html.document) { LOG(("Parsing failed")); @@ -364,6 +366,26 @@ bool html_convert(struct content *c, int width, int height) if (!html_find_stylesheets(c, html)) return false; +#ifdef WITH_HUBBUB + /* Retrieve forms from parser */ + c->data.html.forms = binding_get_forms(c->data.html.parser_binding); + /* Make all actions absolute */ + for (f = c->data.html.forms; f != NULL; f = f->prev) { + char *action; + url_func_result res; + + res = url_join(f->action, c->data.html.base_url, &action); + if (res != URL_FUNC_OK) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + free(f->action); + f->action = action; + } +#endif + /* convert xml tree to box tree */ LOG(("XML to box")); content_set_status(c, messages_get("Processing")); @@ -405,6 +427,10 @@ bool html_convert(struct content *c, int width, int height) c->reformat_time - wallclock())); /*box_dump(c->data.html.layout->children, 0);*/ + /* Destroy the parser binding */ + binding_destroy_tree(c->data.html.parser_binding); + c->data.html.parser_binding = NULL; + if (c->active == 0) c->status = CONTENT_STATUS_DONE; else @@ -1654,8 +1680,17 @@ void html_reformat(struct content *c, int width, int height) void html_destroy(struct content *c) { unsigned int i; + struct form *f, *g; + LOG(("content %p", c)); + /* Destroy forms */ + for (f = c->data.html.forms; f != NULL; f = g) { + g = f->prev; + + form_free(f); + } + imagemap_destroy(c); if (c->bitmap) { diff --git a/render/hubbub_binding.c b/render/hubbub_binding.c index 1a368ccc2..79a3dba0f 100644 --- a/render/hubbub_binding.c +++ b/render/hubbub_binding.c @@ -30,6 +30,7 @@ #include #include +#include "render/form.h" #include "render/parser_binding.h" #include "utils/config.h" @@ -50,6 +51,8 @@ typedef struct hubbub_ctx { #undef NUM_NAMESPACES hubbub_tree_handler tree_handler; + + struct form *forms; } hubbub_ctx; static struct { @@ -91,6 +94,12 @@ static int add_attributes(void *ctx, void *node, static int set_quirks_mode(void *ctx, hubbub_quirks_mode mode); static int change_encoding(void *ctx, const char *charset); +static struct form *parse_form_element(xmlNode *node, const char *docenc); +static struct form_control *parse_input_element(xmlNode *node); +static struct form_control *parse_button_element(xmlNode *node); +static struct form_control *parse_select_element(xmlNode *node); +static struct form_control *parse_textarea_element(xmlNode *node); + static hubbub_tree_handler tree_handler = { create_comment, create_doctype, @@ -133,6 +142,7 @@ binding_error binding_create_tree(void *arena, const char *charset, void **ctx) c->encoding_source = ENCODING_SOURCE_HEADER; c->document = NULL; c->owns_doc = true; + c->forms = NULL; error = hubbub_parser_create(charset, true, myrealloc, arena, &c->parser); @@ -152,8 +162,7 @@ binding_error binding_create_tree(void *arena, const char *charset, void **ctx) } c->document->_private = (void *) 0; - for (i = 0; - i < sizeof(c->namespaces) / sizeof(c->namespaces[0]); i++) { + for (i = 0; i < sizeof(c->namespaces) / sizeof(c->namespaces[0]); i++) { c->namespaces[i] = NULL; } @@ -236,6 +245,42 @@ xmlDocPtr binding_get_document(void *ctx) return doc; } +struct form *binding_get_forms(void *ctx) +{ + hubbub_ctx *c = (hubbub_ctx *) ctx; + + return c->forms; +} + +struct form_control *binding_get_control_for_node(void *ctx, xmlNodePtr node) +{ + hubbub_ctx *c = (hubbub_ctx *) ctx; + struct form *f; + struct form_control *ctl = NULL; + + for (f = c->forms; f != NULL; f = f->prev) { + for (ctl = f->controls; ctl != NULL; ctl = ctl->next) { + if (ctl->node == node) + return ctl; + } + } + + /* No control found. This implies that it's not associated + * with any form. In this case, we create a control for it + * on the fly. */ + if (strcasecmp((const char *) node->name, "input") == 0) { + ctl = parse_input_element(node); + } else if (strcasecmp((const char *) node->name, "button") == 0) { + ctl = parse_button_element(node); + } else if (strcasecmp((const char *) node->name, "select") == 0) { + ctl = parse_select_element(node); + } else if (strcasecmp((const char *) node->name, "textarea") == 0) { + ctl = parse_textarea_element(node); + } + + return ctl; +} + /*****************************************************************************/ char *c_string_from_hubbub_string(hubbub_ctx *ctx, const hubbub_string *str) @@ -246,8 +291,8 @@ char *c_string_from_hubbub_string(hubbub_ctx *ctx, const hubbub_string *str) void create_namespaces(hubbub_ctx *ctx, xmlNode *root) { uint32_t i; - for (i = 1; - i < sizeof(namespaces) / sizeof(namespaces[0]); i++) { + + for (i = 1; i < sizeof(namespaces) / sizeof(namespaces[0]); i++) { ctx->namespaces[i - 1] = xmlNewNs(root, BAD_CAST namespaces[i].url, BAD_CAST namespaces[i].prefix); @@ -367,6 +412,21 @@ int create_element(void *ctx, const hubbub_tag *tag, void **result) return 1; } + if (strcasecmp(name, "form") == 0) { + struct form *form = parse_form_element(n, c->encoding); + + /* Memory exhaustion */ + if (form == NULL) { + xmlFreeNode(n); + free(name); + return 1; + } + + /* Insert into list */ + form->prev = c->forms; + c->forms = form; + } + *result = (void *) n; free(name); @@ -567,6 +627,39 @@ int has_children(void *ctx, void *node, bool *result) int form_associate(void *ctx, void *form, void *node) { + hubbub_ctx *c = (hubbub_ctx *) ctx; + xmlNode *n = (xmlNode *) node; + struct form *f; + struct form_control *control = NULL; + + /* Find form object to associate with */ + for (f = c->forms; f != NULL; f = f->prev) { + if (f->node == form) + break; + } + + /* None found -- give up */ + if (f == NULL) + return 0; + + if (strcasecmp((const char *) n->name, "input") == 0) { + control = parse_input_element(n); + } else if (strcasecmp((const char *) n->name, "button") == 0) { + control = parse_button_element(n); + } else if (strcasecmp((const char *) n->name, "select") == 0) { + control = parse_select_element(n); + } else if (strcasecmp((const char *) n->name, "textarea") == 0) { + control = parse_textarea_element(n); + } else + return 0; + + /* Memory exhaustion */ + if (control == NULL) + return 1; + + /* Add the control to the form */ + form_add_control(f, control); + return 0; } @@ -654,5 +747,242 @@ int change_encoding(void *ctx, const char *charset) return (charset == name) ? 0 : 1; } +struct form *parse_form_element(xmlNode *node, const char *docenc) +{ + struct form *form; + form_method method; + xmlChar *action, *meth, *charset, *target; + + action = xmlGetProp(node, (const xmlChar *) "action"); + charset = xmlGetProp(node, (const xmlChar *) "accept-charset"); + target = xmlGetProp(node, (const xmlChar *) "target"); + + method = method_GET; + meth = xmlGetProp(node, (const xmlChar *) "method"); + if (meth != NULL) { + if (strcasecmp((char *) meth, "post") == 0) { + xmlChar *enctype; + + method = method_POST_URLENC; + + enctype = xmlGetProp(node, (const xmlChar *) "enctype"); + if (enctype != NULL) { + if (strcasecmp((char *) enctype, + "multipart/form-data") == 0) + method = method_POST_MULTIPART; + + xmlFree(enctype); + } + } + xmlFree(meth); + } + + form = form_new(node, (char *) action, (char *) target, method, + (char *) charset, docenc); + + if (target != NULL) + xmlFree(target); + if (charset != NULL) + xmlFree(charset); + if (action != NULL) + xmlFree(action); + + return form; +} + +struct form_control *parse_input_element(xmlNode *node) +{ + struct form_control *control = NULL; + xmlChar *type = xmlGetProp(node, (const xmlChar *) "type"); + xmlChar *name; + + if (type != NULL && strcasecmp((char *) type, "password") == 0) { + control = form_new_control(node, GADGET_PASSWORD); + } else if (type != NULL && strcasecmp((char *) type, "file") == 0) { + control = form_new_control(node, GADGET_FILE); + } else if (type != NULL && strcasecmp((char *) type, "hidden") == 0) { + control = form_new_control(node, GADGET_HIDDEN); + } else if (type != NULL && strcasecmp((char *) type, "checkbox") == 0) { + control = form_new_control(node, GADGET_CHECKBOX); + } else if (type != NULL && strcasecmp((char *) type, "radio") == 0) { + control = form_new_control(node, GADGET_RADIO); + } else if (type != NULL && strcasecmp((char *) type, "submit") == 0) { + control = form_new_control(node, GADGET_SUBMIT); + } else if (type != NULL && strcasecmp((char *) type, "reset") == 0) { + control = form_new_control(node, GADGET_RESET); + } else if (type != NULL && strcasecmp((char *) type, "button") == 0) { + control = form_new_control(node, GADGET_BUTTON); + } else if (type != NULL && strcasecmp((char *) type, "image") == 0) { + control = form_new_control(node, GADGET_IMAGE); + } else { + control = form_new_control(node, GADGET_TEXTBOX); + } + + xmlFree(type); + + if (control == NULL) + return NULL; + + if (control->type == GADGET_CHECKBOX || control->type == GADGET_RADIO) { + control->selected = + xmlHasProp(node, (const xmlChar *) "checked"); + } + + if (control->type == GADGET_PASSWORD || + control->type == GADGET_TEXTBOX) { + xmlChar *len = xmlGetProp(node, (const xmlChar *) "maxlength"); + if (len != NULL) { + if (len[0] != '\0') + control->maxlength = atoi((char *) len); + xmlFree(len); + } + } + + if (control->type != GADGET_FILE && control->type != GADGET_IMAGE) { + xmlChar *value = xmlGetProp(node, (const xmlChar *) "value"); + if (value != NULL) { + control->value = strdup((char *) value); + + xmlFree(value); + + if (control->value == NULL) { + form_free_control(control); + return NULL; + } + + control->length = strlen(control->value); + } + + if (control->type == GADGET_TEXTBOX || + control->type == GADGET_PASSWORD) { + if (control->value == NULL) { + control->value = strdup(""); + if (control->value == NULL) { + form_free_control(control); + return NULL; + } + + control->length = 0; + } + + control->initial_value = strdup(control->value); + if (control->initial_value == NULL) { + form_free_control(control); + return NULL; + } + } + } + + name = xmlGetProp(node, (const xmlChar *) "name"); + if (name != NULL) { + control->name = strdup((char *) name); + + xmlFree(name); + + if (control->name == NULL) { + form_free_control(control); + return NULL; + } + } + + return control; +} + +struct form_control *parse_button_element(xmlNode *node) +{ + struct form_control *control; + xmlChar *type = xmlGetProp(node, (const xmlChar *) "type"); + xmlChar *name; + xmlChar *value; + + if (type == NULL || strcasecmp((char *) type, "submit") == 0) { + control = form_new_control(node, GADGET_SUBMIT); + } else if (strcasecmp((char *) type, "reset") == 0) { + control = form_new_control(node, GADGET_RESET); + } else { + control = form_new_control(node, GADGET_BUTTON); + } + + xmlFree(type); + + if (control == NULL) + return NULL; + + value = xmlGetProp(node, (const xmlChar *) "value"); + if (value != NULL) { + control->value = strdup((char *) value); + + xmlFree(value); + + if (control->value == NULL) { + form_free_control(control); + return NULL; + } + } + + name = xmlGetProp(node, (const xmlChar *) "name"); + if (name != NULL) { + control->name = strdup((char *) name); + + xmlFree(name); + + if (control->name == NULL) { + form_free_control(control); + return NULL; + } + } + + return control; +} + +struct form_control *parse_select_element(xmlNode *node) +{ + struct form_control *control = form_new_control(node, GADGET_SELECT); + xmlChar *name; + + if (control == NULL) + return NULL; + + control->data.select.multiple = + xmlHasProp(node, (const xmlChar *) "multiple"); + + name = xmlGetProp(node, (const xmlChar *) "name"); + if (name != NULL) { + control->name = strdup((char *) name); + + xmlFree(name); + + if (control->name == NULL) { + form_free_control(control); + return NULL; + } + } + + return control; +} + +struct form_control *parse_textarea_element(xmlNode *node) +{ + struct form_control *control = form_new_control(node, GADGET_TEXTAREA); + xmlChar *name; + + if (control == NULL) + return NULL; + + name = xmlGetProp(node, (const xmlChar *) "name"); + if (name != NULL) { + control->name = strdup((char *) name); + + xmlFree(name); + + if (control->name == NULL) { + form_free_control(control); + return NULL; + } + } + + return control; +} + #endif diff --git a/render/parser_binding.h b/render/parser_binding.h index 10c0ad334..d23b79359 100644 --- a/render/parser_binding.h +++ b/render/parser_binding.h @@ -23,6 +23,9 @@ #include +struct form; +struct form_control; + typedef enum binding_error { BINDING_OK, BINDING_NOMEM, @@ -45,5 +48,10 @@ binding_error binding_parse_completed(void *ctx); const char *binding_get_encoding(void *ctx, binding_encoding_source *source); xmlDocPtr binding_get_document(void *ctx); +#ifdef WITH_HUBBUB +struct form *binding_get_forms(void *ctx); +struct form_control *binding_get_control_for_node(void *ctx, xmlNodePtr node); +#endif + #endif -- cgit v1.2.3