/* * Copyright 2005 James Bursa * Copyright 2003 Phil Mellor * Copyright 2005 John M Bell * Copyright 2006 Richard Wilson * Copyright 2008 Michael Drake * Copyright 2020 Vincent Sanders * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file * Implementation of special element handling conversion. */ #include #include #include #include "utils/nsoption.h" #include "utils/corestrings.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/talloc.h" #include "utils/string.h" #include "utils/ascii.h" #include "utils/nsurl.h" #include "netsurf/plot_style.h" #include "css/hints.h" #include "desktop/frame_types.h" #include "content/content_factory.h" #include "html/html.h" #include "html/private.h" #include "html/object.h" #include "html/box.h" #include "html/box_manipulate.h" #include "html/box_construct.h" #include "html/box_special.h" #include "html/box_textarea.h" #include "html/form_internal.h" static const content_type image_types = CONTENT_IMAGE; /** * determine if a box is the root node * * \param n node to check * \return true if node is root else false. */ static inline bool box_is_root(dom_node *n) { dom_node *parent; dom_node_type type; dom_exception err; err = dom_node_get_parent_node(n, &parent); if (err != DOM_NO_ERR) return false; if (parent != NULL) { err = dom_node_get_node_type(parent, &type); dom_node_unref(parent); if (err != DOM_NO_ERR) return false; if (type != DOM_DOCUMENT_NODE) return false; } return true; } /** * Destructor for object_params, for <object> elements * * \param o The object params being destroyed. * \return 0 to allow talloc to continue destroying the tree. */ static int box_object_talloc_destructor(struct object_params *o) { if (o->codebase != NULL) nsurl_unref(o->codebase); if (o->classid != NULL) nsurl_unref(o->classid); if (o->data != NULL) nsurl_unref(o->data); return 0; } /** * Parse a multi-length-list, as defined by HTML 4.01. * * \param ds dom string to parse * \param count updated to number of entries * \return array of struct box_multi_length, or 0 on memory exhaustion */ static struct frame_dimension * box_parse_multi_lengths(const dom_string *ds, unsigned int *count) { char *end; unsigned int i, n; struct frame_dimension *length; const char *s; s = dom_string_data(ds); for (i = 0, n = 1; s[i]; i++) if (s[i] == ',') n++; length = calloc(n, sizeof(struct frame_dimension)); if (!length) return NULL; for (i = 0; i != n; i++) { while (ascii_is_space(*s)) { s++; } length[i].value = strtof(s, &end); if (length[i].value <= 0) { length[i].value = 1; } s = end; switch (*s) { case '%': length[i].unit = FRAME_DIMENSION_PERCENT; break; case '*': length[i].unit = FRAME_DIMENSION_RELATIVE; break; default: length[i].unit = FRAME_DIMENSION_PIXELS; break; } while (*s && *s != ',') { s++; } if (*s == ',') { s++; } } *count = n; return length; } /** * Destructor for content_html_frames, for frame elements * * \param f The frame params being destroyed. * \return 0 to allow talloc to continue destroying the tree. */ static int box_frames_talloc_destructor(struct content_html_frames *f) { if (f->url != NULL) { nsurl_unref(f->url); f->url = NULL; } return 0; } /** * create a frameset box tree */ static bool box_create_frameset(struct content_html_frames *f, dom_node *n, html_content *content) { unsigned int row, col, index, i; unsigned int rows = 1, cols = 1; dom_string *s; dom_exception err; nsurl *url; struct frame_dimension *row_height = 0, *col_width = 0; dom_node *c, *next; struct content_html_frames *frame; bool default_border = true; colour default_border_colour = 0x000000; /* parse rows and columns */ err = dom_element_get_attribute(n, corestring_dom_rows, &s); if (err == DOM_NO_ERR && s != NULL) { row_height = box_parse_multi_lengths(s, &rows); dom_string_unref(s); if (row_height == NULL) return false; } else { row_height = calloc(1, sizeof(struct frame_dimension)); if (row_height == NULL) return false; row_height->value = 100; row_height->unit = FRAME_DIMENSION_PERCENT; } err = dom_element_get_attribute(n, corestring_dom_cols, &s); if (err == DOM_NO_ERR && s != NULL) { col_width = box_parse_multi_lengths(s, &cols); dom_string_unref(s); if (col_width == NULL) { free(row_height); return false; } } else { col_width = calloc(1, sizeof(struct frame_dimension)); if (col_width == NULL) { free(row_height); return false; } col_width->value = 100; col_width->unit = FRAME_DIMENSION_PERCENT; } /* common extension: border="0|1" to control all children */ err = dom_element_get_attribute(n, corestring_dom_border, &s); if (err == DOM_NO_ERR && s != NULL) { if ((dom_string_data(s)[0] == '0') && (dom_string_data(s)[1] == '\0')) default_border = false; dom_string_unref(s); } /* common extension: frameborder="yes|no" to control all children */ err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); if (err == DOM_NO_ERR && s != NULL) { if (dom_string_caseless_lwc_isequal(s, corestring_lwc_no) == 0) default_border = false; dom_string_unref(s); } /* common extension: bordercolor="#RRGGBB|" to control *all children */ err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); if (err == DOM_NO_ERR && s != NULL) { css_color color; if (nscss_parse_colour(dom_string_data(s), &color)) default_border_colour = nscss_color_to_ns(color); dom_string_unref(s); } /* update frameset and create default children */ f->cols = cols; f->rows = rows; f->scrolling = BW_SCROLLING_NO; f->children = talloc_array(content->bctx, struct content_html_frames, (rows * cols)); talloc_set_destructor(f->children, box_frames_talloc_destructor); for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { index = (row * cols) + col; frame = &f->children[index]; frame->cols = 0; frame->rows = 0; frame->width = col_width[col]; frame->height = row_height[row]; frame->margin_width = 0; frame->margin_height = 0; frame->name = NULL; frame->url = NULL; frame->no_resize = false; frame->scrolling = BW_SCROLLING_AUTO; frame->border = default_border; frame->border_colour = default_border_colour; frame->children = NULL; } } free(col_width); free(row_height); /* create the frameset windows */ err = dom_node_get_first_child(n, &c); if (err != DOM_NO_ERR) return false; for (row = 0; c != NULL && row < rows; row++) { for (col = 0; c != NULL && col < cols; col++) { while (c != NULL) { dom_node_type type; dom_string *name; err = dom_node_get_node_type(c, &type); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } err = dom_node_get_node_name(c, &name); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } if (type != DOM_ELEMENT_NODE || (!dom_string_caseless_lwc_isequal( name, corestring_lwc_frame) && !dom_string_caseless_lwc_isequal( name, corestring_lwc_frameset ))) { err = dom_node_get_next_sibling(c, &next); if (err != DOM_NO_ERR) { dom_string_unref(name); dom_node_unref(c); return false; } dom_string_unref(name); dom_node_unref(c); c = next; } else { /* Got a FRAME or FRAMESET element */ dom_string_unref(name); break; } } if (c == NULL) break; /* get current frame */ index = (row * cols) + col; frame = &f->children[index]; /* nest framesets */ err = dom_node_get_node_name(c, &s); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } if (dom_string_caseless_lwc_isequal(s, corestring_lwc_frameset)) { dom_string_unref(s); frame->border = 0; if (box_create_frameset(frame, c, content) == false) { dom_node_unref(c); return false; } err = dom_node_get_next_sibling(c, &next); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } dom_node_unref(c); c = next; continue; } dom_string_unref(s); /* get frame URL (not required) */ url = NULL; err = dom_element_get_attribute(c, corestring_dom_src, &s); if (err == DOM_NO_ERR && s != NULL) { box_extract_link(content, s, content->base_url, &url); dom_string_unref(s); } /* copy url */ if (url != NULL) { /* no self-references */ if (nsurl_compare(content->base_url, url, NSURL_COMPLETE) == false) frame->url = url; url = NULL; } /* fill in specified values */ err = dom_element_get_attribute(c, corestring_dom_name, &s); if (err == DOM_NO_ERR && s != NULL) { frame->name = talloc_strdup(content->bctx, dom_string_data(s)); dom_string_unref(s); } if (dom_element_has_attribute(c, corestring_dom_noresize, &frame->no_resize) != DOM_NO_ERR) { /* If we can't read the attribute for some reason, * assume we didn't have it. */ frame->no_resize = false; } err = dom_element_get_attribute(c, corestring_dom_frameborder, &s); if (err == DOM_NO_ERR && s != NULL) { i = atoi(dom_string_data(s)); frame->border = (i != 0); dom_string_unref(s); } err = dom_element_get_attribute(c, corestring_dom_scrolling, &s); if (err == DOM_NO_ERR && s != NULL) { if (dom_string_caseless_lwc_isequal(s, corestring_lwc_yes)) frame->scrolling = BW_SCROLLING_YES; else if (dom_string_caseless_lwc_isequal(s, corestring_lwc_no)) frame->scrolling = BW_SCROLLING_NO; dom_string_unref(s); } err = dom_element_get_attribute(c, corestring_dom_marginwidth, &s); if (err == DOM_NO_ERR && s != NULL) { frame->margin_width = atoi(dom_string_data(s)); dom_string_unref(s); } err = dom_element_get_attribute(c, corestring_dom_marginheight, &s); if (err == DOM_NO_ERR && s != NULL) { frame->margin_height = atoi(dom_string_data(s)); dom_string_unref(s); } err = dom_element_get_attribute(c, corestring_dom_bordercolor, &s); if (err == DOM_NO_ERR && s != NULL) { css_color color; if (nscss_parse_colour(dom_string_data(s), &color)) frame->border_colour = nscss_color_to_ns(color); dom_string_unref(s); } /* advance */ err = dom_node_get_next_sibling(c, &next); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } dom_node_unref(c); c = next; } } /* If the last child wasn't a frame, we still need to unref it */ if (c != NULL) { dom_node_unref(c); } return true; } /** * Destructor for content_html_iframe, for <iframe> elements * * \param f The iframe params being destroyed. * \return 0 to allow talloc to continue destroying the tree. */ static int box_iframes_talloc_destructor(struct content_html_iframe *f) { if (f->url != NULL) { nsurl_unref(f->url); f->url = NULL; } return 0; } /** * Get the value of a dom node element's attribute. * * \param n dom element node * \param attribute name of attribute * \param context talloc context for result buffer * \param value updated to value, if the attribute is present * \return true on success, false if attribute present but memory exhausted * * \note returning true does not imply that the attribute was found. If the * attribute was not found, *value will be unchanged. */ static bool box_get_attribute(dom_node *n, const char *attribute, void *context, char **value) { char *result; dom_string *attr, *attr_name; dom_exception err; err = dom_string_create_interned((const uint8_t *) attribute, strlen(attribute), &attr_name); if (err != DOM_NO_ERR) return false; err = dom_element_get_attribute(n, attr_name, &attr); if (err != DOM_NO_ERR) { dom_string_unref(attr_name); return false; } dom_string_unref(attr_name); if (attr != NULL) { result = talloc_strdup(context, dom_string_data(attr)); dom_string_unref(attr); if (result == NULL) return false; *value = result; } return true; } /** * Helper function for adding textarea widget to box. * * This is a load of hacks to ensure boxes replaced with textareas * can be handled by the layout code. */ static bool box_input_text(html_content *html, struct box *box, struct dom_node *node) { struct box *inline_container, *inline_box; box->type = BOX_INLINE_BLOCK; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); if (!inline_container) return false; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, html->bctx); if (!inline_box) return false; inline_box->type = BOX_TEXT; inline_box->text = talloc_strdup(html->bctx, ""); box_add_child(inline_container, inline_box); box_add_child(box, inline_container); return box_textarea_create_textarea(html, box, node); } /** * Add an option to a form select control (helper function for box_select()). * * \param control select containing the <option> * \param n xml element node for <option> * \return true on success, false on memory exhaustion */ static bool box_select_add_option(struct form_control *control, dom_node *n) { char *value = NULL; char *text = NULL; char *text_nowrap = NULL; bool selected; dom_string *content, *s; dom_exception err; err = dom_node_get_text_content(n, &content); if (err != DOM_NO_ERR) return false; if (content != NULL) { text = squash_whitespace(dom_string_data(content)); dom_string_unref(content); } else { text = strdup(""); } if (text == NULL) goto no_memory; err = dom_element_get_attribute(n, corestring_dom_value, &s); if (err == DOM_NO_ERR && s != NULL) { value = strdup(dom_string_data(s)); dom_string_unref(s); } else { value = strdup(text); } if (value == NULL) goto no_memory; if (dom_element_has_attribute(n, corestring_dom_selected, &selected) != DOM_NO_ERR) { /* Assume not selected if we can't read the attribute presence */ selected = false; } /* replace spaces/TABs with hard spaces to prevent line wrapping */ text_nowrap = cnv_space2nbsp(text); if (text_nowrap == NULL) goto no_memory; if (form_add_option(control, value, text_nowrap, selected, n) == false) goto no_memory; free(text); return true; no_memory: free(value); free(text); free(text_nowrap); return false; } /** * \name Special case element handlers * * These functions are called by box_construct_element() when an element is * being converted, according to the entries in element_table. * * The parameters are the xmlNode, the content for the document, and a partly * filled in box structure for the element. * * Return true on success, false on memory exhaustion. Set *convert_children * to false if children of this element in the XML tree should be skipped (for * example, if they have been processed in some special way already). * * Elements ordered as in the HTML 4.01 specification. Section numbers in * brackets [] refer to the spec. * * \{ */ /** * special element handler for Anchor [12.2]. */ static bool box_a(dom_node *n, html_content *content, struct box *box, bool *convert_children) { bool ok; nsurl *url; dom_string *s; dom_exception err; err = dom_element_get_attribute(n, corestring_dom_href, &s); if (err == DOM_NO_ERR && s != NULL) { ok = box_extract_link(content, s, content->base_url, &url); dom_string_unref(s); if (!ok) return false; if (url) { if (box->href != NULL) nsurl_unref(box->href); box->href = url; } } /* name and id share the same namespace */ err = dom_element_get_attribute(n, corestring_dom_name, &s); if (err == DOM_NO_ERR && s != NULL) { lwc_string *lwc_name; err = dom_string_intern(s, &lwc_name); dom_string_unref(s); if (err == DOM_NO_ERR) { /* name replaces existing id * TODO: really? */ if (box->id != NULL) lwc_string_unref(box->id); box->id = lwc_name; } } /* target frame [16.3] */ err = dom_element_get_attribute(n, corestring_dom_target, &s); if (err == DOM_NO_ERR && s != NULL) { if (dom_string_caseless_lwc_isequal(s, corestring_lwc__blank)) box->target = "_blank"; else if (dom_string_caseless_lwc_isequal(s, corestring_lwc__top)) box->target = "_top"; else if (dom_string_caseless_lwc_isequal(s, corestring_lwc__parent)) box->target = "_parent"; else if (dom_string_caseless_lwc_isequal(s, corestring_lwc__self)) /* the default may have been overridden by a * , so this is different to 0 */ box->target = "_self"; else { /* 6.16 says that frame names must begin with [a-zA-Z] * This doesn't match reality, so just take anything */ box->target = talloc_strdup(content->bctx, dom_string_data(s)); if (!box->target) { dom_string_unref(s); return false; } } dom_string_unref(s); } return true; } /** * Document body special element handler [7.5.1]. */ static bool box_body(dom_node *n, html_content *content, struct box *box, bool *convert_children) { css_color color; css_computed_background_color(box->style, &color); if (nscss_color_is_transparent(color)) { content->background_colour = NS_TRANSPARENT; } else { content->background_colour = nscss_color_to_ns(color); } return true; } /** * special element handler for forced line break [9.3.2]. */ static bool box_br(dom_node *n, html_content *content, struct box *box, bool *convert_children) { box->type = BOX_BR; return true; } /** * special element handler for Push button [17.5]. */ static bool box_button(dom_node *n, html_content *content, struct box *box, bool *convert_children) { struct form_control *gadget; gadget = html_forms_get_control_for_node(content->forms, n); if (!gadget) return false; gadget->html = content; box->gadget = gadget; box->flags |= IS_REPLACED; gadget->box = box; box->type = BOX_INLINE_BLOCK; /* Just render the contents */ return true; } /** * Canvas element */ static bool box_canvas(dom_node *n, html_content *content, struct box *box, bool *convert_children) { /* If scripting is not enabled display the contents of canvas */ if (!content->enable_scripting) { return true; } *convert_children = false; if (box->style && ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) return true; /* This is replaced content */ box->flags |= IS_REPLACED | REPLACE_DIM; return true; } /** * Embedded object (not in any HTML specification: * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) */ static bool box_embed(dom_node *n, html_content *content, struct box *box, bool *convert_children) { struct object_params *params; struct object_param *param; dom_namednodemap *attrs; unsigned long idx; uint32_t num_attrs; dom_string *src; dom_exception err; if (box->style && ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) return true; params = talloc(content->bctx, struct object_params); if (params == NULL) return false; talloc_set_destructor(params, box_object_talloc_destructor); params->data = NULL; params->type = NULL; params->codetype = NULL; params->codebase = NULL; params->classid = NULL; params->params = NULL; /* src is a URL */ err = dom_element_get_attribute(n, corestring_dom_src, &src); if (err != DOM_NO_ERR || src == NULL) return true; if (box_extract_link(content, src, content->base_url, ¶ms->data) == false) { dom_string_unref(src); return false; } dom_string_unref(src); if (params->data == NULL) return true; /* Don't include ourself */ if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) return true; /* add attributes as parameters to linked list */ err = dom_node_get_attributes(n, &attrs); if (err != DOM_NO_ERR) return false; err = dom_namednodemap_get_length(attrs, &num_attrs); if (err != DOM_NO_ERR) { dom_namednodemap_unref(attrs); return false; } for (idx = 0; idx < num_attrs; idx++) { dom_attr *attr; dom_string *name, *value; err = dom_namednodemap_item(attrs, idx, (void *) &attr); if (err != DOM_NO_ERR) { dom_namednodemap_unref(attrs); return false; } err = dom_attr_get_name(attr, &name); if (err != DOM_NO_ERR) { dom_node_unref(attr); dom_namednodemap_unref(attrs); return false; } if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { dom_node_unref(attr); dom_string_unref(name); continue; } err = dom_attr_get_value(attr, &value); if (err != DOM_NO_ERR) { dom_node_unref(attr); dom_string_unref(name); dom_namednodemap_unref(attrs); return false; } param = talloc(content->bctx, struct object_param); if (param == NULL) { dom_node_unref(attr); dom_string_unref(value); dom_string_unref(name); dom_namednodemap_unref(attrs); return false; } param->name = talloc_strdup(content->bctx, dom_string_data(name)); param->value = talloc_strdup(content->bctx, dom_string_data(value)); param->type = NULL; param->valuetype = talloc_strdup(content->bctx, "data"); param->next = NULL; dom_string_unref(value); dom_string_unref(name); dom_node_unref(attr); if (param->name == NULL || param->value == NULL || param->valuetype == NULL) { dom_namednodemap_unref(attrs); return false; } param->next = params->params; params->params = param; } dom_namednodemap_unref(attrs); box->object_params = params; /* start fetch */ box->flags |= IS_REPLACED; return html_fetch_object(content, params->data, box, CONTENT_ANY, false); } /** * Window subdivision [16.2.1]. */ static bool box_frameset(dom_node *n, html_content *content, struct box *box, bool *convert_children) { bool ok; if (content->frameset) { NSLOG(netsurf, INFO, "Error: multiple framesets in document."); /* Don't convert children */ if (convert_children) *convert_children = false; /* And ignore this spurious frameset */ box->type = BOX_NONE; return true; } content->frameset = talloc_zero(content->bctx, struct content_html_frames); if (!content->frameset) { return false; } ok = box_create_frameset(content->frameset, n, content); if (ok) { box->type = BOX_NONE; } if (convert_children) { *convert_children = false; } return ok; } /** * Inline subwindow [16.5]. */ static bool box_iframe(dom_node *n, html_content *content, struct box *box, bool *convert_children) { nsurl *url; dom_string *s; dom_exception err; struct content_html_iframe *iframe; int i; if (box->style && ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) return true; if (box->style && css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) { /* Don't create iframe discriptors for invisible iframes * TODO: handle hidden iframes at browser_window generation * time instead? */ return true; } /* get frame URL */ err = dom_element_get_attribute(n, corestring_dom_src, &s); if (err != DOM_NO_ERR || s == NULL) return true; if (box_extract_link(content, s, content->base_url, &url) == false) { dom_string_unref(s); return false; } dom_string_unref(s); if (url == NULL) return true; /* don't include ourself */ if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { nsurl_unref(url); return true; } /* create a new iframe */ iframe = talloc(content->bctx, struct content_html_iframe); if (iframe == NULL) { nsurl_unref(url); return false; } talloc_set_destructor(iframe, box_iframes_talloc_destructor); iframe->box = box; iframe->margin_width = 0; iframe->margin_height = 0; iframe->name = NULL; iframe->url = url; iframe->scrolling = BW_SCROLLING_AUTO; iframe->border = true; /* Add this iframe to the linked list of iframes */ iframe->next = content->iframe; content->iframe = iframe; /* fill in specified values */ err = dom_element_get_attribute(n, corestring_dom_name, &s); if (err == DOM_NO_ERR && s != NULL) { iframe->name = talloc_strdup(content->bctx, dom_string_data(s)); dom_string_unref(s); } err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); if (err == DOM_NO_ERR && s != NULL) { i = atoi(dom_string_data(s)); iframe->border = (i != 0); dom_string_unref(s); } err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); if (err == DOM_NO_ERR && s != NULL) { css_color color; if (nscss_parse_colour(dom_string_data(s), &color)) iframe->border_colour = nscss_color_to_ns(color); dom_string_unref(s); } err = dom_element_get_attribute(n, corestring_dom_scrolling, &s); if (err == DOM_NO_ERR && s != NULL) { if (dom_string_caseless_lwc_isequal(s, corestring_lwc_yes)) iframe->scrolling = BW_SCROLLING_YES; else if (dom_string_caseless_lwc_isequal(s, corestring_lwc_no)) iframe->scrolling = BW_SCROLLING_NO; dom_string_unref(s); } err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s); if (err == DOM_NO_ERR && s != NULL) { iframe->margin_width = atoi(dom_string_data(s)); dom_string_unref(s); } err = dom_element_get_attribute(n, corestring_dom_marginheight, &s); if (err == DOM_NO_ERR && s != NULL) { iframe->margin_height = atoi(dom_string_data(s)); dom_string_unref(s); } /* box */ assert(box->style); box->flags |= IFRAME; box->flags |= IS_REPLACED; /* Showing iframe, so don't show alternate content */ if (convert_children) *convert_children = false; return true; } /** * Embedded image [13.2]. */ static bool box_image(dom_node *n, html_content *content, struct box *box, bool *convert_children) { bool ok; dom_string *s; dom_exception err; nsurl *url; enum css_width_e wtype; enum css_height_e htype; css_fixed value = 0; css_unit wunit = CSS_UNIT_PX; css_unit hunit = CSS_UNIT_PX; if (box->style && ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) return true; /* handle alt text */ err = dom_element_get_attribute(n, corestring_dom_alt, &s); if (err == DOM_NO_ERR && s != NULL) { char *alt = squash_whitespace(dom_string_data(s)); dom_string_unref(s); if (alt == NULL) return false; box->text = talloc_strdup(content->bctx, alt); free(alt); if (box->text == NULL) return false; box->length = strlen(box->text); } if (nsoption_bool(foreground_images) == false) { return true; } /* imagemap associated with this image */ if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) return false; if (box->usemap && box->usemap[0] == '#') box->usemap++; /* get image URL */ err = dom_element_get_attribute(n, corestring_dom_src, &s); if (err != DOM_NO_ERR || s == NULL) return true; if (box_extract_link(content, s, content->base_url, &url) == false) { dom_string_unref(s); return false; } dom_string_unref(s); if (url == NULL) return true; /* start fetch */ box->flags |= IS_REPLACED; ok = html_fetch_object(content, url, box, image_types, false); nsurl_unref(url); wtype = css_computed_width(box->style, &value, &wunit); htype = css_computed_height(box->style, &value, &hunit); if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { /* We know the dimensions the image will be shown at * before it's fetched. */ box->flags |= REPLACE_DIM; } return ok; } /** * Form control [17.4]. */ static bool box_input(dom_node *n, html_content *content, struct box *box, bool *convert_children) { struct form_control *gadget; dom_string *type = NULL; dom_exception err; nsurl *url; nserror error; gadget = html_forms_get_control_for_node(content->forms, n); if (gadget == NULL) { return false; } box->gadget = gadget; box->flags |= IS_REPLACED; gadget->box = box; gadget->html = content; /* get entry type */ err = dom_element_get_attribute(n, corestring_dom_type, &type); if ((err != DOM_NO_ERR) || (type == NULL)) { /* no type so "text" is assumed */ if (box_input_text(content, box, n) == false) { return false; } *convert_children = false; return true; } if (dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { if (box_input_text(content, box, n) == false) goto no_memory; } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_file)) { box->type = BOX_INLINE_BLOCK; } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_hidden)) { /* no box for hidden inputs */ box->type = BOX_NONE; } else if ((dom_string_caseless_lwc_isequal(type, corestring_lwc_checkbox) || dom_string_caseless_lwc_isequal(type, corestring_lwc_radio))) { } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_submit) || dom_string_caseless_lwc_isequal(type, corestring_lwc_reset) || dom_string_caseless_lwc_isequal(type, corestring_lwc_button)) { struct box *inline_container, *inline_box; if (box_button(n, content, box, 0) == false) goto no_memory; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); if (inline_container == NULL) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, content->bctx); if (inline_box == NULL) goto no_memory; inline_box->type = BOX_TEXT; if (box->gadget->value != NULL) inline_box->text = talloc_strdup(content->bctx, box->gadget->value); else if (box->gadget->type == GADGET_SUBMIT) inline_box->text = talloc_strdup(content->bctx, messages_get("Form_Submit")); else if (box->gadget->type == GADGET_RESET) inline_box->text = talloc_strdup(content->bctx, messages_get("Form_Reset")); else inline_box->text = talloc_strdup(content->bctx, "Button"); if (inline_box->text == NULL) 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 (dom_string_caseless_lwc_isequal(type, corestring_lwc_image)) { gadget->type = GADGET_IMAGE; if (box->style && ns_computed_display(box->style, box_is_root(n)) != CSS_DISPLAY_NONE && nsoption_bool(foreground_images) == true) { dom_string *s; err = dom_element_get_attribute(n, corestring_dom_src, &s); if (err == DOM_NO_ERR && s != NULL) { error = nsurl_join(content->base_url, dom_string_data(s), &url); dom_string_unref(s); if (error != NSERROR_OK) goto no_memory; /* if url is equivalent to the parent's url, * we've got infinite inclusion. stop it here */ if (nsurl_compare(url, content->base_url, NSURL_COMPLETE) == false) { if (!html_fetch_object(content, url, box, image_types, false)) { nsurl_unref(url); goto no_memory; } } nsurl_unref(url); } } } else { /* unhandled type the default is "text" */ if (box_input_text(content, box, n) == false) goto no_memory; } dom_string_unref(type); *convert_children = false; return true; no_memory: dom_string_unref(type); return false; } /** * Noscript element */ static bool box_noscript(dom_node *n, html_content *content, struct box *box, bool *convert_children) { /* If scripting is enabled, do not display the contents of noscript */ if (content->enable_scripting) { *convert_children = false; } return true; } /** * Generic embedded object [13.3]. */ static bool box_object(dom_node *n, html_content *content, struct box *box, bool *convert_children) { struct object_params *params; struct object_param *param; dom_string *codebase, *classid, *data; dom_node *c; dom_exception err; if (box->style && ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) return true; if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == false) return false; if (box->usemap && box->usemap[0] == '#') box->usemap++; params = talloc(content->bctx, struct object_params); if (params == NULL) return false; talloc_set_destructor(params, box_object_talloc_destructor); params->data = NULL; params->type = NULL; params->codetype = NULL; params->codebase = NULL; params->classid = NULL; params->params = NULL; /* codebase, classid, and data are URLs * (codebase is the base for the other two) */ err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); if (err == DOM_NO_ERR && codebase != NULL) { if (box_extract_link(content, codebase, content->base_url, ¶ms->codebase) == false) { dom_string_unref(codebase); return false; } dom_string_unref(codebase); } if (params->codebase == NULL) params->codebase = nsurl_ref(content->base_url); err = dom_element_get_attribute(n, corestring_dom_classid, &classid); if (err == DOM_NO_ERR && classid != NULL) { if (box_extract_link(content, classid, params->codebase, ¶ms->classid) == false) { dom_string_unref(classid); return false; } dom_string_unref(classid); } err = dom_element_get_attribute(n, corestring_dom_data, &data); if (err == DOM_NO_ERR && data != NULL) { if (box_extract_link(content, data, params->codebase, ¶ms->data) == false) { dom_string_unref(data); return false; } dom_string_unref(data); } if (params->classid == NULL && params->data == NULL) /* nothing to embed; ignore */ return true; /* Don't include ourself */ if (params->classid != NULL && nsurl_compare(content->base_url, params->classid, NSURL_COMPLETE)) return true; if (params->data != NULL && nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) return true; /* codetype and type are MIME types */ if (box_get_attribute(n, "codetype", params, ¶ms->codetype) == false) return false; if (box_get_attribute(n, "type", params, ¶ms->type) == false) return false; /* classid && !data => classid is used (consult codetype) * (classid || !classid) && data => data is used (consult type) * !classid && !data => invalid; ignored */ if (params->classid != NULL && params->data == NULL && params->codetype != NULL) { lwc_string *icodetype; lwc_error lerror; lerror = lwc_intern_string(params->codetype, strlen(params->codetype), &icodetype); if (lerror != lwc_error_ok) return false; if (content_factory_type_from_mime_type(icodetype) == CONTENT_NONE) { /* can't handle this MIME type */ lwc_string_unref(icodetype); return true; } lwc_string_unref(icodetype); } if (params->data != NULL && params->type != NULL) { lwc_string *itype; lwc_error lerror; lerror = lwc_intern_string(params->type, strlen(params->type), &itype); if (lerror != lwc_error_ok) return false; if (content_factory_type_from_mime_type(itype) == CONTENT_NONE) { /* can't handle this MIME type */ lwc_string_unref(itype); return true; } lwc_string_unref(itype); } /* add parameters to linked list */ err = dom_node_get_first_child(n, &c); if (err != DOM_NO_ERR) return false; while (c != NULL) { dom_node *next; dom_node_type type; err = dom_node_get_node_type(c, &type); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } if (type == DOM_ELEMENT_NODE) { dom_string *name; err = dom_node_get_node_name(c, &name); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } if (!dom_string_caseless_lwc_isequal(name, corestring_lwc_param)) { /* The first non-param child is the start of * the alt html. Therefore, we should break * out of this loop. */ dom_string_unref(name); dom_node_unref(c); break; } dom_string_unref(name); param = talloc(params, struct object_param); if (param == NULL) { dom_node_unref(c); return false; } param->name = NULL; param->value = NULL; param->type = NULL; param->valuetype = NULL; param->next = NULL; if (box_get_attribute(c, "name", param, ¶m->name) == false) { dom_node_unref(c); return false; } if (box_get_attribute(c, "value", param, ¶m->value) == false) { dom_node_unref(c); return false; } if (box_get_attribute(c, "type", param, ¶m->type) == false) { dom_node_unref(c); return false; } if (box_get_attribute(c, "valuetype", param, ¶m->valuetype) == false) { dom_node_unref(c); return false; } if (param->valuetype == NULL) { param->valuetype = talloc_strdup(param, "data"); if (param->valuetype == NULL) { dom_node_unref(c); return false; } } param->next = params->params; params->params = param; } err = dom_node_get_next_sibling(c, &next); if (err != DOM_NO_ERR) { dom_node_unref(c); return false; } dom_node_unref(c); c = next; } box->object_params = params; /* start fetch (MIME type is ok or not specified) */ box->flags |= IS_REPLACED; if (!html_fetch_object(content, params->data ? params->data : params->classid, box, CONTENT_ANY, false)) return false; *convert_children = false; return true; } /** * Preformatted text [9.3.4]. */ static bool box_pre(dom_node *n, html_content *content, struct box *box, bool *convert_children) { box->flags |= PRE_STRIP; return true; } /** * Option selector [17.6]. */ static bool box_select(dom_node *n, html_content *content, struct box *box, bool *convert_children) { struct box *inline_container; struct box *inline_box; struct form_control *gadget; dom_node *c, *c2; dom_node *next, *next2; dom_exception err; gadget = html_forms_get_control_for_node(content->forms, n); if (gadget == NULL) return false; gadget->html = content; err = dom_node_get_first_child(n, &c); if (err != DOM_NO_ERR) { form_free_control(gadget); return false; } while (c != NULL) { dom_string *name; err = dom_node_get_node_name(c, &name); if (err != DOM_NO_ERR) { dom_node_unref(c); form_free_control(gadget); return false; } if (dom_string_caseless_lwc_isequal(name, corestring_lwc_option)) { dom_string_unref(name); if (box_select_add_option(gadget, c) == false) { dom_node_unref(c); form_free_control(gadget); return false; } } else if (dom_string_caseless_lwc_isequal(name, corestring_lwc_optgroup)) { dom_string_unref(name); err = dom_node_get_first_child(c, &c2); if (err != DOM_NO_ERR) { dom_node_unref(c); form_free_control(gadget); return false; } while (c2 != NULL) { dom_string *c2_name; err = dom_node_get_node_name(c2, &c2_name); if (err != DOM_NO_ERR) { dom_node_unref(c2); dom_node_unref(c); form_free_control(gadget); return false; } if (dom_string_caseless_lwc_isequal(c2_name, corestring_lwc_option)) { dom_string_unref(c2_name); if (box_select_add_option(gadget, c2) == false) { dom_node_unref(c2); dom_node_unref(c); form_free_control(gadget); return false; } } else { dom_string_unref(c2_name); } err = dom_node_get_next_sibling(c2, &next2); if (err != DOM_NO_ERR) { dom_node_unref(c2); dom_node_unref(c); form_free_control(gadget); return false; } dom_node_unref(c2); c2 = next2; } } else { dom_string_unref(name); } err = dom_node_get_next_sibling(c, &next); if (err != DOM_NO_ERR) { dom_node_unref(c); form_free_control(gadget); return false; } dom_node_unref(c); c = next; } if (gadget->data.select.num_items == 0) { /* no options: ignore entire select */ form_free_control(gadget); return true; } box->type = BOX_INLINE_BLOCK; box->gadget = gadget; box->flags |= IS_REPLACED; gadget->box = box; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); if (inline_container == NULL) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, content->bctx); if (inline_box == NULL) goto no_memory; inline_box->type = BOX_TEXT; box_add_child(inline_container, inline_box); box_add_child(box, inline_container); if (gadget->data.select.multiple == false && gadget->data.select.num_selected == 0) { gadget->data.select.current = gadget->data.select.items; gadget->data.select.current->initial_selected = gadget->data.select.current->selected = true; gadget->data.select.num_selected = 1; dom_html_option_element_set_selected( gadget->data.select.current->node, true); } if (gadget->data.select.num_selected == 0) inline_box->text = talloc_strdup(content->bctx, messages_get("Form_None")); else if (gadget->data.select.num_selected == 1) inline_box->text = talloc_strdup(content->bctx, gadget->data.select.current->text); else inline_box->text = talloc_strdup(content->bctx, messages_get("Form_Many")); if (inline_box->text == NULL) goto no_memory; inline_box->length = strlen(inline_box->text); *convert_children = false; return true; no_memory: return false; } /** * Multi-line text field [17.7]. */ static bool box_textarea(dom_node *n, html_content *content, struct box *box, bool *convert_children) { /* Get the form_control for the DOM node */ box->gadget = html_forms_get_control_for_node(content->forms, n); if (box->gadget == NULL) return false; box->flags |= IS_REPLACED; box->gadget->html = content; box->gadget->box = box; if (!box_input_text(content, box, n)) return false; *convert_children = false; return true; } /** * \} */ /* exported interface documented in html/box_special.h */ bool convert_special_elements(dom_node *node, html_content *content, struct box *box, bool *convert_children) { dom_exception exc; dom_html_element_type tag_type; bool res; exc = dom_html_element_get_tag_type(node, &tag_type); if (exc != DOM_NO_ERR) { tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; } switch (tag_type) { case DOM_HTML_ELEMENT_TYPE_A: res = box_a(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_BODY: res = box_body(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_BR: res = box_br(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_BUTTON: res = box_button(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_CANVAS: res = box_canvas(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_EMBED: res = box_embed(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_FRAMESET: res = box_frameset(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_IFRAME: res = box_iframe(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_IMG: res = box_image(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_INPUT: res = box_input(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_NOSCRIPT: res = box_noscript(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_OBJECT: res = box_object(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_PRE: res = box_pre(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_SELECT: res = box_select(node, content, box, convert_children); break; case DOM_HTML_ELEMENT_TYPE_TEXTAREA: res = box_textarea(node, content, box, convert_children); break; default: res = true; } return res; }