@@ -2081,6 +2081,97 @@ PHP_METHOD(DOMNode, lookupNamespaceURI)
20812081}
20822082/* }}} end dom_node_lookup_namespace_uri */
20832083
2084+ static void dom_relink_ns_decls_element (HashTable * links , xmlNodePtr node )
2085+ {
2086+ if (node -> type == XML_ELEMENT_NODE ) {
2087+ for (xmlAttrPtr attr = node -> properties ; attr ; attr = attr -> next ) {
2088+ if (php_dom_ns_is_fast ((const xmlNode * ) attr , php_dom_ns_is_xmlns_magic_token )) {
2089+ xmlNsPtr ns = xmlMalloc (sizeof (* ns ));
2090+ if (!ns ) {
2091+ return ;
2092+ }
2093+
2094+ zval * zv = zend_hash_index_lookup (links , (zend_ulong ) node );
2095+ if (Z_ISNULL_P (zv )) {
2096+ ZVAL_LONG (zv , 1 );
2097+ } else {
2098+ Z_LVAL_P (zv )++ ;
2099+ }
2100+
2101+ bool should_free ;
2102+ xmlChar * attr_value = php_libxml_attr_value (attr , & should_free );
2103+
2104+ memset (ns , 0 , sizeof (* ns ));
2105+ ns -> type = XML_LOCAL_NAMESPACE ;
2106+ ns -> href = should_free ? attr_value : xmlStrdup (attr_value );
2107+ ns -> prefix = attr -> ns -> prefix ? xmlStrdup (attr -> name ) : NULL ;
2108+ ns -> next = node -> nsDef ;
2109+ node -> nsDef = ns ;
2110+
2111+ ns -> _private = attr ;
2112+ if (attr -> prev ) {
2113+ attr -> prev = attr -> next ;
2114+ } else {
2115+ node -> properties = attr -> next ;
2116+ }
2117+ if (attr -> next ) {
2118+ attr -> next -> prev = attr -> prev ;
2119+ }
2120+ }
2121+ }
2122+
2123+ /* The default namespace is handled separately from the other namespaces in C14N.
2124+ * The default namespace is explicitly looked up while the other namespaces are
2125+ * deduplicated and compared to a list of visible namespaces. */
2126+ if (node -> ns && !node -> ns -> prefix ) {
2127+ /* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c
2128+ * can return the current namespace. */
2129+ zend_hash_index_add_new_ptr (links , (zend_ulong ) node | 1 , node -> ns );
2130+ node -> ns = xmlSearchNs (node -> doc , node , NULL );
2131+ }
2132+ }
2133+ }
2134+
2135+ static void dom_relink_ns_decls (HashTable * links , xmlNodePtr root )
2136+ {
2137+ dom_relink_ns_decls_element (links , root );
2138+
2139+ xmlNodePtr base = root ;
2140+ xmlNodePtr node = base -> children ;
2141+ while (node != NULL ) {
2142+ dom_relink_ns_decls_element (links , node );
2143+ node = php_dom_next_in_tree_order (node , base );
2144+ }
2145+ }
2146+
2147+ static void dom_unlink_ns_decls (HashTable * links )
2148+ {
2149+ ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL (links , zend_ulong h , zval * data ) {
2150+ if (h & 1 ) {
2151+ xmlNodePtr node = (xmlNodePtr ) (h ^ 1 );
2152+ node -> ns = Z_PTR_P (data );
2153+ } else {
2154+ xmlNodePtr node = (xmlNodePtr ) h ;
2155+ while (Z_LVAL_P (data )-- > 0 ) {
2156+ xmlNsPtr ns = node -> nsDef ;
2157+ node -> nsDef = ns -> next ;
2158+
2159+ xmlAttrPtr attr = ns -> _private ;
2160+ if (attr -> prev ) {
2161+ attr -> prev -> next = attr ;
2162+ } else {
2163+ node -> properties = attr ;
2164+ }
2165+ if (attr -> next ) {
2166+ attr -> next -> prev = attr ;
2167+ }
2168+
2169+ xmlFreeNs (ns );
2170+ }
2171+ }
2172+ } ZEND_HASH_FOREACH_END ();
2173+ }
2174+
20842175static int dom_canonicalize_node_parent_lookup_cb (void * user_data , xmlNodePtr node , xmlNodePtr parent )
20852176{
20862177 xmlNodePtr root = user_data ;
@@ -2136,7 +2227,23 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21362227
21372228 docp = nodep -> doc ;
21382229
2139- if (! docp ) {
2230+ HashTable links ;
2231+ bool modern = php_dom_follow_spec_node (nodep );
2232+ if (modern ) {
2233+ xmlNodePtr root = nodep ;
2234+ while (root -> parent ) {
2235+ root = root -> parent ;
2236+ }
2237+
2238+ if (UNEXPECTED (root -> type != XML_DOCUMENT_NODE && root -> type != XML_HTML_DOCUMENT_NODE )) {
2239+ php_dom_throw_error_with_message (HIERARCHY_REQUEST_ERR , "Canonicalization can only happen on nodes attached to a document." , /* strict */ true);
2240+ RETURN_THROWS ();
2241+ }
2242+
2243+ zend_hash_init (& links , 0 , NULL , NULL , false);
2244+ dom_relink_ns_decls (& links , xmlDocGetRootElement (docp ));
2245+ } else if (!docp ) {
2246+ /* Note: not triggerable with modern DOM */
21402247 zend_throw_error (NULL , "Node must be associated with a document" );
21412248 RETURN_THROWS ();
21422249 }
@@ -2158,12 +2265,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21582265 if (!tmp ) {
21592266 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
21602267 zend_argument_value_error (3 + mode , "must have a \"query\" key" );
2161- RETURN_THROWS () ;
2268+ goto clean_links ;
21622269 }
21632270 if (Z_TYPE_P (tmp ) != IS_STRING ) {
21642271 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
21652272 zend_argument_type_error (3 + mode , "\"query\" option must be a string, %s given" , zend_zval_value_name (tmp ));
2166- RETURN_THROWS () ;
2273+ goto clean_links ;
21672274 }
21682275 xquery = Z_STRVAL_P (tmp );
21692276
@@ -2195,7 +2302,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21952302 }
21962303 xmlXPathFreeContext (ctxp );
21972304 zend_throw_error (NULL , "XPath query did not return a nodeset" );
2198- RETURN_THROWS () ;
2305+ goto clean_links ;
21992306 }
22002307 }
22012308
@@ -2264,6 +2371,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
22642371 RETURN_LONG (bytes );
22652372 }
22662373 }
2374+
2375+ clean_links :
2376+ if (modern ) {
2377+ dom_unlink_ns_decls (& links );
2378+ zend_hash_destroy (& links );
2379+ }
22672380}
22682381/* }}} */
22692382
0 commit comments