From b1e6c63a2f00a84c1774d7a5165a7518d8bd7b4d Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 5 Jan 2014 15:50:23 +0000 Subject: [PATCH] Rewrite form_successful_controls() to use the DOM. This stops image inputs from working temporarily, but makes inputs inside display:none work correctly. As such, this is a net-win. --- render/form.c | 514 +++++++++++++++++++++++++++++++++++++++++++- render/form.h | 3 + utils/corestrings.c | 36 ++++ utils/corestrings.h | 15 ++ 4 files changed, 563 insertions(+), 5 deletions(-) diff --git a/render/form.c b/render/form.c index 9ac52392e..48648b103 100644 --- a/render/form.c +++ b/render/form.c @@ -88,7 +88,7 @@ static plot_font_style_t plot_fstyle_entry = { }; static char *form_acceptable_charset(struct form *form); -static char *form_encode_item(const char *item, const char *charset, +static char *form_encode_item(const char *item, uint32_t len, const char *charset, const char *fallback); static void form_select_menu_clicked(struct form_control *control, int x, int y); @@ -320,6 +320,509 @@ bool form_add_option(struct form_control *control, char *value, char *text, } +/** + * Identify 'successful' controls via the DOM. + * + * All text strings in the successful controls list will be in the charset most + * appropriate for submission. Therefore, no utf8_to_* processing should be + * performed upon them. + * + * \todo The chosen charset needs to be made available such that it can be + * included in the submission request (e.g. in the fetch's Content-Type header) + * + * \param form form to search for successful controls + * \param submit_button control used to submit the form, if any + * \param successful_controls updated to point to linked list of + * fetch_multipart_data, 0 if no controls + * \return true on success, false on memory exhaustion + * + * See HTML 4.01 section 17.13.2. + */ +bool form_successful_controls_dom(struct form *_form, + struct form_control *_submit_button, + struct fetch_multipart_data **successful_controls) +{ + dom_html_form_element *form = _form->node; + dom_html_element *submit_button = (_submit_button != NULL) ? _submit_button->node : NULL; + dom_html_collection *form_elements = NULL; + dom_html_options_collection *options = NULL; + dom_node *form_element = NULL, *option_element = NULL; + dom_exception err; + dom_string *nodename = NULL, *inputname = NULL, *inputvalue = NULL, *inputtype = NULL; + struct fetch_multipart_data sentinel, *last_success, *success_new; + bool had_submit = false, element_disabled; + char *charset, *rawfile_temp = NULL, *basename; + uint32_t index, element_count; + + last_success = &sentinel; + sentinel.next = NULL; + + LOG(("XYZZY: Yay, let's look for a form")); + + /** \todo Replace this call with something DOMish */ + charset = form_acceptable_charset(_form); + if (charset == NULL) { + LOG(("failed to find charset")); + return false; + } + +#define ENCODE_ITEM(i) (((i) == NULL) ? ( \ + form_encode_item("", 0, charset, _form->document_charset) \ + ):( \ + form_encode_item(dom_string_data(i), dom_string_byte_length(i), \ + charset, _form->document_charset) \ + )) + + err = dom_html_form_element_get_elements(form, &form_elements); + + if (err != DOM_NO_ERR) { + LOG(("Could not get form elements")); + goto dom_no_memory; + } + + LOG(("Reffed %p", form_elements)); + + err = dom_html_collection_get_length(form_elements, &element_count); + + if (err != DOM_NO_ERR) { + LOG(("Could not get form element count")); + goto dom_no_memory; + } + + for (index = 0; index < element_count; index++) { + if (form_element != NULL) { + LOG(("Unreffed %p", form_element)); + dom_node_unref(form_element); + form_element = NULL; + } + if (nodename != NULL) { + dom_string_unref(nodename); + nodename = NULL; + } + if (inputname != NULL) { + dom_string_unref(inputname); + inputname = NULL; + } + if (inputvalue != NULL) { + dom_string_unref(inputvalue); + inputvalue = NULL; + } + if (inputtype != NULL) { + dom_string_unref(inputtype); + inputtype = NULL; + } + if (options != NULL) { + dom_html_options_collection_unref(options); + options = NULL; + } + err = dom_html_collection_item(form_elements, + index, &form_element); + if (err != DOM_NO_ERR) { + LOG(("Could not retrieve form element %d", index)); + goto dom_no_memory; + } + LOG(("Reffed %p", form_element)); + /* Form elements are one of: + * HTMLInputElement + * HTMLTextAreaElement + * HTMLSelectElement + */ + err = dom_node_get_node_name(form_element, &nodename); + if (err != DOM_NO_ERR) { + LOG(("Could not get node name")); + goto dom_no_memory; + } + LOG(("Found a node(%p): `%*s`", nodename, + dom_string_byte_length(nodename), + dom_string_data(nodename))); + if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { + err = dom_html_text_area_element_get_disabled( + (dom_html_text_area_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + LOG(("Could not get text area disabled property")); + goto dom_no_memory; + } + err = dom_html_text_area_element_get_name( + (dom_html_text_area_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + LOG(("Could not get text area name property")); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { + err = dom_html_select_element_get_disabled( + (dom_html_select_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + LOG(("Could not get select disabled property")); + goto dom_no_memory; + } + err = dom_html_select_element_get_name( + (dom_html_select_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + LOG(("Could not get select name property")); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { + err = dom_html_input_element_get_disabled( + (dom_html_input_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + LOG(("Could not get input disabled property")); + goto dom_no_memory; + } + err = dom_html_input_element_get_name( + (dom_html_input_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + LOG(("Could not get input name property")); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) { + /* It was a button, no fair */ + continue; + } else { + /* Unknown element type came through! */ + LOG(("Unknown element type: %*s", + dom_string_byte_length(nodename), + dom_string_data(nodename))); + goto dom_no_memory; + } + if (element_disabled) + continue; + if (inputname == NULL) + continue; + + if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { + err = dom_html_text_area_element_get_value( + (dom_html_text_area_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get text area content")); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { + uint32_t options_count, option_index; + err = dom_html_select_element_get_options( + (dom_html_select_element *)form_element, + &options); + if (err != DOM_NO_ERR) { + LOG(("Could not get select options collection")); + goto dom_no_memory; + } + err = dom_html_options_collection_get_length( + options, &options_count); + if (err != DOM_NO_ERR) { + LOG(("Could not get select options collection length")); + goto dom_no_memory; + } + for(option_index = 0; option_index < options_count; + ++option_index) { + bool selected; + if (option_element != NULL) { + dom_node_unref(option_element); + option_element = NULL; + } + if (inputvalue != NULL) { + dom_string_unref(inputvalue); + inputvalue = NULL; + } + err = dom_html_options_collection_item( + options, option_index, &option_element); + if (err != DOM_NO_ERR) { + LOG(("Could not get options item %d", option_index)); + goto dom_no_memory; + } + err = dom_html_option_element_get_selected( + (dom_html_option_element *)option_element, + &selected); + if (err != DOM_NO_ERR) { + LOG(("Could not get option selected property")); + goto dom_no_memory; + } + if (!selected) + continue; + err = dom_html_option_element_get_value( + (dom_html_option_element *)option_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get option value")); + goto dom_no_memory; + } + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + LOG(("Could not allocate data for option")); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = ENCODE_ITEM(inputname); + if (success_new->name == NULL) { + LOG(("Could not encode name for option")); + goto dom_no_memory; + } + success_new->value = ENCODE_ITEM(inputvalue); + if (success_new->value == NULL) { + LOG(("Could not encode value for option")); + goto dom_no_memory; + } + } + continue; + } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { + /* Things to consider here */ + /* Buttons -- only if the successful control */ + /* radio and checkbox -- only if selected */ + /* file -- also get the rawfile */ + /* everything else -- just value */ + err = dom_html_input_element_get_type( + (dom_html_input_element *) form_element, + &inputtype); + if (err != DOM_NO_ERR) { + LOG(("Could not get input element type")); + goto dom_no_memory; + } + if (dom_string_caseless_isequal( + inputtype, corestring_dom_submit)) { + LOG(("Examining submit button")); + if (submit_button == NULL && !had_submit) + /* no button used, and first submit + * node found, so use it + */ + had_submit = true; + else if ((dom_node *)submit_button != + (dom_node *)form_element) + continue; + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get submit button value")); + goto dom_no_memory; + } + /* Drop through to report the successful button */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_image)) { + /* We *ONLY* use an image input if it was the + * thing which activated us + */ + LOG(("Examining image button")); + if ((dom_node *)submit_button != + (dom_node *)form_element) + continue; + + basename = ENCODE_ITEM(inputname); + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + free(basename); + LOG(("Could not allocate data for image.x")); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = malloc(strlen(basename) + 3); + if (success_new->name == NULL) { + free(basename); + LOG(("Could not allocate name for image.x")); + goto dom_no_memory; + } + success_new->value = malloc(20); + if (success_new->value == NULL) { + free(basename); + LOG(("Could not allocate value for image.x")); + goto dom_no_memory; + } + sprintf(success_new->name, "%s.x", basename); + /** \todo Store this on the node and + * retrieve it here + */ + sprintf(success_new->value, "%d", 0); + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + free(basename); + LOG(("Could not allocate data for image.y")); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = malloc(strlen(basename) + 3); + if (success_new->name == NULL) { + free(basename); + LOG(("Could not allocate name for image.y")); + goto dom_no_memory; + } + success_new->value = malloc(20); + if (success_new->value == NULL) { + free(basename); + LOG(("Could not allocate value for image.y")); + goto dom_no_memory; + } + sprintf(success_new->name, "%s.y", basename); + /** \todo Store this on the node and + * retrieve it here + */ + sprintf(success_new->value, "%d", 0); + free(basename); + continue; + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_radio) || + dom_string_caseless_isequal( + inputtype, corestring_dom_checkbox)) { + LOG(("Examining radio or checkbox")); + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get input element value")); + goto dom_no_memory; + } + if (inputvalue == NULL) + inputvalue = dom_string_ref( + corestring_dom_on); + /* Fall through to simple allocation */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_file)) { + LOG(("Examining file input")); + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get file value")); + goto dom_no_memory; + } + err = dom_node_get_user_data( + form_element, + corestring_dom___ns_key_file_name_node_data, + &rawfile_temp); + if (err != DOM_NO_ERR) { + LOG(("Could not get file rawname")); + goto dom_no_memory; + } + rawfile_temp = strdup(rawfile_temp != NULL ? + rawfile_temp : + ""); + if (rawfile_temp == NULL) { + LOG(("Could not copy file rawname")); + goto dom_no_memory; + } + /* Fall out to the allocation */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_reset) || + dom_string_caseless_isequal( + inputtype, corestring_dom_button)) { + /* Skip these */ + LOG(("Skipping RESET and BUTTON")); + continue; + } else { + /* Everything else is treated as text values */ + LOG(("Retrieving generic input text")); + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + LOG(("Could not get input value")); + goto dom_no_memory; + } + /* Fall out to the allocation */ + } + } + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + LOG(("Could not allocate data for generic")); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = ENCODE_ITEM(inputname); + if (success_new->name == NULL) { + LOG(("Could not encode name for generic")); + goto dom_no_memory; + } + success_new->value = ENCODE_ITEM(inputvalue); + if (success_new->value == NULL) { + LOG(("Could not encode value for generic")); + goto dom_no_memory; + } + if (rawfile_temp != NULL) { + success_new->file = true; + success_new->rawfile = rawfile_temp; + rawfile_temp = NULL; + } + } + + free(charset); + if (form_element != NULL) { + LOG(("Unreffed %p", form_element)); + dom_node_unref(form_element); + } + if (form_elements != NULL) { + LOG(("Unreffed %p", form_elements)); + dom_html_collection_unref(form_elements); + } + if (nodename != NULL) + dom_string_unref(nodename); + if (inputname != NULL) + dom_string_unref(inputname); + if (inputvalue != NULL) + dom_string_unref(inputvalue); + if (options != NULL) + dom_html_options_collection_unref(options); + if (option_element != NULL) + dom_node_unref(option_element); + if (inputtype != NULL) + dom_string_unref(inputtype); + if (rawfile_temp != NULL) + free(rawfile_temp); + *successful_controls = sentinel.next; + + for (success_new = *successful_controls; success_new != NULL; + success_new = success_new->next) { + LOG(("%p -> %s=%s", success_new, success_new->name, success_new->value)); + LOG(("%p -> file=%s rawfile=%s", success_new, + success_new->file ? "yes" : "no", success_new->rawfile)); + } + return true; + +dom_no_memory: + free(charset); + fetch_multipart_data_destroy(sentinel.next); + + if (form_elements != NULL) + dom_html_collection_unref(form_elements); + if (form_element != NULL) + dom_node_unref(form_element); + if (nodename != NULL) + dom_string_unref(nodename); + if (inputname != NULL) + dom_string_unref(inputname); + if (inputvalue != NULL) + dom_string_unref(inputvalue); + if (options != NULL) + dom_html_options_collection_unref(options); + if (option_element != NULL) + dom_node_unref(option_element); + if (inputtype != NULL) + dom_string_unref(inputtype); + if (rawfile_temp != NULL) + free(rawfile_temp); + + return false; +} +#undef ENCODE_ITEM + /** * Identify 'successful' controls. * @@ -356,7 +859,7 @@ bool form_successful_controls(struct form *form, if (charset == NULL) return false; -#define ENCODE_ITEM(i) form_encode_item((i), charset, form->document_charset) +#define ENCODE_ITEM(i) form_encode_item((i), 0, charset, form->document_charset) for (control = form->controls; control; control = control->next) { /* ignore disabled controls */ @@ -812,12 +1315,13 @@ char *form_acceptable_charset(struct form *form) * \todo Return charset used? * * \param item String to convert + * \param len Length of string to convert * \param charset Destination charset * \param fallback Fallback charset (may be NULL), * used iff converting to charset fails * \return Pointer to converted string (on heap, caller frees), or NULL */ -char *form_encode_item(const char *item, const char *charset, +char *form_encode_item(const char *item, uint32_t len, const char *charset, const char *fallback) { utf8_convert_ret err; @@ -833,7 +1337,7 @@ char *form_encode_item(const char *item, const char *charset, if (err == UTF8_CONVERT_BADENC) { /* charset not understood, try without transliteration */ snprintf(cset, sizeof cset, "%s", charset); - err = utf8_to_enc(item, cset, 0, &ret); + err = utf8_to_enc(item, cset, len, &ret); if (err == UTF8_CONVERT_BADENC) { /* nope, try fallback charset (if any) */ @@ -1490,7 +1994,7 @@ void form_submit(nsurl *page_url, struct browser_window *target, assert(form != NULL); - if (form_successful_controls(form, submit_button, &success) == false) { + if (form_successful_controls_dom(form, submit_button, &success) == false) { warn_user("NoMemory", 0); return; } diff --git a/render/form.h b/render/form.h index b538c3b33..e65206d33 100644 --- a/render/form.h +++ b/render/form.h @@ -159,6 +159,9 @@ bool form_add_option(struct form_control *control, char *value, char *text, bool form_successful_controls(struct form *form, struct form_control *submit_button, struct fetch_multipart_data **successful_controls); +bool form_successful_controls_dom(struct form *form, + struct form_control *submit_button, + struct fetch_multipart_data **successful_controls); bool form_open_select_menu(void *client_data, struct form_control *control, diff --git a/utils/corestrings.c b/utils/corestrings.c index 8623d127b..9050ffe48 100644 --- a/utils/corestrings.c +++ b/utils/corestrings.c @@ -232,6 +232,16 @@ dom_string *corestring_dom_volumechange; dom_string *corestring_dom_vspace; dom_string *corestring_dom_waiting; dom_string *corestring_dom_width; +dom_string *corestring_dom_BUTTON; +dom_string *corestring_dom_INPUT; +dom_string *corestring_dom_SELECT; +dom_string *corestring_dom_TEXTAREA; +dom_string *corestring_dom_button; +dom_string *corestring_dom_image; +dom_string *corestring_dom_radio; +dom_string *corestring_dom_checkbox; +dom_string *corestring_dom_file; +dom_string *corestring_dom_on; dom_string *corestring_dom___ns_key_libcss_node_data; dom_string *corestring_dom___ns_key_file_name_node_data; @@ -463,6 +473,19 @@ void corestrings_fini(void) CSS_DOM_STRING_UNREF(vspace); CSS_DOM_STRING_UNREF(waiting); CSS_DOM_STRING_UNREF(width); + /* DOM node names, not really CSS */ + CSS_DOM_STRING_UNREF(BUTTON); + CSS_DOM_STRING_UNREF(INPUT); + CSS_DOM_STRING_UNREF(SELECT); + CSS_DOM_STRING_UNREF(TEXTAREA); + /* DOM input types, not really CSS */ + CSS_DOM_STRING_UNREF(button); + CSS_DOM_STRING_UNREF(image); + CSS_DOM_STRING_UNREF(radio); + CSS_DOM_STRING_UNREF(checkbox); + CSS_DOM_STRING_UNREF(file); + CSS_DOM_STRING_UNREF(on); + /* DOM userdata keys, not really CSS */ CSS_DOM_STRING_UNREF(__ns_key_libcss_node_data); CSS_DOM_STRING_UNREF(__ns_key_file_name_node_data); #undef CSS_DOM_STRING_UNREF @@ -725,6 +748,19 @@ nserror corestrings_init(void) CSS_DOM_STRING_INTERN(vspace); CSS_DOM_STRING_INTERN(waiting); CSS_DOM_STRING_INTERN(width); + /* DOM node names, not really CSS */ + CSS_DOM_STRING_INTERN(BUTTON); + CSS_DOM_STRING_INTERN(INPUT); + CSS_DOM_STRING_INTERN(SELECT); + CSS_DOM_STRING_INTERN(TEXTAREA); + /* DOM input types, not really CSS */ + CSS_DOM_STRING_INTERN(button); + CSS_DOM_STRING_INTERN(image); + CSS_DOM_STRING_INTERN(radio); + CSS_DOM_STRING_INTERN(checkbox); + CSS_DOM_STRING_INTERN(file); + CSS_DOM_STRING_INTERN(on); + /* DOM userdata keys, not really CSS */ CSS_DOM_STRING_INTERN(__ns_key_libcss_node_data); CSS_DOM_STRING_INTERN(__ns_key_file_name_node_data); #undef CSS_DOM_STRING_INTERN diff --git a/utils/corestrings.h b/utils/corestrings.h index a8c345946..839d86522 100644 --- a/utils/corestrings.h +++ b/utils/corestrings.h @@ -238,6 +238,21 @@ extern struct dom_string *corestring_dom_volumechange; extern struct dom_string *corestring_dom_vspace; extern struct dom_string *corestring_dom_waiting; extern struct dom_string *corestring_dom_width; +/* DOM node types */ +extern struct dom_string *corestring_dom_BUTTON; +extern struct dom_string *corestring_dom_INPUT; +extern struct dom_string *corestring_dom_SELECT; +extern struct dom_string *corestring_dom_TEXTAREA; +/* DOM input node types */ +extern struct dom_string *corestring_dom_button; +/* extern struct dom_string *corestring_dom_submit; */ +/* extern struct dom_string *corestring_dom_reset; */ +extern struct dom_string *corestring_dom_image; +extern struct dom_string *corestring_dom_radio; +extern struct dom_string *corestring_dom_checkbox; +extern struct dom_string *corestring_dom_file; +extern struct dom_string *corestring_dom_on; +/* DOM userdata keys */ extern struct dom_string *corestring_dom___ns_key_libcss_node_data; extern struct dom_string *corestring_dom___ns_key_file_name_node_data;