netsurf/content/handlers/html/box_special.c
Vincent Sanders ebe1b05114 improve desktop text search header usage
remove unecessary inclusion of desktop search header in content
  header which has knock on effect of not having ctype or string
  system headers dragged in unecessarily.

Futher this highlighted use of ctype API where internal ascii
  processing ought to be used.
2020-06-24 23:49:59 +01:00

1927 lines
44 KiB
C

/*
* Copyright 2005 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk>
* Copyright 2006 Richard Wilson <info@tinct.net>
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
* Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* \file
* Implementation of special element handling conversion.
*/
#include <string.h>
#include <stdbool.h>
#include <dom/dom.h>
#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 &lt;object&gt; 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|<named colour>" 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 &lt;iframe&gt; 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 &lt;option&gt;
* \param n xml element node for &lt;option&gt;
* \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
* <base target=...>, 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,
&params->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,
&params->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, &params->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, &params->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,
&params->codetype) == false)
return false;
if (box_get_attribute(n, "type", params, &params->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,
&param->name) == false) {
dom_node_unref(c);
return false;
}
if (box_get_attribute(c, "value", param,
&param->value) == false) {
dom_node_unref(c);
return false;
}
if (box_get_attribute(c, "type", param,
&param->type) == false) {
dom_node_unref(c);
return false;
}
if (box_get_attribute(c, "valuetype", param,
&param->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;
}