Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions ext/dom/document.c
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,35 @@ static int dom_perform_xinclude(xmlDocPtr docp, dom_object *intern, zend_long fl
return err;
}

/* For modern DOM, namespace declarations are stored as attributes (node->nsDef
* is NULL), so libxml's native validators can't resolve prefixed QNames found in
* content (e.g. an xs:QName attribute value). Temporarily relink them, mirroring
* what C14N does in dom_canonicalization(). */
typedef struct {
HashTable links;
bool active;
} dom_validate_ns_guard;

static void dom_validate_ns_guard_begin(dom_validate_ns_guard *guard, xmlDocPtr docp)
{
guard->active = php_dom_follow_spec_node((const xmlNode *) docp);
if (guard->active) {
zend_hash_init(&guard->links, 0, NULL, NULL, false);
xmlNodePtr root_element = xmlDocGetRootElement(docp);
if (root_element) {
dom_relink_ns_decls(&guard->links, root_element);
}
}
}

static void dom_validate_ns_guard_end(dom_validate_ns_guard *guard)
{
if (guard->active) {
dom_unlink_ns_decls(&guard->links);
zend_hash_destroy(&guard->links);
}
}

/* {{{ Substitutues xincludes in a DomDocument */
PHP_METHOD(DOMDocument, xinclude)
{
Expand Down Expand Up @@ -1832,8 +1861,11 @@ PHP_METHOD(DOMDocument, validate)
cvp->userData = NULL;
cvp->error = (xmlValidityErrorFunc) php_libxml_error_handler;
cvp->warning = (xmlValidityErrorFunc) php_libxml_error_handler;

if (xmlValidateDocument(cvp, docp)) {
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
int dtd_valid = xmlValidateDocument(cvp, docp);
dom_validate_ns_guard_end(&guard);
if (dtd_valid) {
RETVAL_TRUE;
} else {
RETVAL_FALSE;
Expand Down Expand Up @@ -1930,7 +1962,10 @@ static void dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type)
PHP_LIBXML_SANITIZE_GLOBALS(validate);
xmlSchemaSetValidOptions(vptr, valid_opts);
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
is_valid = xmlSchemaValidateDoc(vptr, docp);
dom_validate_ns_guard_end(&guard);
xmlSchemaFree(sptr);
xmlSchemaFreeValidCtxt(vptr);
PHP_LIBXML_RESTORE_GLOBALS(validate);
Expand Down Expand Up @@ -2028,7 +2063,10 @@ static void dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int type
}

xmlRelaxNGSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
dom_validate_ns_guard guard;
dom_validate_ns_guard_begin(&guard, docp);
is_valid = xmlRelaxNGValidateDoc(vptr, docp);
dom_validate_ns_guard_end(&guard);
xmlRelaxNGFree(sptr);
xmlRelaxNGFreeValidCtxt(vptr);

Expand Down
138 changes: 138 additions & 0 deletions ext/dom/namespace_compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,142 @@ PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns
}
}

static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node)
{
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
if (!ns) {
return NULL;
}

zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
if (Z_ISNULL_P(zv)) {
ZVAL_LONG(zv, 1);
} else {
Z_LVAL_P(zv)++;
}

memset(ns, 0, sizeof(*ns));
ns->type = XML_LOCAL_NAMESPACE;
ns->next = node->nsDef;
node->nsDef = ns;

return ns;
}

/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns
* but have no matching xmlns attribute (typical for createElementNS). */
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
{
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
if (!ns) {
return;
}

ns->href = xmlStrdup(src_ns->href);
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
}

/* Same, but for attribute namespaces, which may collide by prefix with the
* element's own ns or with a sibling attribute's ns. */
static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
{
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
return;
}
}

dom_add_synthetic_ns_decl(links, node, src_ns);
}

static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
{
if (node->type == XML_ELEMENT_NODE) {
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
if (!ns) {
return;
}

bool should_free;
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);

ns->href = should_free ? attr_value : xmlStrdup(attr_value);
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
ns->_private = attr;
if (attr->prev) {
attr->prev->next = attr->next;
} else {
node->properties = attr->next;
}
if (attr->next) {
attr->next->prev = attr->prev;
}
}
}

/* The default namespace is handled separately from the other namespaces in C14N.
* The default namespace is explicitly looked up while the other namespaces are
* deduplicated and compared to a list of visible namespaces. */
if (node->ns && !node->ns->prefix) {
/* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c
* can return the current namespace. */
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
node->ns = xmlSearchNs(node->doc, node, NULL);
} else if (node->ns) {
dom_add_synthetic_ns_decl(links, node, node->ns);
}

for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns);
}
}
}
}

void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
{
dom_relink_ns_decls_element(links, root);

xmlNodePtr base = root;
xmlNodePtr node = base->children;
while (node != NULL) {
dom_relink_ns_decls_element(links, node);
node = php_dom_next_in_tree_order(node, base);
}
}

void dom_unlink_ns_decls(HashTable *links)
{
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
if (h & 1) {
xmlNodePtr node = (xmlNodePtr) (h ^ 1);
node->ns = Z_PTR_P(data);
} else {
xmlNodePtr node = (xmlNodePtr) h;
while (Z_LVAL_P(data)-- > 0) {
xmlNsPtr ns = node->nsDef;
node->nsDef = ns->next;

xmlAttrPtr attr = ns->_private;
if (attr) {
if (attr->prev) {
attr->prev->next = attr;
} else {
node->properties = attr;
}
if (attr->next) {
attr->next->prev = attr;
}
}

xmlFreeNs(ns);
}
}
} ZEND_HASH_FOREACH_END();
}


#endif /* HAVE_LIBXML && HAVE_DOM */
140 changes: 0 additions & 140 deletions ext/dom/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -2103,146 +2103,6 @@ PHP_METHOD(DOMNode, lookupNamespaceURI)
}
/* }}} end dom_node_lookup_namespace_uri */

/* Allocate, track and prepend a temporary nsDef entry for C14N.
* Returns the new xmlNsPtr for the caller to fill in href/prefix/_private,
* or NULL on allocation failure. */
static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node)
{
xmlNsPtr ns = xmlMalloc(sizeof(*ns));
if (!ns) {
return NULL;
}

zval *zv = zend_hash_index_lookup(links, (zend_ulong) node);
if (Z_ISNULL_P(zv)) {
ZVAL_LONG(zv, 1);
} else {
Z_LVAL_P(zv)++;
}

memset(ns, 0, sizeof(*ns));
ns->type = XML_LOCAL_NAMESPACE;
ns->next = node->nsDef;
node->nsDef = ns;

return ns;
}

/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns
* but have no matching xmlns attribute (typical for createElementNS). */
static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
{
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
if (!ns) {
return;
}

ns->href = xmlStrdup(src_ns->href);
ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL;
}

/* Same, but for attribute namespaces, which may collide by prefix with the
* element's own ns or with a sibling attribute's ns. */
static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns)
{
for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) {
if (xmlStrEqual(existing->prefix, src_ns->prefix)) {
return;
}
}

dom_add_synthetic_ns_decl(links, node, src_ns);
}

static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node)
{
if (node->type == XML_ELEMENT_NODE) {
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
xmlNsPtr ns = dom_alloc_ns_decl(links, node);
if (!ns) {
return;
}

bool should_free;
xmlChar *attr_value = php_libxml_attr_value(attr, &should_free);

ns->href = should_free ? attr_value : xmlStrdup(attr_value);
ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL;
ns->_private = attr;
if (attr->prev) {
attr->prev->next = attr->next;
} else {
node->properties = attr->next;
}
if (attr->next) {
attr->next->prev = attr->prev;
}
}
}

/* The default namespace is handled separately from the other namespaces in C14N.
* The default namespace is explicitly looked up while the other namespaces are
* deduplicated and compared to a list of visible namespaces. */
if (node->ns && !node->ns->prefix) {
/* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c
* can return the current namespace. */
zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns);
node->ns = xmlSearchNs(node->doc, node, NULL);
} else if (node->ns) {
dom_add_synthetic_ns_decl(links, node, node->ns);
}

for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) {
dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns);
}
}
}
}

static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root)
{
dom_relink_ns_decls_element(links, root);

xmlNodePtr base = root;
xmlNodePtr node = base->children;
while (node != NULL) {
dom_relink_ns_decls_element(links, node);
node = php_dom_next_in_tree_order(node, base);
}
}

static void dom_unlink_ns_decls(HashTable *links)
{
ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) {
if (h & 1) {
xmlNodePtr node = (xmlNodePtr) (h ^ 1);
node->ns = Z_PTR_P(data);
} else {
xmlNodePtr node = (xmlNodePtr) h;
while (Z_LVAL_P(data)-- > 0) {
xmlNsPtr ns = node->nsDef;
node->nsDef = ns->next;

xmlAttrPtr attr = ns->_private;
if (attr) {
if (attr->prev) {
attr->prev->next = attr;
} else {
node->properties = attr;
}
if (attr->next) {
attr->next->prev = attr;
}
}

xmlFreeNs(ns);
}
}
} ZEND_HASH_FOREACH_END();
}

static int dom_canonicalize_node_parent_lookup_cb(void *user_data, xmlNodePtr node, xmlNodePtr parent)
{
xmlNodePtr root = user_data;
Expand Down
7 changes: 7 additions & 0 deletions ext/dom/php_dom.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_obje
xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive);
void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document);
void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document);

/* Temporarily materialize namespace declarations as nsDef entries on the tree so
* that libxml's native validators/canonicalizers can resolve prefixed QNames that
* appear in element/attribute *content*. Modern DOM keeps declarations off the
* tree (node->nsDef == NULL), which xmlSearchNs() cannot follow. Internal only. */
void dom_relink_ns_decls(HashTable *links, xmlNodePtr root);
void dom_unlink_ns_decls(HashTable *links);
zval *dom_element_class_list_zval(dom_object *obj);

typedef enum {
Expand Down
Loading
Loading