@@ -2081,6 +2081,93 @@ 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+ if (node -> ns && !node -> ns -> prefix ) { // TODO: is this complete?
2124+ /* Workaround for the behaviour where xmlSearchNs() can return the current namespace */
2125+ zend_hash_index_add_new_ptr (links , (zend_ulong ) node | 1 , node -> ns );
2126+ node -> ns = xmlSearchNs (node -> doc , node , NULL );
2127+ }
2128+ }
2129+ }
2130+
2131+ static void dom_relink_ns_decls (HashTable * links , xmlNodePtr root )
2132+ {
2133+ dom_relink_ns_decls_element (links , root );
2134+
2135+ xmlNodePtr base = root ;
2136+ xmlNodePtr node = base -> children ;
2137+ while (node != NULL ) {
2138+ dom_relink_ns_decls_element (links , node );
2139+ node = php_dom_next_in_tree_order (node , base );
2140+ }
2141+ }
2142+
2143+ static void dom_unlink_ns_decls (HashTable * links )
2144+ {
2145+ ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL (links , zend_ulong h , zval * data ) {
2146+ if (h & 1 ) {
2147+ xmlNodePtr node = (xmlNodePtr ) (h ^ 1 );
2148+ node -> ns = Z_PTR_P (data );
2149+ } else {
2150+ xmlNodePtr node = (xmlNodePtr ) h ;
2151+ while (Z_LVAL_P (data )-- > 0 ) {
2152+ xmlNsPtr ns = node -> nsDef ;
2153+ node -> nsDef = node -> nsDef -> next ;
2154+
2155+ xmlAttrPtr attr = ns -> _private ;
2156+ if (attr -> prev ) {
2157+ attr -> prev -> next = attr ;
2158+ } else {
2159+ node -> properties = attr ;
2160+ }
2161+ if (attr -> next ) {
2162+ attr -> next -> prev = attr ;
2163+ }
2164+
2165+ xmlFreeNs (ns );
2166+ }
2167+ }
2168+ } ZEND_HASH_FOREACH_END ();
2169+ }
2170+
20842171static int dom_canonicalize_node_parent_lookup_cb (void * user_data , xmlNodePtr node , xmlNodePtr parent )
20852172{
20862173 xmlNodePtr root = user_data ;
@@ -2136,7 +2223,23 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21362223
21372224 docp = nodep -> doc ;
21382225
2139- if (! docp ) {
2226+ HashTable links ;
2227+ bool modern = php_dom_follow_spec_node (nodep );
2228+ if (modern ) {
2229+ xmlNodePtr root = nodep ;
2230+ while (root -> parent ) {
2231+ root = root -> parent ;
2232+ }
2233+
2234+ if (UNEXPECTED (root -> type != XML_DOCUMENT_NODE && root -> type != XML_HTML_DOCUMENT_NODE )) {
2235+ php_dom_throw_error_with_message (HIERARCHY_REQUEST_ERR , "Canonicalization can only happen on nodes attached to a document." , /* strict */ true);
2236+ RETURN_THROWS ();
2237+ }
2238+
2239+ zend_hash_init (& links , 0 , NULL , NULL , false);
2240+ dom_relink_ns_decls (& links , xmlDocGetRootElement (docp ));
2241+ } else if (!docp ) {
2242+ /* Note: not triggerable with modern DOM */
21402243 zend_throw_error (NULL , "Node must be associated with a document" );
21412244 RETURN_THROWS ();
21422245 }
@@ -2158,12 +2261,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21582261 if (!tmp ) {
21592262 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
21602263 zend_argument_value_error (3 + mode , "must have a \"query\" key" );
2161- RETURN_THROWS () ;
2264+ goto clean_links ;
21622265 }
21632266 if (Z_TYPE_P (tmp ) != IS_STRING ) {
21642267 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
21652268 zend_argument_type_error (3 + mode , "\"query\" option must be a string, %s given" , zend_zval_value_name (tmp ));
2166- RETURN_THROWS () ;
2269+ goto clean_links ;
21672270 }
21682271 xquery = Z_STRVAL_P (tmp );
21692272
@@ -2195,7 +2298,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
21952298 }
21962299 xmlXPathFreeContext (ctxp );
21972300 zend_throw_error (NULL , "XPath query did not return a nodeset" );
2198- RETURN_THROWS () ;
2301+ goto clean_links ;
21992302 }
22002303 }
22012304
@@ -2264,6 +2367,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{
22642367 RETURN_LONG (bytes );
22652368 }
22662369 }
2370+
2371+ clean_links :
2372+ if (modern ) {
2373+ dom_unlink_ns_decls (& links );
2374+ zend_hash_destroy (& links );
2375+ }
22672376}
22682377/* }}} */
22692378
0 commit comments