1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-11-04 08:17:17 -05:00
elinks/src/dom/node.c
Kalle Olavi Niemitalo 8465b19d0c Bug 1071: Fix null-ptr crash in get_dom_node_child
struct dom_node contains a union that contains various structs that
have members of type struct dom_node * in them.
get_dom_node_list_by_type returns the address (struct dom_node **) of
one of those members, or NULL.  However the member itself can also be
NULL if no nodes have been added to the list and the list has thus not
yet been allocated.  (add_to_dom_node_list lazily allocates the lists.)
get_dom_node_child did not expect a null pointer there and crashed, as
shown in bug 1071.  Fix by adding a check so that it treats a NULL list
as an empty list.
2009-04-04 21:56:53 +03:00

572 lines
13 KiB
C

/* The DOM node handling */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "dom/node.h"
#include "dom/string.h"
#include "util/hash.h"
#include "util/memory.h"
static void done_dom_node_data(struct dom_node *node);
/* Node lists */
#define DOM_NODE_LIST_GRANULARITY 0x7
#define DOM_NODE_LIST_BLOCK_SIZE \
(ALIGN_MEMORY_SIZE(1, DOM_NODE_LIST_GRANULARITY) * sizeof(struct dom_node *))
/* The node list struct has one node pointer */
#define DOM_NODE_LIST_SIZE(size) \
((size - 1) * sizeof(struct dom_node *) + sizeof(struct dom_node_list))
static inline struct dom_node_list *
realloc_dom_node_list(struct dom_node_list **oldlist)
{
struct dom_node_list *list = *oldlist;
size_t size = list ? list->size : 0;
size_t oldsize = ALIGN_MEMORY_SIZE(size, DOM_NODE_LIST_GRANULARITY);
size_t newsize = ALIGN_MEMORY_SIZE(size + 1, DOM_NODE_LIST_GRANULARITY);
if (newsize <= oldsize) return list;
list = mem_realloc(list, DOM_NODE_LIST_SIZE(newsize));
if (!list) return NULL;
/* If this is the first reallocation clear the size */
if (!size) list->size = 0;
/* Clear the new block of entries */
memset(&list->entries[oldsize], 0, DOM_NODE_LIST_BLOCK_SIZE);
*oldlist = list;
return list;
}
struct dom_node_list *
add_to_dom_node_list(struct dom_node_list **list_ptr,
struct dom_node *node, int position)
{
struct dom_node_list *list;
assert(list_ptr && node);
list = realloc_dom_node_list(list_ptr);
if (!list) return NULL;
assertm(position < 0 || position <= list->size,
"position out of bound %d > %zu", position, list->size);
if (position < 0) {
position = list->size;
} else if (position < list->size) {
/* Make room if we have to add the node in the middle of the list */
struct dom_node **offset = &list->entries[position];
size_t size = (list->size - position) * sizeof(*offset);
memmove(offset + 1, offset, size);
}
list->size++;
list->entries[position] = node;
return list;
}
static void
del_from_dom_node_list(struct dom_node_list *list, struct dom_node *node)
{
struct dom_node *entry;
size_t i;
if (!list) return;
foreach_dom_node (list, entry, i) {
size_t successors;
if (entry != node) continue;
successors = list->size - (i + 1);
if (successors)
memmove(&list->entries[i], &list->entries[i+1],
sizeof(*list->entries) * successors);
list->size--;
}
}
void
done_dom_node_list(struct dom_node_list *list)
{
struct dom_node *node;
int i;
assert(list);
foreach_dom_node (list, node, i) {
/* Avoid that the node start messing with the node list. */
done_dom_node_data(node);
}
mem_free(list);
}
/* Node map */
struct dom_node_search {
struct dom_node *key;
unsigned int from, pos, to;
};
#define INIT_DOM_NODE_SEARCH(key, list) \
{ (key), -1, 0, (list)->size, }
int
dom_node_casecmp(struct dom_node *node1, struct dom_node *node2)
{
if (node1->type == node2->type) {
switch (node1->type) {
case DOM_NODE_ELEMENT:
if (node1->data.element.type && node2->data.element.type)
return node1->data.element.type - node2->data.element.type;
break;
case DOM_NODE_ATTRIBUTE:
if (node1->data.attribute.type && node2->data.attribute.type)
return node1->data.attribute.type - node2->data.attribute.type;
break;
default:
break;
}
}
return dom_string_casecmp(&node1->string, &node2->string);
}
static inline int
get_bsearch_position(struct dom_node_list *list, int from, int to)
{
int pos = from + ((to - from) / 2);
assertm(0 <= pos && pos < list->size, "pos %d", pos);
return pos;
}
#define has_bsearch_node(from, to) ((from) + 1 < (to))
static inline struct dom_node *
dom_node_list_bsearch(struct dom_node_search *search, struct dom_node_list *list)
{
assert(has_bsearch_node(search->from, search->to));
do {
int pos = get_bsearch_position(list, search->from, search->to);
struct dom_node *node = list->entries[pos];
int difference = dom_node_casecmp(search->key, node);
search->pos = pos;
if (!difference) return node;
if (difference < 0) {
search->to = search->pos;
} else {
search->from = search->pos;
}
} while (has_bsearch_node(search->from, search->to));
return NULL;
}
int
get_dom_node_map_index(struct dom_node_list *list, struct dom_node *node)
{
struct dom_node_search search = INIT_DOM_NODE_SEARCH(node, list);
struct dom_node *match = dom_node_list_bsearch(&search, list);
return match ? search.pos : search.to;
}
struct dom_node *
get_dom_node_map_entry(struct dom_node_list *list, enum dom_node_type type,
uint16_t subtype, struct dom_string *name)
{
struct dom_node node = { type, 0, INIT_DOM_STRING(name->string, name->length) };
struct dom_node_search search = INIT_DOM_NODE_SEARCH(&node, list);
if (subtype) {
/* Set the subtype */
switch (type) {
case DOM_NODE_ELEMENT:
node.data.element.type = subtype;
break;
case DOM_NODE_ATTRIBUTE:
node.data.attribute.type = subtype;
break;
case DOM_NODE_PROCESSING_INSTRUCTION:
node.data.proc_instruction.type = subtype;
break;
default:
break;
}
}
return dom_node_list_bsearch(&search, list);
}
static int
get_dom_node_list_pos(struct dom_node_list *list, struct dom_node *node)
{
struct dom_node *entry;
int i;
assert(list);
foreach_dom_node (list, entry, i) {
if (entry == node)
return i;
}
return -1;
}
int
get_dom_node_list_index(struct dom_node *parent, struct dom_node *node)
{
struct dom_node_list **list = get_dom_node_list(parent, node);
return list ? get_dom_node_list_pos(*list, node) : -1;
}
struct dom_node *
get_dom_node_prev(struct dom_node *node)
{
struct dom_node_list **list;
int index;
assert(node->parent);
list = get_dom_node_list(node->parent, node);
if (!list) return NULL;
index = get_dom_node_list_pos(*list, node);
if (index > 0)
return (*list)->entries[index - 1];
return NULL;
}
struct dom_node *
get_dom_node_next(struct dom_node *node)
{
struct dom_node_list **list;
int index;
assert(node->parent);
list = get_dom_node_list(node->parent, node);
if (!list) return NULL;
index = get_dom_node_list_pos(*list, node);
if (index + 1 < (*list)->size)
return (*list)->entries[index + 1];
return NULL;
}
struct dom_node *
get_dom_node_child(struct dom_node *parent, enum dom_node_type type,
int16_t subtype)
{
struct dom_node_list **list;
struct dom_node *node;
int index;
list = get_dom_node_list_by_type(parent, type);
if (!list) return NULL; /* parent doesn't support this type */
if (!*list) return NULL; /* list is empty and not yet allocated */
foreach_dom_node (*list, node, index) {
if (node->type != type)
continue;
if (!subtype) return node;
switch (type) {
case DOM_NODE_ELEMENT:
if (node->data.element.type == subtype)
return node;
break;
case DOM_NODE_ATTRIBUTE:
if (node->data.attribute.type == subtype)
return node;
break;
case DOM_NODE_PROCESSING_INSTRUCTION:
if (node->data.attribute.type == subtype)
return node;
break;
default:
return node;
}
}
return NULL;
}
/* Nodes */
struct dom_node *
init_dom_node_at(
#ifdef DEBUG_MEMLEAK
unsigned char *file, int line,
#endif
struct dom_node *parent, enum dom_node_type type,
struct dom_string *string, int allocated)
{
#ifdef DEBUG_MEMLEAK
struct dom_node *node = debug_mem_calloc(file, line, 1, sizeof(*node));
#else
struct dom_node *node = mem_calloc(1, sizeof(*node));
#endif
if (!node) return NULL;
node->type = type;
node->parent = parent;
/* Make it possible to add a node to a parent without allocating the
* strings. */
if (allocated >= 0) {
node->allocated = !!allocated;
} else if (parent) {
node->allocated = parent->allocated;
}
if (node->allocated) {
if (!init_dom_string(&node->string, string->string, string->length)) {
done_dom_node(node);
return NULL;
}
} else {
copy_dom_string(&node->string, string);
}
if (parent) {
struct dom_node_list **list = get_dom_node_list(parent, node);
int sort = (type == DOM_NODE_ATTRIBUTE);
int index;
assertm(list != NULL, "Adding node %d to bad parent %d",
node->type, parent->type);
index = *list && (*list)->size > 0 && sort
? get_dom_node_map_index(*list, node) : -1;
if (!add_to_dom_node_list(list, node, index)) {
done_dom_node(node);
return NULL;
}
}
return node;
}
void
done_dom_node_data(struct dom_node *node)
{
union dom_node_data *data;
assert(node);
assert(node->type < DOM_NODES); /* unsigned comparison */
data = &node->data;
switch (node->type) {
case DOM_NODE_ATTRIBUTE:
if (node->allocated)
done_dom_string(&data->attribute.value);
break;
case DOM_NODE_DOCUMENT:
if (data->document.children)
done_dom_node_list(data->document.children);
break;
case DOM_NODE_ELEMENT:
if (data->element.children)
done_dom_node_list(data->element.children);
if (data->element.map)
done_dom_node_list(data->element.map);
break;
case DOM_NODE_PROCESSING_INSTRUCTION:
if (data->proc_instruction.map)
done_dom_node_list(data->proc_instruction.map);
if (node->allocated)
done_dom_string(&data->proc_instruction.instruction);
break;
default:
break;
}
if (node->allocated)
done_dom_string(&node->string);
/* call_dom_stack_callbacks() asserts that the node type is
* within range. If assertions are enabled, store an
* out-of-range value there to make the assertion more likely
* to fail if a callback has freed the node prematurely.
* This is not entirely reliable though, because the memory
* is freed below and may be allocated for some other purpose
* before the assertion. */
#ifndef CONFIG_FASTMEM
node->type = -1;
#endif
mem_free(node);
}
void
done_dom_node(struct dom_node *node)
{
assert(node);
if (node->parent) {
struct dom_node *parent = node->parent;
union dom_node_data *data = &parent->data;
switch (parent->type) {
case DOM_NODE_DOCUMENT:
del_from_dom_node_list(data->document.children, node);
break;
case DOM_NODE_ELEMENT:
del_from_dom_node_list(data->element.children, node);
del_from_dom_node_list(data->element.map, node);
break;
case DOM_NODE_PROCESSING_INSTRUCTION:
del_from_dom_node_list(data->proc_instruction.map, node);
break;
default:
break;
}
}
done_dom_node_data(node);
}
#define set_node_name(name, namelen, str) \
do { (name) = (str); (namelen) = sizeof(str) - 1; } while (0)
struct dom_string *
get_dom_node_name(struct dom_node *node)
{
static struct dom_string cdata_section_str = STATIC_DOM_STRING("#cdata-section");
static struct dom_string comment_str = STATIC_DOM_STRING("#comment");
static struct dom_string document_str = STATIC_DOM_STRING("#document");
static struct dom_string document_fragment_str = STATIC_DOM_STRING("#document-fragment");
static struct dom_string text_str = STATIC_DOM_STRING("#text");
assert(node);
switch (node->type) {
case DOM_NODE_CDATA_SECTION:
return &cdata_section_str;
case DOM_NODE_COMMENT:
return &comment_str;
case DOM_NODE_DOCUMENT:
return &document_str;
case DOM_NODE_DOCUMENT_FRAGMENT:
return &document_fragment_str;
case DOM_NODE_TEXT:
return &text_str;
case DOM_NODE_ATTRIBUTE:
case DOM_NODE_DOCUMENT_TYPE:
case DOM_NODE_ELEMENT:
case DOM_NODE_ENTITY:
case DOM_NODE_ENTITY_REFERENCE:
case DOM_NODE_NOTATION:
case DOM_NODE_PROCESSING_INSTRUCTION:
default:
return &node->string;
}
}
struct dom_string *
get_dom_node_value(struct dom_node *node)
{
assert(node);
switch (node->type) {
case DOM_NODE_ATTRIBUTE:
return &node->data.attribute.value;
case DOM_NODE_PROCESSING_INSTRUCTION:
return &node->data.proc_instruction.instruction;
case DOM_NODE_CDATA_SECTION:
case DOM_NODE_COMMENT:
case DOM_NODE_TEXT:
return &node->string;
case DOM_NODE_ENTITY_REFERENCE:
case DOM_NODE_NOTATION:
case DOM_NODE_DOCUMENT:
case DOM_NODE_DOCUMENT_FRAGMENT:
case DOM_NODE_DOCUMENT_TYPE:
case DOM_NODE_ELEMENT:
case DOM_NODE_ENTITY:
default:
return NULL;
}
}
struct dom_string *
get_dom_node_type_name(enum dom_node_type type)
{
static struct dom_string dom_node_type_names[DOM_NODES] = {
INIT_DOM_STRING(NULL, 0),
/* DOM_NODE_ELEMENT */ STATIC_DOM_STRING("element"),
/* DOM_NODE_ATTRIBUTE */ STATIC_DOM_STRING("attribute"),
/* DOM_NODE_TEXT */ STATIC_DOM_STRING("text"),
/* DOM_NODE_CDATA_SECTION */ STATIC_DOM_STRING("cdata-section"),
/* DOM_NODE_ENTITY_REFERENCE */ STATIC_DOM_STRING("entity-reference"),
/* DOM_NODE_ENTITY */ STATIC_DOM_STRING("entity"),
/* DOM_NODE_PROCESSING_INSTRUCTION */ STATIC_DOM_STRING("proc-instruction"),
/* DOM_NODE_COMMENT */ STATIC_DOM_STRING("comment"),
/* DOM_NODE_DOCUMENT */ STATIC_DOM_STRING("document"),
/* DOM_NODE_DOCUMENT_TYPE */ STATIC_DOM_STRING("document-type"),
/* DOM_NODE_DOCUMENT_FRAGMENT */ STATIC_DOM_STRING("document-fragment"),
/* DOM_NODE_NOTATION */ STATIC_DOM_STRING("notation"),
};
assert(type < DOM_NODES);
return &dom_node_type_names[type];
}