xref: /php-src/ext/dom/document.c (revision 61615d56)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@php.net>                            |
15    +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21 
22 #include "php.h"
23 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
24 #include "php_dom.h"
25 #include "namespace_compat.h"
26 #include "private_data.h"
27 #include "xml_serializer.h"
28 #include "internal_helpers.h"
29 #include "dom_properties.h"
30 #include <libxml/SAX.h>
31 #ifdef LIBXML_SCHEMAS_ENABLED
32 #include <libxml/relaxng.h>
33 #include <libxml/xmlschemas.h>
34 #endif
35 
36 /*
37 * class DOMDocument extends DOMNode
38 *
39 * URL: https://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-i-Document
40 * Since:
41 */
42 
43 /* {{{ docType	DOMDocumentType
44 readonly=yes
45 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-B63ED1A31
46 Since:
47 */
dom_document_doctype_read(dom_object * obj,zval * retval)48 zend_result dom_document_doctype_read(dom_object *obj, zval *retval)
49 {
50 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
51 
52 	xmlDtdPtr dtdptr = xmlGetIntSubset(docp);
53 
54 	php_dom_create_nullable_object((xmlNodePtr) dtdptr, retval, obj);
55 	return SUCCESS;
56 }
57 
58 /* }}} */
59 
60 /* {{{ implementation	DOMImplementation
61 readonly=yes
62 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1B793EBA
63 Since:
64 */
dom_document_implementation_read(dom_object * obj,zval * retval)65 zend_result dom_document_implementation_read(dom_object *obj, zval *retval)
66 {
67 	php_dom_create_implementation(retval, false);
68 	return SUCCESS;
69 }
70 /* }}} */
71 
72 /* {{{ documentElement	DOMElement
73 readonly=yes
74 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-87CD092
75 Since:
76 */
dom_document_document_element_read(dom_object * obj,zval * retval)77 zend_result dom_document_document_element_read(dom_object *obj, zval *retval)
78 {
79 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
80 
81 	xmlNodePtr root = xmlDocGetRootElement(docp);
82 
83 	php_dom_create_nullable_object(root, retval, obj);
84 	return SUCCESS;
85 }
86 
87 /* }}} */
88 
89 /* {{{ encoding	string
90 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-encoding
91 Since: DOM Level 3
92 */
dom_document_encoding_read(dom_object * obj,zval * retval)93 zend_result dom_document_encoding_read(dom_object *obj, zval *retval)
94 {
95 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
96 
97 	const char *encoding = (const char *) docp->encoding;
98 
99 	if (encoding != NULL) {
100 		ZVAL_STRING(retval, encoding);
101 	} else {
102 		ZVAL_NULL(retval);
103 	}
104 
105 	return SUCCESS;
106 }
107 
dom_document_actual_encoding_read(dom_object * obj,zval * retval)108 zend_result dom_document_actual_encoding_read(dom_object *obj, zval *retval)
109 {
110 	PHP_DOM_DEPRECATED_PROPERTY("Property DOMDocument::$actualEncoding is deprecated");
111 
112 	return dom_document_encoding_read(obj, retval);
113 }
114 
dom_document_encoding_write(dom_object * obj,zval * newval)115 zend_result dom_document_encoding_write(dom_object *obj, zval *newval)
116 {
117 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
118 
119 	/* Typed property, can only be IS_STRING or IS_NULL. */
120 	ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING || Z_TYPE_P(newval) == IS_NULL);
121 
122 	if (Z_TYPE_P(newval) == IS_NULL) {
123 		goto invalid_encoding;
124 	}
125 
126 	const zend_string *str = Z_STR_P(newval);
127 
128 	xmlCharEncodingHandlerPtr handler = xmlFindCharEncodingHandler(ZSTR_VAL(str));
129 
130 	if (handler != NULL) {
131 		xmlCharEncCloseFunc(handler);
132 		if (docp->encoding != NULL) {
133 			xmlFree(BAD_CAST docp->encoding);
134 		}
135 		docp->encoding = xmlStrdup((const xmlChar *) ZSTR_VAL(str));
136 	} else {
137 		goto invalid_encoding;
138 	}
139 
140 	return SUCCESS;
141 
142 invalid_encoding:
143 	zend_value_error("Invalid document encoding");
144 	return FAILURE;
145 }
146 
147 /* }}} */
148 
149 /* {{{ standalone	boolean
150 readonly=no
151 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-standalone
152 Since: DOM Level 3
153 */
dom_document_standalone_read(dom_object * obj,zval * retval)154 zend_result dom_document_standalone_read(dom_object *obj, zval *retval)
155 {
156 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
157 	ZVAL_BOOL(retval, docp->standalone > 0);
158 	return SUCCESS;
159 }
160 
dom_document_standalone_write(dom_object * obj,zval * newval)161 zend_result dom_document_standalone_write(dom_object *obj, zval *newval)
162 {
163 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
164 
165 	ZEND_ASSERT(Z_TYPE_P(newval) == IS_TRUE || Z_TYPE_P(newval) == IS_FALSE);
166 	docp->standalone = Z_TYPE_P(newval) == IS_TRUE;
167 
168 	return SUCCESS;
169 }
170 
171 /* }}} */
172 
173 /* {{{ version	string
174 readonly=no
175 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-version
176 Since: DOM Level 3
177 */
dom_document_version_read(dom_object * obj,zval * retval)178 zend_result dom_document_version_read(dom_object *obj, zval *retval)
179 {
180 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
181 
182 	const char *version = (const char *) docp->version;
183 
184 	if (version != NULL) {
185 		ZVAL_STRING(retval, version);
186 	} else {
187 		ZVAL_NULL(retval);
188 	}
189 
190 	return SUCCESS;
191 }
192 
dom_document_version_write(dom_object * obj,zval * newval)193 zend_result dom_document_version_write(dom_object *obj, zval *newval)
194 {
195 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
196 
197 	/* Cannot fail because the type is either null or a string. */
198 	zend_string *str = zval_get_string(newval);
199 
200 	if (php_dom_follow_spec_intern(obj)) {
201 		if (!zend_string_equals_literal(str, "1.0") && !zend_string_equals_literal(str, "1.1")) {
202 			zend_value_error("Invalid XML version");
203 			zend_string_release_ex(str, 0);
204 			return FAILURE;
205 		}
206 	}
207 
208 	if (docp->version != NULL) {
209 		xmlFree(BAD_CAST docp->version);
210 	}
211 
212 	docp->version = xmlStrdup((const xmlChar *) ZSTR_VAL(str));
213 
214 	zend_string_release_ex(str, 0);
215 	return SUCCESS;
216 }
217 
218 /* }}} */
219 
220 /* {{{ strictErrorChecking	boolean
221 readonly=no
222 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-strictErrorChecking
223 Since: DOM Level 3
224 */
dom_document_strict_error_checking_read(dom_object * obj,zval * retval)225 zend_result dom_document_strict_error_checking_read(dom_object *obj, zval *retval)
226 {
227 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
228 	ZVAL_BOOL(retval, doc_prop->stricterror);
229 	return SUCCESS;
230 }
231 
dom_document_strict_error_checking_write(dom_object * obj,zval * newval)232 zend_result dom_document_strict_error_checking_write(dom_object *obj, zval *newval)
233 {
234 	if (obj->document) {
235 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
236 		doc_prop->stricterror = zend_is_true(newval);
237 	}
238 
239 	return SUCCESS;
240 }
241 
242 /* }}} */
243 
244 /* {{{ formatOutput	boolean
245 readonly=no
246 */
dom_document_format_output_read(dom_object * obj,zval * retval)247 zend_result dom_document_format_output_read(dom_object *obj, zval *retval)
248 {
249 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
250 	ZVAL_BOOL(retval, doc_prop->formatoutput);
251 	return SUCCESS;
252 }
253 
dom_document_format_output_write(dom_object * obj,zval * newval)254 zend_result dom_document_format_output_write(dom_object *obj, zval *newval)
255 {
256 	if (obj->document) {
257 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
258 		doc_prop->formatoutput = zend_is_true(newval);
259 	}
260 
261 	return SUCCESS;
262 }
263 /* }}} */
264 
265 /* {{{ validateOnParse	boolean
266 readonly=no
267 */
dom_document_validate_on_parse_read(dom_object * obj,zval * retval)268 zend_result	dom_document_validate_on_parse_read(dom_object *obj, zval *retval)
269 {
270 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
271 	ZVAL_BOOL(retval, doc_prop->validateonparse);
272 	return SUCCESS;
273 }
274 
dom_document_validate_on_parse_write(dom_object * obj,zval * newval)275 zend_result dom_document_validate_on_parse_write(dom_object *obj, zval *newval)
276 {
277 	if (obj->document) {
278 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
279 		doc_prop->validateonparse = zend_is_true(newval);
280 	}
281 
282 	return SUCCESS;
283 }
284 /* }}} */
285 
286 /* {{{ resolveExternals	boolean
287 readonly=no
288 */
dom_document_resolve_externals_read(dom_object * obj,zval * retval)289 zend_result dom_document_resolve_externals_read(dom_object *obj, zval *retval)
290 {
291 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
292 	ZVAL_BOOL(retval, doc_prop->resolveexternals);
293 	return SUCCESS;
294 }
295 
dom_document_resolve_externals_write(dom_object * obj,zval * newval)296 zend_result dom_document_resolve_externals_write(dom_object *obj, zval *newval)
297 {
298 	if (obj->document) {
299 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
300 		doc_prop->resolveexternals = zend_is_true(newval);
301 	}
302 
303 	return SUCCESS;
304 }
305 /* }}} */
306 
307 /* {{{ preserveWhiteSpace	boolean
308 readonly=no
309 */
dom_document_preserve_whitespace_read(dom_object * obj,zval * retval)310 zend_result dom_document_preserve_whitespace_read(dom_object *obj, zval *retval)
311 {
312 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
313 	ZVAL_BOOL(retval, doc_prop->preservewhitespace);
314 	return SUCCESS;
315 }
316 
dom_document_preserve_whitespace_write(dom_object * obj,zval * newval)317 zend_result dom_document_preserve_whitespace_write(dom_object *obj, zval *newval)
318 {
319 	if (obj->document) {
320 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
321 		doc_prop->preservewhitespace = zend_is_true(newval);
322 	}
323 
324 	return SUCCESS;
325 }
326 /* }}} */
327 
328 /* {{{ recover	boolean
329 readonly=no
330 */
dom_document_recover_read(dom_object * obj,zval * retval)331 zend_result dom_document_recover_read(dom_object *obj, zval *retval)
332 {
333 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
334 	ZVAL_BOOL(retval, doc_prop->recover);
335 	return SUCCESS;
336 }
337 
dom_document_recover_write(dom_object * obj,zval * newval)338 zend_result dom_document_recover_write(dom_object *obj, zval *newval)
339 {
340 	if (obj->document) {
341 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
342 		doc_prop->recover = zend_is_true(newval);
343 	}
344 
345 	return SUCCESS;
346 }
347 /* }}} */
348 
349 /* {{{ substituteEntities	boolean
350 readonly=no
351 */
dom_document_substitute_entities_read(dom_object * obj,zval * retval)352 zend_result dom_document_substitute_entities_read(dom_object *obj, zval *retval)
353 {
354 	libxml_doc_props const* doc_prop = dom_get_doc_props_read_only(obj->document);
355 	ZVAL_BOOL(retval, doc_prop->substituteentities);
356 	return SUCCESS;
357 }
358 
dom_document_substitute_entities_write(dom_object * obj,zval * newval)359 zend_result dom_document_substitute_entities_write(dom_object *obj, zval *newval)
360 {
361 	if (obj->document) {
362 		dom_doc_propsptr doc_prop = dom_get_doc_props(obj->document);
363 		doc_prop->substituteentities = zend_is_true(newval);
364 	}
365 
366 	return SUCCESS;
367 }
368 /* }}} */
369 
370 /* {{{ documentURI	string
371 readonly=no
372 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-documentURI
373 Since: DOM Level 3
374 */
dom_document_document_uri_read(dom_object * obj,zval * retval)375 zend_result dom_document_document_uri_read(dom_object *obj, zval *retval)
376 {
377 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
378 
379 	const char *url = (const char *) docp->URL;
380 	if (url != NULL) {
381 		ZVAL_STRING(retval, url);
382 	} else {
383 		if (php_dom_follow_spec_intern(obj)) {
384 			ZVAL_STRING(retval, "about:blank");
385 		} else {
386 			ZVAL_NULL(retval);
387 		}
388 	}
389 
390 	return SUCCESS;
391 }
392 
dom_document_document_uri_write(dom_object * obj,zval * newval)393 zend_result dom_document_document_uri_write(dom_object *obj, zval *newval)
394 {
395 	DOM_PROP_NODE(xmlDocPtr, docp, obj);
396 
397 	/* Cannot fail because the type is either null or a string. */
398 	zend_string *str = zval_get_string(newval);
399 
400 	if (docp->URL != NULL) {
401 		xmlFree(BAD_CAST docp->URL);
402 	}
403 
404 	docp->URL = xmlStrdup((const xmlChar *) ZSTR_VAL(str));
405 
406 	zend_string_release_ex(str, 0);
407 	return SUCCESS;
408 }
409 
410 /* }}} */
411 
412 /* {{{ config	DOMConfiguration
413 readonly=yes
414 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-config
415 Since: DOM Level 3
416 */
dom_document_config_read(dom_object * obj,zval * retval)417 zend_result dom_document_config_read(dom_object *obj, zval *retval)
418 {
419 	PHP_DOM_DEPRECATED_PROPERTY("Property DOMDocument::$config is deprecated");
420 
421 	ZVAL_NULL(retval);
422 	return SUCCESS;
423 }
424 
425 /* }}} */
426 
427 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-2141741547
428 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-createelement
429 Since:
430 */
PHP_METHOD(DOMDocument,createElement)431 PHP_METHOD(DOMDocument, createElement)
432 {
433 	xmlDocPtr docp;
434 	dom_object *intern;
435 	size_t value_len;
436 	char *value = NULL;
437 	zend_string *name;
438 
439 	ZEND_PARSE_PARAMETERS_START(1, 2)
440 		Z_PARAM_STR(name)
441 		Z_PARAM_OPTIONAL
442 		Z_PARAM_STRING(value, value_len)
443 	ZEND_PARSE_PARAMETERS_END();
444 
445 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
446 
447 	if (xmlValidateName(BAD_CAST ZSTR_VAL(name), 0) != 0) {
448 		php_dom_throw_error(INVALID_CHARACTER_ERR, dom_get_strict_error(intern->document));
449 		RETURN_FALSE;
450 	}
451 
452 	xmlNodePtr node = xmlNewDocNode(docp, NULL, BAD_CAST ZSTR_VAL(name), BAD_CAST value);
453 
454 	if (!node) {
455 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
456 		RETURN_THROWS();
457 	}
458 
459 	DOM_RET_OBJ(node, intern);
460 }
461 
PHP_METHOD(Dom_Document,createElement)462 PHP_METHOD(Dom_Document, createElement)
463 {
464 	xmlNode *node;
465 	xmlDocPtr docp;
466 	dom_object *intern;
467 	zend_string *name;
468 
469 	ZEND_PARSE_PARAMETERS_START(1, 1)
470 		Z_PARAM_STR(name)
471 	ZEND_PARSE_PARAMETERS_END();
472 
473 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
474 
475 	if (xmlValidateName(BAD_CAST ZSTR_VAL(name), 0) != 0) {
476 		php_dom_throw_error(INVALID_CHARACTER_ERR, /* strict */ true);
477 		RETURN_THROWS();
478 	}
479 
480 	if (docp->type == XML_HTML_DOCUMENT_NODE) {
481 		php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
482 		char *lower = zend_str_tolower_dup_ex(ZSTR_VAL(name), ZSTR_LEN(name));
483 		node = xmlNewDocRawNode(docp, php_dom_libxml_ns_mapper_ensure_html_ns(ns_mapper), BAD_CAST (lower ? lower : ZSTR_VAL(name)), NULL);
484 		efree(lower);
485 	} else {
486 		node = xmlNewDocNode(docp, NULL, BAD_CAST ZSTR_VAL(name), NULL);
487 	}
488 
489 	if (!node) {
490 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
491 		RETURN_THROWS();
492 	}
493 
494 	DOM_RET_OBJ(node, intern);
495 }
496 /* }}} end dom_document_create_element */
497 
498 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-35CB04B5
499 Since:
500 */
PHP_METHOD(DOMDocument,createDocumentFragment)501 PHP_METHOD(DOMDocument, createDocumentFragment)
502 {
503 	zval *id;
504 	xmlNode *node;
505 	xmlDocPtr docp;
506 	dom_object *intern;
507 
508 	id = ZEND_THIS;
509 	if (zend_parse_parameters_none() == FAILURE) {
510 		RETURN_THROWS();
511 	}
512 
513 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
514 
515 	node = xmlNewDocFragment(docp);
516 	if (!node) {
517 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
518 		RETURN_THROWS();
519 	}
520 
521 	DOM_RET_OBJ(node, intern);
522 }
523 /* }}} end dom_document_create_document_fragment */
524 
525 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1975348127
526 Since:
527 */
PHP_METHOD(DOMDocument,createTextNode)528 PHP_METHOD(DOMDocument, createTextNode)
529 {
530 	xmlNode *node;
531 	xmlDocPtr docp;
532 	size_t value_len;
533 	dom_object *intern;
534 	char *value;
535 
536 	ZEND_PARSE_PARAMETERS_START(1, 1)
537 		Z_PARAM_STRING(value, value_len)
538 	ZEND_PARSE_PARAMETERS_END();
539 
540 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
541 
542 	node = xmlNewDocText(docp, BAD_CAST value);
543 	if (!node) {
544 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
545 		RETURN_THROWS();
546 	}
547 
548 	DOM_RET_OBJ(node, intern);
549 }
550 /* }}} end dom_document_create_text_node */
551 
552 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1334481328
553 Since:
554 */
PHP_METHOD(DOMDocument,createComment)555 PHP_METHOD(DOMDocument, createComment)
556 {
557 	zval *id;
558 	xmlNode *node;
559 	xmlDocPtr docp;
560 	size_t value_len;
561 	dom_object *intern;
562 	char *value;
563 
564 	id = ZEND_THIS;
565 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &value, &value_len) == FAILURE) {
566 		RETURN_THROWS();
567 	}
568 
569 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
570 
571 	node = xmlNewDocComment(docp, BAD_CAST value);
572 	if (!node) {
573 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
574 		RETURN_THROWS();
575 	}
576 
577 	DOM_RET_OBJ(node, intern);
578 }
579 /* }}} end dom_document_create_comment */
580 
581 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-D26C0AF8
582 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-createcdatasection
583 Since:
584 */
PHP_METHOD(DOMDocument,createCDATASection)585 PHP_METHOD(DOMDocument, createCDATASection)
586 {
587 	zval *id;
588 	xmlNode *node;
589 	xmlDocPtr docp;
590 	size_t value_len;
591 	dom_object *intern;
592 	char *value;
593 
594 	id = ZEND_THIS;
595 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &value, &value_len) == FAILURE) {
596 		RETURN_THROWS();
597 	}
598 
599 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
600 
601 	if (php_dom_follow_spec_intern(intern)) {
602 		if (docp->type == XML_HTML_DOCUMENT_NODE) {
603 			php_dom_throw_error_with_message(NOT_SUPPORTED_ERR, "This operation is not supported for HTML documents", /* strict */ true);
604 			RETURN_THROWS();
605 		}
606 
607 		if (zend_memnstr(value, "]]>", strlen("]]>"), value + value_len) != NULL) {
608 			php_dom_throw_error_with_message(INVALID_CHARACTER_ERR, "Invalid character sequence \"]]>\" in CDATA section", /* strict */ true);
609 			RETURN_THROWS();
610 		}
611 	}
612 
613 	node = xmlNewCDataBlock(docp, BAD_CAST value, value_len);
614 	if (!node) {
615 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
616 		RETURN_THROWS();
617 	}
618 
619 	DOM_RET_OBJ(node, intern);
620 }
621 /* }}} end dom_document_create_cdatasection */
622 
623 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-135944439
624 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
625 Since:
626 */
dom_document_create_processing_instruction(INTERNAL_FUNCTION_PARAMETERS,bool modern)627 static void dom_document_create_processing_instruction(INTERNAL_FUNCTION_PARAMETERS, bool modern)
628 {
629 	xmlNode *node;
630 	xmlDocPtr docp;
631 	size_t value_len, name_len = 0;
632 	dom_object *intern;
633 	char *name, *value = NULL;
634 
635 	if (zend_parse_parameters(ZEND_NUM_ARGS(), modern ? "ss" : "s|s", &name, &name_len, &value, &value_len) != SUCCESS) {
636 		RETURN_THROWS();
637 	}
638 
639 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
640 
641 	if (xmlValidateName(BAD_CAST name, 0) != 0) {
642 		php_dom_throw_error(INVALID_CHARACTER_ERR, dom_get_strict_error(intern->document));
643 		RETURN_FALSE;
644 	}
645 
646 	if (modern) {
647 		if (value != NULL && zend_memnstr(value, "?>", strlen("?>"), value + value_len) != NULL) {
648 			php_dom_throw_error_with_message(INVALID_CHARACTER_ERR, "Invalid character sequence \"?>\" in processing instruction", /* strict */ true);
649 			RETURN_THROWS();
650 		}
651 	}
652 
653 	node = xmlNewDocPI(docp, BAD_CAST name, BAD_CAST value);
654 	if (!node) {
655 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
656 		RETURN_THROWS();
657 	}
658 
659 	DOM_RET_OBJ(node, intern);
660 }
661 
PHP_METHOD(DOMDocument,createProcessingInstruction)662 PHP_METHOD(DOMDocument, createProcessingInstruction)
663 {
664 	dom_document_create_processing_instruction(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
665 }
666 
PHP_METHOD(Dom_Document,createProcessingInstruction)667 PHP_METHOD(Dom_Document, createProcessingInstruction)
668 {
669 	dom_document_create_processing_instruction(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
670 }
671 /* }}} end dom_document_create_processing_instruction */
672 
673 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1084891198
674 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-createattribute
675 Since:
676 */
PHP_METHOD(DOMDocument,createAttribute)677 PHP_METHOD(DOMDocument, createAttribute)
678 {
679 	zval *id;
680 	xmlAttrPtr node;
681 	xmlDocPtr docp;
682 	dom_object *intern;
683 	zend_string *name;
684 
685 	id = ZEND_THIS;
686 	ZEND_PARSE_PARAMETERS_START(1, 1)
687 		Z_PARAM_PATH_STR(name)
688 	ZEND_PARSE_PARAMETERS_END();
689 
690 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
691 
692 	if (xmlValidateName(BAD_CAST ZSTR_VAL(name), 0) != 0) {
693 		php_dom_throw_error(INVALID_CHARACTER_ERR, dom_get_strict_error(intern->document));
694 		RETURN_FALSE;
695 	}
696 
697 	if (docp->type == XML_HTML_DOCUMENT_NODE && php_dom_follow_spec_intern(intern)) {
698 		char *lower = zend_str_tolower_dup_ex(ZSTR_VAL(name), ZSTR_LEN(name));
699 		node = xmlNewDocProp(docp, BAD_CAST (lower ? lower : ZSTR_VAL(name)), NULL);
700 		efree(lower);
701 	} else {
702 		node = xmlNewDocProp(docp, BAD_CAST ZSTR_VAL(name), NULL);
703 	}
704 
705 	if (!node) {
706 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
707 		RETURN_THROWS();
708 	}
709 
710 	DOM_RET_OBJ((xmlNodePtr) node, intern);
711 
712 }
713 /* }}} end dom_document_create_attribute */
714 
715 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-392B75AE
716 Since:
717 */
PHP_METHOD(DOMDocument,createEntityReference)718 PHP_METHOD(DOMDocument, createEntityReference)
719 {
720 	zval *id;
721 	xmlNode *node;
722 	xmlDocPtr docp = NULL;
723 	dom_object *intern;
724 	size_t name_len;
725 	char *name;
726 
727 	id = ZEND_THIS;
728 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
729 		RETURN_THROWS();
730 	}
731 
732 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
733 
734 	if (xmlValidateName(BAD_CAST name, 0) != 0) {
735 		php_dom_throw_error(INVALID_CHARACTER_ERR, dom_get_strict_error(intern->document));
736 		RETURN_FALSE;
737 	}
738 
739 	node = xmlNewReference(docp, BAD_CAST name);
740 	if (!node) {
741 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
742 		RETURN_THROWS();
743 	}
744 
745 	DOM_RET_OBJ((xmlNodePtr) node, intern);
746 }
747 /* }}} end dom_document_create_entity_reference */
748 
749 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Core-Document-importNode
750 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-importnode
751 Since: DOM Level 2
752 */
PHP_METHOD(DOMDocument,importNode)753 PHP_METHOD(DOMDocument, importNode)
754 {
755 	zval *node;
756 	xmlDocPtr docp;
757 	xmlNodePtr nodep, retnodep;
758 	dom_object *intern, *nodeobj;
759 	bool recursive = 0;
760 
761 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &node, dom_node_class_entry, &recursive) == FAILURE) {
762 		RETURN_THROWS();
763 	}
764 
765 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
766 
767 	DOM_GET_OBJ(nodep, node, xmlNodePtr, nodeobj);
768 
769 	if (nodep->type == XML_HTML_DOCUMENT_NODE || nodep->type == XML_DOCUMENT_NODE) {
770 		php_error_docref(NULL, E_WARNING, "Cannot import: Node Type Not Supported");
771 		RETURN_FALSE;
772 	}
773 
774 	if (nodep->doc == docp) {
775 		retnodep = nodep;
776 	} else {
777 		retnodep = dom_clone_node(NULL, nodep, docp, recursive);
778 		if (!retnodep) {
779 			RETURN_FALSE;
780 		}
781 
782 		if (retnodep->type == XML_ATTRIBUTE_NODE && nodep->ns != NULL && retnodep->ns == NULL) {
783 			xmlNsPtr nsptr = NULL;
784 			xmlNodePtr root = xmlDocGetRootElement(docp);
785 
786 			nsptr = xmlSearchNsByHref (docp, root, nodep->ns->href);
787 			if (nsptr == NULL || nsptr->prefix == NULL) {
788 				int errorcode;
789 				nsptr = dom_get_ns(root, (char *) nodep->ns->href, &errorcode, (char *) nodep->ns->prefix);
790 
791 				/* If there is no root, the namespace cannot be attached to it, so we have to attach it to the old list. */
792 				if (nsptr != NULL && root == NULL) {
793 					php_libxml_set_old_ns(docp, nsptr);
794 				}
795 			}
796 			retnodep->ns = nsptr;
797 		}
798 	}
799 
800 	DOM_RET_OBJ(retnodep, intern);
801 }
802 
dom_modern_document_import_node(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)803 static void dom_modern_document_import_node(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
804 {
805 	zval *node;
806 	xmlDocPtr docp;
807 	xmlNodePtr nodep, retnodep;
808 	dom_object *intern, *nodeobj;
809 	bool recursive = 0;
810 
811 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &node, node_ce, &recursive) != SUCCESS) {
812 		RETURN_THROWS();
813 	}
814 
815 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
816 
817 	DOM_GET_OBJ(nodep, node, xmlNodePtr, nodeobj);
818 
819 	if (nodep->type == XML_HTML_DOCUMENT_NODE || nodep->type == XML_DOCUMENT_NODE) {
820 		php_dom_throw_error(NOT_SUPPORTED_ERR, /* strict */ true);
821 		RETURN_THROWS();
822 	}
823 
824 	if (nodep->doc == docp) {
825 		retnodep = nodep;
826 	} else {
827 		retnodep = dom_clone_node(php_dom_get_ns_mapper(intern), nodep, docp, recursive);
828 		if (!retnodep) {
829 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
830 			RETURN_THROWS();
831 		}
832 	}
833 
834 	DOM_RET_OBJ(retnodep, intern);
835 }
836 
PHP_METHOD(Dom_Document,importNode)837 PHP_METHOD(Dom_Document, importNode)
838 {
839 	dom_modern_document_import_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
840 }
841 /* }}} end dom_document_import_node */
842 
PHP_METHOD(Dom_Document,importLegacyNode)843 PHP_METHOD(Dom_Document, importLegacyNode)
844 {
845 	dom_modern_document_import_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
846 }
847 
848 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-DocCrElNS
849 Modern spec URL: https://dom.spec.whatwg.org/#internal-createelementns-steps
850 Since: DOM Level 2
851 */
PHP_METHOD(DOMDocument,createElementNS)852 PHP_METHOD(DOMDocument, createElementNS)
853 {
854 	xmlDocPtr docp;
855 	xmlNodePtr nodep = NULL;
856 	size_t value_len = 0;
857 	char *value = NULL;
858 	int errorcode;
859 	dom_object *intern;
860 	zend_string *name = NULL, *uri;
861 
862 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!S|s", &uri, &name, &value, &value_len) == FAILURE) {
863 		RETURN_THROWS();
864 	}
865 
866 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
867 
868 	char *localname = NULL, *prefix = NULL;
869 	errorcode = dom_check_qname(ZSTR_VAL(name), &localname, &prefix, uri ? ZSTR_LEN(uri) : 0, ZSTR_LEN(name));
870 
871 	if (errorcode == 0) {
872 		if (xmlValidateName(BAD_CAST localname, 0) == 0) {
873 			nodep = xmlNewDocNode(docp, NULL, BAD_CAST localname, BAD_CAST value);
874 			if (UNEXPECTED(nodep == NULL)) {
875 				php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
876 				RETURN_THROWS();
877 			}
878 
879 			if (uri != NULL) {
880 				xmlNsPtr nsptr = xmlSearchNsByHref(nodep->doc, nodep, BAD_CAST ZSTR_VAL(uri));
881 				if (nsptr == NULL) {
882 					nsptr = dom_get_ns(nodep, ZSTR_VAL(uri), &errorcode, prefix);
883 				}
884 				nodep->ns = nsptr;
885 			}
886 		} else {
887 			errorcode = INVALID_CHARACTER_ERR;
888 		}
889 	}
890 
891 	xmlFree(localname);
892 	xmlFree(prefix);
893 
894 	if (errorcode != 0) {
895 		xmlFreeNode(nodep);
896 		php_dom_throw_error(errorcode, dom_get_strict_error(intern->document));
897 		RETURN_FALSE;
898 	}
899 
900 	DOM_RET_OBJ(nodep, intern);
901 }
902 
PHP_METHOD(Dom_Document,createElementNS)903 PHP_METHOD(Dom_Document, createElementNS)
904 {
905 	xmlDocPtr docp;
906 	dom_object *intern;
907 	zend_string *name, *uri;
908 
909 	ZEND_PARSE_PARAMETERS_START(2, 2)
910 		Z_PARAM_STR_OR_NULL(uri)
911 		Z_PARAM_STR(name)
912 	ZEND_PARSE_PARAMETERS_END();
913 
914 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
915 
916 	xmlChar *localname = NULL, *prefix = NULL;
917 	int errorcode = dom_validate_and_extract(uri, name, &localname, &prefix);
918 
919 	if (errorcode == 0) {
920 		php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
921 		xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
922 
923 		/* Try to create the node with the local name interned. */
924 		const xmlChar *interned_localname = xmlDictLookup(docp->dict, localname, -1);
925 		xmlNodePtr nodep;
926 		if (interned_localname == NULL) {
927 			nodep = xmlNewDocNodeEatName(docp, ns, localname, NULL);
928 		} else {
929 			xmlFree(localname);
930 			nodep = xmlNewDocNodeEatName(docp, ns, BAD_CAST interned_localname, NULL);
931 		}
932 
933 		if (UNEXPECTED(nodep == NULL)) {
934 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
935 		} else {
936 			DOM_RET_OBJ(nodep, intern);
937 		}
938 	} else {
939 		php_dom_throw_error(errorcode, dom_get_strict_error(intern->document));
940 		xmlFree(localname);
941 	}
942 
943 	xmlFree(prefix);
944 }
945 /* }}} end dom_document_create_element_ns */
946 
947 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-DocCrAttrNS
948 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-createattributens
949 Since: DOM Level 2
950 */
PHP_METHOD(DOMDocument,createAttributeNS)951 PHP_METHOD(DOMDocument, createAttributeNS)
952 {
953 	zval *id;
954 	xmlDocPtr docp;
955 	xmlNodePtr nodep = NULL, root;
956 	xmlNsPtr nsptr;
957 	zend_string *name, *uri;
958 	xmlChar *localname = NULL, *prefix = NULL;
959 	dom_object *intern;
960 	int errorcode;
961 
962 	id = ZEND_THIS;
963 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!S", &uri, &name) == FAILURE) {
964 		RETURN_THROWS();
965 	}
966 
967 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
968 
969 	root = xmlDocGetRootElement(docp);
970 	if (root != NULL || php_dom_follow_spec_intern(intern)) {
971 		errorcode = dom_validate_and_extract(uri, name, &localname, &prefix);
972 		if (UNEXPECTED(errorcode != 0)) {
973 			if (!php_dom_follow_spec_intern(intern)) {
974 				/* legacy behaviour */
975 				errorcode = NAMESPACE_ERR;
976 			}
977 			goto error;
978 		}
979 
980 		nodep = (xmlNodePtr) xmlNewDocProp(docp, localname, NULL);
981 		if (UNEXPECTED(nodep == NULL)) {
982 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
983 			RETURN_THROWS();
984 		}
985 
986 		if (uri != NULL && ZSTR_LEN(uri) > 0) {
987 			if (php_dom_follow_spec_intern(intern)) {
988 				php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
989 				nsptr = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
990 			} else {
991 				nsptr = xmlSearchNsByHref(docp, root, BAD_CAST ZSTR_VAL(uri));
992 
993 				if (zend_string_equals_literal(name, "xmlns") || xmlStrEqual(BAD_CAST prefix, BAD_CAST "xml")) {
994 					if (nsptr == NULL) {
995 						nsptr = xmlNewNs(NULL, BAD_CAST ZSTR_VAL(uri), BAD_CAST prefix);
996 						php_libxml_set_old_ns(docp, nsptr);
997 					}
998 				} else {
999 					if (nsptr == NULL || nsptr->prefix == NULL) {
1000 						nsptr = dom_get_ns_unchecked(root, ZSTR_VAL(uri), prefix ? (char *) prefix : "default");
1001 						if (UNEXPECTED(nsptr == NULL)) {
1002 							errorcode = NAMESPACE_ERR;
1003 						}
1004 					}
1005 				}
1006 			}
1007 			nodep->ns = nsptr;
1008 		}
1009 	} else {
1010 		php_error_docref(NULL, E_WARNING, "Document Missing Root Element");
1011 		RETURN_FALSE;
1012 	}
1013 
1014 error:
1015 	xmlFree(localname);
1016 	xmlFree(prefix);
1017 
1018 	if (errorcode != 0) {
1019 		xmlFreeProp((xmlAttrPtr) nodep);
1020 		php_dom_throw_error(errorcode, dom_get_strict_error(intern->document));
1021 		RETURN_FALSE;
1022 	}
1023 
1024 	DOM_RET_OBJ(nodep, intern);
1025 }
1026 /* }}} end dom_document_create_attribute_ns */
1027 
1028 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId
1029 Since: DOM Level 2
1030 */
PHP_METHOD(DOMDocument,getElementById)1031 PHP_METHOD(DOMDocument, getElementById)
1032 {
1033 	xmlDocPtr docp;
1034 	size_t idname_len;
1035 	dom_object *intern;
1036 	char *idname;
1037 
1038 	ZEND_PARSE_PARAMETERS_START(1, 1)
1039 		Z_PARAM_STRING(idname, idname_len)
1040 	ZEND_PARSE_PARAMETERS_END();
1041 
1042 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
1043 
1044 	/* If the document has not been manipulated yet, the ID cache will be in sync and we can trust its result.
1045 	 * This check mainly exists because a lot of times people just query a document without modifying it,
1046 	 * and we can allow quick access to IDs in that case. */
1047 	if (!dom_is_document_cache_modified_since_parsing(intern->document)) {
1048 		const xmlAttr *attrp = xmlGetID(docp, BAD_CAST idname);
1049 		if (attrp && attrp->parent) {
1050 			DOM_RET_OBJ(attrp->parent, intern);
1051 		}
1052 	} else {
1053 		/* From the moment an ID is created, libxml2's behaviour is to cache that element, even
1054 		 * if that element is not yet attached to the document. Similarly, only upon destruction of
1055 		 * the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply
1056 		 * ingrained in the library, and uses the cache for various purposes, it seems like a bad
1057 		 * idea and lost cause to fight it. */
1058 
1059 		const xmlNode *base = (const xmlNode *) docp;
1060 		const xmlNode *node = base->children;
1061 		while (node != NULL) {
1062 			if (node->type == XML_ELEMENT_NODE) {
1063 				for (const xmlAttr *attr = node->properties; attr != NULL; attr = attr->next) {
1064 					if (attr->atype == XML_ATTRIBUTE_ID && dom_compare_value(attr, BAD_CAST idname)) {
1065 						DOM_RET_OBJ((xmlNodePtr) node, intern);
1066 						return;
1067 					}
1068 				}
1069 			}
1070 
1071 			node = php_dom_next_in_tree_order(node, base);
1072 		}
1073 	}
1074 }
1075 /* }}} end dom_document_get_element_by_id */
1076 
php_dom_transfer_document_ref_single_node(xmlNodePtr node,php_libxml_ref_obj * new_document)1077 static void php_dom_transfer_document_ref_single_node(xmlNodePtr node, php_libxml_ref_obj *new_document)
1078 {
1079 	php_libxml_node_ptr *iteration_object_ptr = node->_private;
1080 	if (iteration_object_ptr) {
1081 		php_libxml_node_object *iteration_object = iteration_object_ptr->_private;
1082 		ZEND_ASSERT(iteration_object != NULL);
1083 		/* Must increase refcount first because we could be the last reference holder, and the document may be equal. */
1084 		new_document->refcount++;
1085 		php_libxml_decrement_doc_ref(iteration_object);
1086 		iteration_object->document = new_document;
1087 	}
1088 }
1089 
php_dom_transfer_document_ref_single_aux(xmlNodePtr node,php_libxml_ref_obj * new_document)1090 static void php_dom_transfer_document_ref_single_aux(xmlNodePtr node, php_libxml_ref_obj *new_document)
1091 {
1092 	php_dom_transfer_document_ref_single_node(node, new_document);
1093 	if (node->type == XML_ELEMENT_NODE) {
1094 		for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1095 			php_dom_transfer_document_ref_single_node((xmlNodePtr) attr, new_document);
1096 		}
1097 	}
1098 }
1099 
php_dom_transfer_document_ref(xmlNodePtr node,php_libxml_ref_obj * new_document)1100 static void php_dom_transfer_document_ref(xmlNodePtr node, php_libxml_ref_obj *new_document)
1101 {
1102 	xmlNodePtr base = node;
1103 	php_dom_transfer_document_ref_single_aux(base, new_document);
1104 
1105 	node = node->children;
1106 	while (node != NULL) {
1107 		php_dom_transfer_document_ref_single_aux(node, new_document);
1108 		node = php_dom_next_in_tree_order(node, base);
1109 	}
1110 }
1111 
1112 /* Workaround for bug that was fixed in https://github.com/GNOME/libxml2/commit/4bc3ebf3eaba352fbbce2ef70ad00a3c7752478a */
1113 #if LIBXML_VERSION < 21000
libxml_copy_dicted_string(xmlDictPtr src_dict,xmlDictPtr dst_dict,xmlChar * str)1114 static xmlChar *libxml_copy_dicted_string(xmlDictPtr src_dict, xmlDictPtr dst_dict, xmlChar *str)
1115 {
1116 	if (str == NULL) {
1117 		return NULL;
1118 	}
1119 	if (xmlDictOwns(src_dict, str) == 1) {
1120 		if (dst_dict == NULL) {
1121 			return xmlStrdup(str);
1122 		}
1123 		return BAD_CAST xmlDictLookup(dst_dict, str, -1);
1124 	}
1125 	return str;
1126 }
1127 
libxml_fixup_name_and_content(xmlDocPtr src_doc,xmlDocPtr dst_doc,xmlNodePtr node)1128 static void libxml_fixup_name_and_content(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1129 {
1130 	if (src_doc != NULL && dst_doc != src_doc && src_doc->dict != NULL) {
1131 		node->name = libxml_copy_dicted_string(src_doc->dict, dst_doc->dict, BAD_CAST node->name);
1132 		node->content = libxml_copy_dicted_string(src_doc->dict, NULL, node->content);
1133 	}
1134 }
1135 
libxml_fixup_name_and_content_element(xmlDocPtr src_doc,xmlDocPtr dst_doc,xmlNodePtr node)1136 static void libxml_fixup_name_and_content_element(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1137 {
1138 	libxml_fixup_name_and_content(src_doc, dst_doc, node);
1139 	for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1140 		libxml_fixup_name_and_content(src_doc, dst_doc, (xmlNodePtr) attr);
1141 	}
1142 
1143 	for (xmlNodePtr child = node->children; child != NULL; child = child->next) {
1144 		libxml_fixup_name_and_content_element(src_doc, dst_doc, child);
1145 	}
1146 }
1147 #endif
1148 
php_dom_adopt_node(xmlNodePtr nodep,dom_object * dom_object_new_document,xmlDocPtr new_document)1149 bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
1150 {
1151 	xmlDocPtr original_document = nodep->doc;
1152 	php_libxml_invalidate_node_list_cache_from_doc(original_document);
1153 	if (nodep->doc != new_document) {
1154 		php_libxml_invalidate_node_list_cache(dom_object_new_document->document);
1155 
1156 		/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
1157 		 * and since this unlink it; the owner element will be unset (i.e. parentNode). */
1158 		if (php_dom_follow_spec_intern(dom_object_new_document)) {
1159 			xmlUnlinkNode(nodep);
1160 			xmlSetTreeDoc(nodep, new_document);
1161 			php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(dom_object_new_document);
1162 			php_dom_libxml_reconcile_modern(ns_mapper, nodep);
1163 #if LIBXML_VERSION < 21000
1164 			libxml_fixup_name_and_content_element(original_document, new_document, nodep);
1165 #endif
1166 		} else {
1167 			int ret = xmlDOMWrapAdoptNode(NULL, original_document, nodep, new_document, NULL, /* options, unused */ 0);
1168 			if (UNEXPECTED(ret != 0)) {
1169 				return false;
1170 			}
1171 		}
1172 
1173 		php_dom_transfer_document_ref(nodep, dom_object_new_document->document);
1174 	} else {
1175 		xmlUnlinkNode(nodep);
1176 	}
1177 	return true;
1178 }
1179 
1180 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-adoptNode
1181 Since: DOM Level 3
1182 Modern spec URL: https://dom.spec.whatwg.org/#dom-document-adoptnode
1183 */
dom_document_adopt_node(INTERNAL_FUNCTION_PARAMETERS,bool modern)1184 static void dom_document_adopt_node(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1185 {
1186 	zval *node_zval;
1187 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node_zval, dom_get_node_ce(modern)) == FAILURE) {
1188 		RETURN_THROWS();
1189 	}
1190 
1191 	xmlNodePtr nodep;
1192 	dom_object *dom_object_nodep;
1193 	DOM_GET_OBJ(nodep, node_zval, xmlNodePtr, dom_object_nodep);
1194 
1195 	if (UNEXPECTED(nodep->type == XML_DOCUMENT_NODE
1196 		|| nodep->type == XML_HTML_DOCUMENT_NODE
1197 		|| nodep->type == XML_DOCUMENT_TYPE_NODE
1198 		|| nodep->type == XML_DTD_NODE
1199 		|| nodep->type == XML_ENTITY_NODE
1200 		|| nodep->type == XML_NOTATION_NODE)) {
1201 		php_dom_throw_error(NOT_SUPPORTED_ERR, dom_get_strict_error(dom_object_nodep->document));
1202 		RETURN_FALSE;
1203 	}
1204 
1205 	xmlDocPtr new_document;
1206 	dom_object *dom_object_new_document;
1207 	zval *new_document_zval = ZEND_THIS;
1208 	DOM_GET_OBJ(new_document, new_document_zval, xmlDocPtr, dom_object_new_document);
1209 
1210 	if (!php_dom_adopt_node(nodep, dom_object_new_document, new_document)) {
1211 		if (modern) {
1212 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
1213 			RETURN_THROWS();
1214 		}
1215 		RETURN_FALSE;
1216 	}
1217 
1218 	RETURN_OBJ_COPY(&dom_object_nodep->std);
1219 }
1220 
PHP_METHOD(DOMDocument,adoptNode)1221 PHP_METHOD(DOMDocument, adoptNode)
1222 {
1223 	dom_document_adopt_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1224 }
1225 
PHP_METHOD(Dom_Document,adoptNode)1226 PHP_METHOD(Dom_Document, adoptNode)
1227 {
1228 	dom_document_adopt_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1229 }
1230 /* }}} end dom_document_adopt_node */
1231 
1232 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-normalizeDocument
1233 Since: DOM Level 3
1234 */
PHP_METHOD(DOMDocument,normalizeDocument)1235 PHP_METHOD(DOMDocument, normalizeDocument)
1236 {
1237 	zval *id;
1238 	xmlDocPtr docp;
1239 	dom_object *intern;
1240 
1241 	id = ZEND_THIS;
1242 	if (zend_parse_parameters_none() == FAILURE) {
1243 		RETURN_THROWS();
1244 	}
1245 
1246 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
1247 
1248 	php_dom_normalize_legacy((xmlNodePtr) docp);
1249 }
1250 /* }}} end dom_document_normalize_document */
1251 
1252 /* {{{ */
PHP_METHOD(DOMDocument,__construct)1253 PHP_METHOD(DOMDocument, __construct)
1254 {
1255 	xmlDoc *docp = NULL, *olddoc;
1256 	dom_object *intern;
1257 	char *encoding, *version = NULL;
1258 	size_t encoding_len = 0, version_len = 0;
1259 	unsigned int refcount;
1260 
1261 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ss", &version, &version_len, &encoding, &encoding_len) == FAILURE) {
1262 		RETURN_THROWS();
1263 	}
1264 
1265 	docp = xmlNewDoc(BAD_CAST version);
1266 
1267 	if (!docp) {
1268 		php_dom_throw_error(INVALID_STATE_ERR, true);
1269 		return;
1270 	}
1271 
1272 	if (encoding_len > 0) {
1273 		docp->encoding = (const xmlChar *) xmlStrdup(BAD_CAST encoding);
1274 	}
1275 
1276 	intern = Z_DOMOBJ_P(ZEND_THIS);
1277 	olddoc = (xmlDocPtr) dom_object_get_node(intern);
1278 	if (olddoc != NULL) {
1279 		php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
1280 		refcount = php_libxml_decrement_doc_ref((php_libxml_node_object *)intern);
1281 		if (refcount != 0) {
1282 			olddoc->_private = NULL;
1283 		}
1284 	}
1285 	intern->document = NULL;
1286 	php_libxml_increment_doc_ref((php_libxml_node_object *)intern, docp);
1287 	php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)docp, (void *)intern);
1288 }
1289 /* }}} end DOMDocument::__construct */
1290 
dom_get_valid_file_path(const char * source,char * resolved_path,int resolved_path_len)1291 const char *dom_get_valid_file_path(const char *source, char *resolved_path, int resolved_path_len ) /* {{{ */
1292 {
1293 	xmlURI *uri;
1294 	xmlChar *escsource;
1295 	const char *file_dest;
1296 	int isFileUri = 0;
1297 
1298 	uri = xmlCreateURI();
1299 	if (uri == NULL) {
1300 		return NULL;
1301 	}
1302 	escsource = xmlURIEscapeStr(BAD_CAST source, BAD_CAST ":");
1303 	xmlParseURIReference(uri, (char *) escsource);
1304 	xmlFree(escsource);
1305 
1306 	if (uri->scheme != NULL) {
1307 		/* absolute file uris - libxml only supports localhost or empty host */
1308 #ifdef PHP_WIN32
1309 		if (strncasecmp(source, "file://",7) == 0 && ':' == source[8]) {
1310 			isFileUri = 1;
1311 			source += 7;
1312 		} else
1313 #endif
1314 		if (strncasecmp(source, "file:///",8) == 0) {
1315 			isFileUri = 1;
1316 #ifdef PHP_WIN32
1317 			source += 8;
1318 #else
1319 			source += 7;
1320 #endif
1321 		} else if (strncasecmp(source, "file://localhost/",17) == 0) {
1322 			isFileUri = 1;
1323 #ifdef PHP_WIN32
1324 			source += 17;
1325 #else
1326 			source += 16;
1327 #endif
1328 		}
1329 	}
1330 
1331 	file_dest = source;
1332 
1333 	if ((uri->scheme == NULL || isFileUri)) {
1334 		/* XXX possible buffer overflow if VCWD_REALPATH does not know size of resolved_path */
1335 		if (!VCWD_REALPATH(source, resolved_path) && !expand_filepath(source, resolved_path)) {
1336 			xmlFreeURI(uri);
1337 			return NULL;
1338 		}
1339 		file_dest = resolved_path;
1340 	}
1341 
1342 	xmlFreeURI(uri);
1343 
1344 	return file_dest;
1345 }
1346 /* }}} */
1347 
dom_document_parser(zval * id,dom_load_mode mode,const char * source,size_t source_len,size_t options,xmlCharEncodingHandlerPtr encoding)1348 xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source, size_t source_len, size_t options, xmlCharEncodingHandlerPtr encoding) /* {{{ */
1349 {
1350 	xmlDocPtr ret;
1351 	xmlParserCtxtPtr ctxt = NULL;
1352 	int validate, recover, resolve_externals, keep_blanks, substitute_ent;
1353 	int resolved_path_len;
1354 	int old_error_reporting = 0;
1355 	char *directory=NULL, resolved_path[MAXPATHLEN + 1];
1356 
1357 	libxml_doc_props const* doc_props;
1358 	if (id == NULL) {
1359 		doc_props = dom_get_doc_props_read_only(NULL);
1360 	} else {
1361 		dom_object *intern = Z_DOMOBJ_P(id);
1362 		php_libxml_ref_obj *document = intern->document;
1363 		doc_props = dom_get_doc_props_read_only(document);
1364 	}
1365 	validate = doc_props->validateonparse;
1366 	resolve_externals = doc_props->resolveexternals;
1367 	keep_blanks = doc_props->preservewhitespace;
1368 	substitute_ent = doc_props->substituteentities;
1369 	recover = doc_props->recover || (options & XML_PARSE_RECOVER) == XML_PARSE_RECOVER;
1370 
1371 	xmlInitParser();
1372 
1373 	if (mode == DOM_LOAD_FILE) {
1374 		if (CHECK_NULL_PATH(source, source_len)) {
1375 			zend_argument_value_error(1, "must not contain any null bytes");
1376 			return NULL;
1377 		}
1378 		const char *file_dest = dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
1379 		if (file_dest) {
1380 			ctxt = xmlCreateFileParserCtxt(file_dest);
1381 		}
1382 	} else {
1383 		ctxt = xmlCreateMemoryParserCtxt(source, source_len);
1384 	}
1385 
1386 	if (ctxt == NULL) {
1387 		return(NULL);
1388 	}
1389 
1390 	if (encoding != NULL) {
1391 		/* Note: libxml 2.12+ doesn't handle NULL encoding well. */
1392 		(void) xmlSwitchToEncoding(ctxt, encoding);
1393 	}
1394 
1395 	/* If loading from memory, we need to set the base directory for the document */
1396 	if (mode != DOM_LOAD_FILE) {
1397 #ifdef HAVE_GETCWD
1398 		directory = VCWD_GETCWD(resolved_path, MAXPATHLEN);
1399 #elif defined(HAVE_GETWD)
1400 		directory = VCWD_GETWD(resolved_path);
1401 #endif
1402 		if (directory) {
1403 			if(ctxt->directory != NULL) {
1404 				xmlFree((char *) ctxt->directory);
1405 			}
1406 			resolved_path_len = strlen(resolved_path);
1407 			if (resolved_path[resolved_path_len - 1] != DEFAULT_SLASH) {
1408 				resolved_path[resolved_path_len] = DEFAULT_SLASH;
1409 				resolved_path[++resolved_path_len] = '\0';
1410 			}
1411 			ctxt->directory = (char *) xmlCanonicPath((const xmlChar *) resolved_path);
1412 		}
1413 	}
1414 
1415 	ctxt->vctxt.error = php_libxml_ctx_error;
1416 	ctxt->vctxt.warning = php_libxml_ctx_warning;
1417 
1418 	if (ctxt->sax != NULL) {
1419 		ctxt->sax->error = php_libxml_ctx_error;
1420 		ctxt->sax->warning = php_libxml_ctx_warning;
1421 	}
1422 
1423 	if (validate && ! (options & XML_PARSE_DTDVALID)) {
1424 		options |= XML_PARSE_DTDVALID;
1425 	}
1426 	if (resolve_externals && ! (options & XML_PARSE_DTDATTR)) {
1427 		options |= XML_PARSE_DTDATTR;
1428 	}
1429 	if (substitute_ent && ! (options & XML_PARSE_NOENT)) {
1430 		options |= XML_PARSE_NOENT;
1431 	}
1432 	if (keep_blanks == 0 && ! (options & XML_PARSE_NOBLANKS)) {
1433 		options |= XML_PARSE_NOBLANKS;
1434 	}
1435 	if (recover) {
1436 		options |= XML_PARSE_RECOVER;
1437 	}
1438 
1439 	php_libxml_sanitize_parse_ctxt_options(ctxt);
1440 	xmlCtxtUseOptions(ctxt, options);
1441 
1442 	if (recover) {
1443 		old_error_reporting = EG(error_reporting);
1444 		EG(error_reporting) = old_error_reporting | E_WARNING;
1445 	}
1446 
1447 	xmlParseDocument(ctxt);
1448 
1449 	if (ctxt->wellFormed || recover) {
1450 		ret = ctxt->myDoc;
1451 		if (recover) {
1452 			EG(error_reporting) = old_error_reporting;
1453 		}
1454 		/* If loading from memory, set the base reference uri for the document */
1455 		if (ret && ret->URL == NULL && ctxt->directory != NULL) {
1456 			ret->URL = xmlStrdup(BAD_CAST ctxt->directory);
1457 		}
1458 	} else {
1459 		ret = DOM_DOCUMENT_MALFORMED;
1460 		xmlFreeDoc(ctxt->myDoc);
1461 		ctxt->myDoc = NULL;
1462 	}
1463 
1464 	xmlFreeParserCtxt(ctxt);
1465 
1466 	return(ret);
1467 }
1468 /* }}} */
1469 
php_dom_finish_loading_document(zval * this,zval * return_value,xmlDocPtr newdoc)1470 static void php_dom_finish_loading_document(zval *this, zval *return_value, xmlDocPtr newdoc)
1471 {
1472 	if (!newdoc)
1473 		RETURN_FALSE;
1474 
1475 	dom_object *intern = Z_DOMOBJ_P(this);
1476 	size_t old_modification_nr = 0;
1477 	if (intern != NULL) {
1478 		php_libxml_class_type class_type = PHP_LIBXML_CLASS_LEGACY;
1479 		xmlDocPtr docp = (xmlDocPtr) dom_object_get_node(intern);
1480 		dom_doc_propsptr doc_prop = NULL;
1481 		if (docp != NULL) {
1482 			const php_libxml_ref_obj *doc_ptr = intern->document;
1483 			ZEND_ASSERT(doc_ptr != NULL); /* Must exist, we have a document */
1484 			class_type = doc_ptr->class_type;
1485 			old_modification_nr = doc_ptr->cache_tag.modification_nr;
1486 			php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
1487 			doc_prop = intern->document->doc_props;
1488 			intern->document->doc_props = NULL;
1489 			unsigned int refcount = php_libxml_decrement_doc_ref((php_libxml_node_object *)intern);
1490 			if (refcount != 0) {
1491 				docp->_private = NULL;
1492 			}
1493 		}
1494 		intern->document = NULL;
1495 		php_libxml_increment_doc_ref((php_libxml_node_object *)intern, newdoc);
1496 		intern->document->doc_props = doc_prop;
1497 		intern->document->class_type = class_type;
1498 	}
1499 
1500 	php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)newdoc, (void *)intern);
1501 	/* Since iterators should invalidate, we need to start the modification number from the old counter */
1502 	if (old_modification_nr != 0) {
1503 		intern->document->cache_tag.modification_nr = old_modification_nr;
1504 		php_libxml_invalidate_node_list_cache(intern->document);
1505 	}
1506 
1507 	RETURN_TRUE;
1508 }
1509 
dom_parse_document(INTERNAL_FUNCTION_PARAMETERS,int mode)1510 static void dom_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode)
1511 {
1512 	char *source;
1513 	size_t source_len;
1514 	zend_long options = 0;
1515 
1516 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &source, &source_len, &options) == FAILURE) {
1517 		RETURN_THROWS();
1518 	}
1519 
1520 	if (!source_len) {
1521 		zend_argument_must_not_be_empty_error(1);
1522 		RETURN_THROWS();
1523 	}
1524 	if (ZEND_SIZE_T_INT_OVFL(source_len)) {
1525 		php_error_docref(NULL, E_WARNING, "Input string is too long");
1526 		RETURN_FALSE;
1527 	}
1528 	if (ZEND_LONG_EXCEEDS_INT(options)) {
1529 		php_error_docref(NULL, E_WARNING, "Invalid options");
1530 		RETURN_FALSE;
1531 	}
1532 
1533 	xmlDocPtr newdoc = dom_document_parser(ZEND_THIS, mode, source, source_len, options, NULL);
1534 	if (newdoc == DOM_DOCUMENT_MALFORMED) {
1535 		newdoc = NULL;
1536 	}
1537 	php_dom_finish_loading_document(ZEND_THIS, return_value, newdoc);
1538 }
1539 
1540 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-LS/load-save.html#LS-DocumentLS-load
1541 Since: DOM Level 3
1542 */
PHP_METHOD(DOMDocument,load)1543 PHP_METHOD(DOMDocument, load)
1544 {
1545 	dom_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
1546 }
1547 /* }}} end dom_document_load */
1548 
1549 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-LS/load-save.html#LS-DocumentLS-loadXML
1550 Since: DOM Level 3
1551 */
PHP_METHOD(DOMDocument,loadXML)1552 PHP_METHOD(DOMDocument, loadXML)
1553 {
1554 	dom_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
1555 }
1556 /* }}} end dom_document_loadxml */
1557 
1558 /* {{{ Convenience method to save to file */
PHP_METHOD(DOMDocument,save)1559 PHP_METHOD(DOMDocument, save)
1560 {
1561 	zval *id;
1562 	xmlDoc *docp;
1563 	size_t file_len = 0;
1564 	int saveempty = 0;
1565 	dom_object *intern;
1566 	char *file;
1567 	zend_long options = 0;
1568 
1569 	id = ZEND_THIS;
1570 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|l", &file, &file_len, &options) == FAILURE) {
1571 		RETURN_THROWS();
1572 	}
1573 
1574 	if (file_len == 0) {
1575 		zend_argument_must_not_be_empty_error(1);
1576 		RETURN_THROWS();
1577 	}
1578 
1579 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
1580 
1581 	/* encoding handled by property on doc */
1582 
1583 	libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document);
1584 	bool format = doc_props->formatoutput;
1585 	if (options & LIBXML_SAVE_NOEMPTYTAG) {
1586 		saveempty = xmlSaveNoEmptyTags;
1587 		xmlSaveNoEmptyTags = 1;
1588 	}
1589 	zend_long bytes = intern->document->handlers->dump_doc_to_file(file, docp, format, (const char *) docp->encoding);
1590 	if (options & LIBXML_SAVE_NOEMPTYTAG) {
1591 		xmlSaveNoEmptyTags = saveempty;
1592 	}
1593 	if (bytes == -1) {
1594 		RETURN_FALSE;
1595 	}
1596 	RETURN_LONG(bytes);
1597 }
1598 /* }}} end dom_document_save */
1599 
1600 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-LS/load-save.html#LS-DocumentLS-saveXML
1601 Since: DOM Level 3
1602 */
dom_document_save_xml(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)1603 static void dom_document_save_xml(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
1604 {
1605 	zval *nodep = NULL;
1606 	xmlDoc *docp;
1607 	xmlNode *node;
1608 	dom_object *intern, *nodeobj;
1609 	int old_xml_save_no_empty_tags;
1610 	zend_long options = 0;
1611 
1612 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|O!l", &nodep, node_ce, &options) != SUCCESS) {
1613 		RETURN_THROWS();
1614 	}
1615 
1616 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
1617 
1618 	libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document);
1619 	bool format = doc_props->formatoutput;
1620 
1621 	zend_string *res;
1622 	if (nodep != NULL) {
1623 		/* Dump contents of Node */
1624 		DOM_GET_OBJ(node, nodep, xmlNodePtr, nodeobj);
1625 		if (node->doc != docp) {
1626 			php_dom_throw_error(WRONG_DOCUMENT_ERR, dom_get_strict_error(intern->document));
1627 			RETURN_FALSE;
1628 		}
1629 
1630 		/* Save libxml2 global, override its value, and restore after saving (don't move me or risk breaking the state
1631 		 * w.r.t. the implicit return in DOM_GET_OBJ). */
1632 		old_xml_save_no_empty_tags = xmlSaveNoEmptyTags;
1633 		xmlSaveNoEmptyTags = (options & LIBXML_SAVE_NOEMPTYTAG) ? 1 : 0;
1634 		res = intern->document->handlers->dump_node_to_str(docp, node, format, (const char *) docp->encoding);
1635 		xmlSaveNoEmptyTags = old_xml_save_no_empty_tags;
1636 	} else {
1637 		int converted_options = XML_SAVE_AS_XML;
1638 		if (options & XML_SAVE_NO_DECL) {
1639 			converted_options |= XML_SAVE_NO_DECL;
1640 		}
1641 		if (format) {
1642 			converted_options |= XML_SAVE_FORMAT;
1643 		}
1644 
1645 		/* Save libxml2 global, override its value, and restore after saving. */
1646 		old_xml_save_no_empty_tags = xmlSaveNoEmptyTags;
1647 		xmlSaveNoEmptyTags = (options & LIBXML_SAVE_NOEMPTYTAG) ? 1 : 0;
1648 		res = intern->document->handlers->dump_doc_to_str(docp, converted_options, (const char *) docp->encoding);
1649 		xmlSaveNoEmptyTags = old_xml_save_no_empty_tags;
1650 	}
1651 
1652 	if (!res) {
1653 		php_error_docref(NULL, E_WARNING, "Could not save document");
1654 		RETURN_FALSE;
1655 	} else {
1656 		RETURN_NEW_STR(res);
1657 	}
1658 }
1659 
PHP_METHOD(DOMDocument,saveXML)1660 PHP_METHOD(DOMDocument, saveXML)
1661 {
1662 	dom_document_save_xml(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
1663 }
1664 
PHP_METHOD(Dom_XMLDocument,saveXml)1665 PHP_METHOD(Dom_XMLDocument, saveXml)
1666 {
1667 	dom_document_save_xml(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
1668 }
1669 /* }}} end dom_document_savexml */
1670 
php_dom_free_xinclude_node(xmlNodePtr cur)1671 static xmlNodePtr php_dom_free_xinclude_node(xmlNodePtr cur) /* {{{ */
1672 {
1673 	xmlNodePtr xincnode;
1674 
1675 	xincnode = cur;
1676 	cur = cur->next;
1677 	xmlUnlinkNode(xincnode);
1678 	php_libxml_node_free_resource(xincnode);
1679 
1680 	return cur;
1681 }
1682 /* }}} */
1683 
php_dom_remove_xinclude_nodes(xmlNodePtr cur)1684 static void php_dom_remove_xinclude_nodes(xmlNodePtr cur) /* {{{ */
1685 {
1686 	while(cur) {
1687 		if (cur->type == XML_XINCLUDE_START) {
1688 			cur = php_dom_free_xinclude_node(cur);
1689 
1690 			/* XML_XINCLUDE_END node will be a sibling of XML_XINCLUDE_START */
1691 			while(cur && cur->type != XML_XINCLUDE_END) {
1692 				/* remove xinclude processing nodes from recursive xincludes */
1693 				if (cur->type == XML_ELEMENT_NODE) {
1694 					   php_dom_remove_xinclude_nodes(cur->children);
1695 				}
1696 				cur = cur->next;
1697 			}
1698 
1699 			if (cur && cur->type == XML_XINCLUDE_END) {
1700 				cur = php_dom_free_xinclude_node(cur);
1701 			}
1702 		} else {
1703 			if (cur->type == XML_ELEMENT_NODE) {
1704 				php_dom_remove_xinclude_nodes(cur->children);
1705 			}
1706 			cur = cur->next;
1707 		}
1708 	}
1709 }
1710 /* }}} */
1711 
dom_xinclude_strip_references(xmlNodePtr basep)1712 static void dom_xinclude_strip_references(xmlNodePtr basep)
1713 {
1714 	php_libxml_node_free_resource(basep);
1715 
1716 	xmlNodePtr current = basep->children;
1717 
1718 	while (current) {
1719 		php_libxml_node_free_resource(current);
1720 		current = php_dom_next_in_tree_order(current, basep);
1721 	}
1722 }
1723 
1724 /* See GH-14702.
1725  * We have to remove userland references to xinclude fallback nodes because libxml2 will make clones of these
1726  * and remove the original nodes. If the originals are removed while there are still userland references
1727  * this will cause memory corruption. */
dom_xinclude_strip_fallback_references(const xmlNode * basep)1728 static void dom_xinclude_strip_fallback_references(const xmlNode *basep)
1729 {
1730 	xmlNodePtr current = basep->children;
1731 
1732 	while (current) {
1733 		if (current->type == XML_ELEMENT_NODE && current->ns != NULL && current->_private != NULL
1734 			&& xmlStrEqual(current->name, XINCLUDE_FALLBACK)
1735 			&& (xmlStrEqual(current->ns->href, XINCLUDE_NS) || xmlStrEqual(current->ns->href, XINCLUDE_OLD_NS))) {
1736 			dom_xinclude_strip_references(current);
1737 		}
1738 
1739 		current = php_dom_next_in_tree_order(current, basep);
1740 	}
1741 }
1742 
dom_perform_xinclude(xmlDocPtr docp,dom_object * intern,zend_long flags)1743 static int dom_perform_xinclude(xmlDocPtr docp, dom_object *intern, zend_long flags)
1744 {
1745 	dom_xinclude_strip_fallback_references((const xmlNode *) docp);
1746 
1747 	PHP_LIBXML_SANITIZE_GLOBALS(xinclude);
1748 	int err = xmlXIncludeProcessFlags(docp, (int)flags);
1749 	PHP_LIBXML_RESTORE_GLOBALS(xinclude);
1750 
1751 	/* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these
1752 	are added via xmlXIncludeProcess to mark beginning and ending of xincluded document
1753 	but are not wanted in resulting document - must be done even if err as it could fail after
1754 	having processed some xincludes */
1755 	xmlNodePtr root = docp->children;
1756 	while (root && root->type != XML_ELEMENT_NODE && root->type != XML_XINCLUDE_START) {
1757 		root = root->next;
1758 	}
1759 	if (root) {
1760 		php_dom_remove_xinclude_nodes(root);
1761 	}
1762 
1763 	php_libxml_invalidate_node_list_cache(intern->document);
1764 
1765 	return err;
1766 }
1767 
1768 /* {{{ Substitutues xincludes in a DomDocument */
PHP_METHOD(DOMDocument,xinclude)1769 PHP_METHOD(DOMDocument, xinclude)
1770 {
1771 	xmlDoc *docp;
1772 	zend_long flags = 0;
1773 	dom_object *intern;
1774 
1775 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags) == FAILURE) {
1776 		RETURN_THROWS();
1777 	}
1778 
1779 	if (ZEND_LONG_EXCEEDS_INT(flags)) {
1780 		php_error_docref(NULL, E_WARNING, "Invalid flags");
1781 		RETURN_FALSE;
1782 	}
1783 
1784 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
1785 
1786 	int err = dom_perform_xinclude(docp, intern, flags);
1787 
1788 	if (err) {
1789 		RETVAL_LONG(err);
1790 	} else {
1791 		RETVAL_FALSE;
1792 	}
1793 }
1794 
PHP_METHOD(Dom_XMLDocument,xinclude)1795 PHP_METHOD(Dom_XMLDocument, xinclude)
1796 {
1797 	xmlDoc *docp;
1798 	zend_long flags = 0;
1799 	dom_object *intern;
1800 
1801 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags) == FAILURE) {
1802 		RETURN_THROWS();
1803 	}
1804 
1805 	if (ZEND_LONG_EXCEEDS_INT(flags)) {
1806 		zend_argument_value_error(1, "is too large");
1807 		RETURN_THROWS();
1808 	}
1809 
1810 	DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
1811 
1812 	int err = dom_perform_xinclude(docp, intern, flags);
1813 
1814 	if (err < 0) {
1815 		php_dom_throw_error(INVALID_MODIFICATION_ERR, /* strict */ true);
1816 	} else {
1817 		RETURN_LONG(err);
1818 	}
1819 }
1820 /* }}} */
1821 
1822 /* {{{ Since: DOM extended */
PHP_METHOD(DOMDocument,validate)1823 PHP_METHOD(DOMDocument, validate)
1824 {
1825 	zval *id;
1826 	xmlDoc *docp;
1827 	dom_object *intern;
1828 	xmlValidCtxt *cvp;
1829 
1830 	id = ZEND_THIS;
1831 	if (zend_parse_parameters_none() == FAILURE) {
1832 		RETURN_THROWS();
1833 	}
1834 
1835 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
1836 
1837 	PHP_LIBXML_SANITIZE_GLOBALS(validate);
1838 	cvp = xmlNewValidCtxt();
1839 
1840 	cvp->userData = NULL;
1841 	cvp->error    = (xmlValidityErrorFunc) php_libxml_error_handler;
1842 	cvp->warning  = (xmlValidityErrorFunc) php_libxml_error_handler;
1843 
1844 	if (xmlValidateDocument(cvp, docp)) {
1845 		RETVAL_TRUE;
1846 	} else {
1847 		RETVAL_FALSE;
1848 	}
1849 	PHP_LIBXML_RESTORE_GLOBALS(validate);
1850 
1851 	xmlFreeValidCtxt(cvp);
1852 
1853 }
1854 /* }}} */
1855 
1856 #ifdef LIBXML_SCHEMAS_ENABLED
dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS,int type)1857 static void dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */
1858 {
1859 	zval *id;
1860 	xmlDoc *docp;
1861 	dom_object *intern;
1862 	char *source = NULL;
1863 	const char *valid_file = NULL;
1864 	size_t source_len = 0;
1865 	int valid_opts = 0;
1866 	zend_long flags = 0;
1867 	xmlSchemaParserCtxtPtr  parser;
1868 	xmlSchemaPtr            sptr;
1869 	xmlSchemaValidCtxtPtr   vptr;
1870 	int                     is_valid;
1871 	char resolved_path[MAXPATHLEN + 1];
1872 
1873 	id = ZEND_THIS;
1874 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &source, &source_len, &flags) == FAILURE) {
1875 		RETURN_THROWS();
1876 	}
1877 
1878 	if (!source_len) {
1879 		zend_argument_must_not_be_empty_error(1);
1880 		RETURN_THROWS();
1881 	}
1882 
1883 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
1884 
1885 	PHP_LIBXML_SANITIZE_GLOBALS(new_parser_ctxt);
1886 
1887 	switch (type) {
1888 	case DOM_LOAD_FILE:
1889 		if (CHECK_NULL_PATH(source, source_len)) {
1890 			PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
1891 			zend_argument_value_error(1, "must not contain any null bytes");
1892 			RETURN_THROWS();
1893 		}
1894 		valid_file = dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
1895 		if (!valid_file) {
1896 			PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
1897 			php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
1898 			RETURN_FALSE;
1899 		}
1900 		parser = xmlSchemaNewParserCtxt(valid_file);
1901 		break;
1902 	case DOM_LOAD_STRING:
1903 		parser = xmlSchemaNewMemParserCtxt(source, source_len);
1904 		/* If loading from memory, we need to set the base directory for the document
1905 		   but it is not apparent how to do that for schema's */
1906 		break;
1907 	default:
1908 		return;
1909 	}
1910 
1911 	xmlSchemaSetParserErrors(parser,
1912 		(xmlSchemaValidityErrorFunc) php_libxml_error_handler,
1913 		(xmlSchemaValidityWarningFunc) php_libxml_error_handler,
1914 		parser);
1915 	sptr = xmlSchemaParse(parser);
1916 	xmlSchemaFreeParserCtxt(parser);
1917 	PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
1918 	if (!sptr) {
1919 		if (!EG(exception)) {
1920 			php_error_docref(NULL, E_WARNING, "Invalid Schema");
1921 		}
1922 		RETURN_FALSE;
1923 	}
1924 
1925 	docp = (xmlDocPtr) dom_object_get_node(intern);
1926 
1927 	vptr = xmlSchemaNewValidCtxt(sptr);
1928 	if (!vptr) {
1929 		xmlSchemaFree(sptr);
1930 		zend_throw_error(NULL, "Invalid Schema Validation Context");
1931 		RETURN_THROWS();
1932 	}
1933 
1934 	if (flags & XML_SCHEMA_VAL_VC_I_CREATE) {
1935 		valid_opts |= XML_SCHEMA_VAL_VC_I_CREATE;
1936 	}
1937 
1938 	PHP_LIBXML_SANITIZE_GLOBALS(validate);
1939 	xmlSchemaSetValidOptions(vptr, valid_opts);
1940 	xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
1941 	is_valid = xmlSchemaValidateDoc(vptr, docp);
1942 	xmlSchemaFree(sptr);
1943 	xmlSchemaFreeValidCtxt(vptr);
1944 	PHP_LIBXML_RESTORE_GLOBALS(validate);
1945 
1946 	if (is_valid == 0) {
1947 		RETURN_TRUE;
1948 	} else {
1949 		RETURN_FALSE;
1950 	}
1951 }
1952 /* }}} */
1953 
1954 /* {{{ */
PHP_METHOD(DOMDocument,schemaValidate)1955 PHP_METHOD(DOMDocument, schemaValidate)
1956 {
1957 	dom_document_schema_validate(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
1958 }
1959 /* }}} end dom_document_schema_validate_file */
1960 
1961 /* {{{ */
PHP_METHOD(DOMDocument,schemaValidateSource)1962 PHP_METHOD(DOMDocument, schemaValidateSource)
1963 {
1964 	dom_document_schema_validate(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
1965 }
1966 /* }}} end dom_document_schema_validate */
1967 
dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS,int type)1968 static void dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */
1969 {
1970 	zval *id;
1971 	xmlDoc *docp;
1972 	dom_object *intern;
1973 	char *source = NULL;
1974 	const char *valid_file = NULL;
1975 	size_t source_len = 0;
1976 	xmlRelaxNGParserCtxtPtr parser;
1977 	xmlRelaxNGPtr           sptr;
1978 	xmlRelaxNGValidCtxtPtr  vptr;
1979 	int                     is_valid;
1980 	char resolved_path[MAXPATHLEN + 1];
1981 
1982 	id = ZEND_THIS;
1983 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &source, &source_len) == FAILURE) {
1984 		RETURN_THROWS();
1985 	}
1986 
1987 	if (!source_len) {
1988 		zend_argument_must_not_be_empty_error(1);
1989 		RETURN_THROWS();
1990 	}
1991 
1992 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
1993 
1994 	switch (type) {
1995 	case DOM_LOAD_FILE:
1996 		if (CHECK_NULL_PATH(source, source_len)) {
1997 			zend_argument_value_error(1, "must not contain any null bytes");
1998 			RETURN_THROWS();
1999 		}
2000 		valid_file = dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
2001 		if (!valid_file) {
2002 			php_error_docref(NULL, E_WARNING, "Invalid RelaxNG file source");
2003 			RETURN_FALSE;
2004 		}
2005 		parser = xmlRelaxNGNewParserCtxt(valid_file);
2006 		break;
2007 	case DOM_LOAD_STRING:
2008 		parser = xmlRelaxNGNewMemParserCtxt(source, source_len);
2009 		/* If loading from memory, we need to set the base directory for the document
2010 		   but it is not apparent how to do that for schema's */
2011 		break;
2012 	default:
2013 		return;
2014 	}
2015 
2016 	PHP_LIBXML_SANITIZE_GLOBALS(parse);
2017 	xmlRelaxNGSetParserErrors(parser,
2018 		(xmlRelaxNGValidityErrorFunc) php_libxml_error_handler,
2019 		(xmlRelaxNGValidityWarningFunc) php_libxml_error_handler,
2020 		parser);
2021 	sptr = xmlRelaxNGParse(parser);
2022 	xmlRelaxNGFreeParserCtxt(parser);
2023 	PHP_LIBXML_RESTORE_GLOBALS(parse);
2024 	if (!sptr) {
2025 		php_error_docref(NULL, E_WARNING, "Invalid RelaxNG");
2026 		RETURN_FALSE;
2027 	}
2028 
2029 	docp = (xmlDocPtr) dom_object_get_node(intern);
2030 
2031 	vptr = xmlRelaxNGNewValidCtxt(sptr);
2032 	if (!vptr) {
2033 		xmlRelaxNGFree(sptr);
2034 		zend_throw_error(NULL, "Invalid RelaxNG Validation Context");
2035 		RETURN_THROWS();
2036 	}
2037 
2038 	xmlRelaxNGSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
2039 	is_valid = xmlRelaxNGValidateDoc(vptr, docp);
2040 	xmlRelaxNGFree(sptr);
2041 	xmlRelaxNGFreeValidCtxt(vptr);
2042 
2043 	if (is_valid == 0) {
2044 		RETURN_TRUE;
2045 	} else {
2046 		RETURN_FALSE;
2047 	}
2048 }
2049 /* }}} */
2050 
2051 /* {{{ */
PHP_METHOD(DOMDocument,relaxNGValidate)2052 PHP_METHOD(DOMDocument, relaxNGValidate)
2053 {
2054 	dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
2055 }
2056 /* }}} end dom_document_relaxNG_validate_file */
2057 
2058 /* {{{ */
PHP_METHOD(DOMDocument,relaxNGValidateSource)2059 PHP_METHOD(DOMDocument, relaxNGValidateSource)
2060 {
2061 	dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
2062 }
2063 /* }}} end dom_document_relaxNG_validate_xml */
2064 
2065 #endif
2066 
2067 #ifdef LIBXML_HTML_ENABLED
2068 
dom_load_html(INTERNAL_FUNCTION_PARAMETERS,int mode)2069 static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
2070 {
2071 	char *source;
2072 	size_t source_len;
2073 	zend_long options = 0;
2074 	htmlParserCtxtPtr ctxt;
2075 
2076 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &source, &source_len, &options) == FAILURE) {
2077 		RETURN_THROWS();
2078 	}
2079 
2080 	if (!source_len) {
2081 		zend_argument_must_not_be_empty_error(1);
2082 		RETURN_THROWS();
2083 	}
2084 
2085 	if (ZEND_LONG_EXCEEDS_INT(options)) {
2086 		php_error_docref(NULL, E_WARNING, "Invalid options");
2087 		RETURN_FALSE;
2088 	}
2089 
2090 	if (mode == DOM_LOAD_FILE) {
2091 		if (CHECK_NULL_PATH(source, source_len)) {
2092 			zend_argument_value_error(1, "must not contain any null bytes");
2093 			RETURN_THROWS();
2094 		}
2095 		ctxt = htmlCreateFileParserCtxt(source, NULL);
2096 	} else {
2097 		if (ZEND_SIZE_T_INT_OVFL(source_len)) {
2098 			php_error_docref(NULL, E_WARNING, "Input string is too long");
2099 			RETURN_FALSE;
2100 		}
2101 		ctxt = htmlCreateMemoryParserCtxt(source, (int)source_len);
2102 	}
2103 
2104 	if (!ctxt) {
2105 		RETURN_FALSE;
2106 	}
2107 
2108 
2109 	ctxt->vctxt.error = php_libxml_ctx_error;
2110 	ctxt->vctxt.warning = php_libxml_ctx_warning;
2111 	if (ctxt->sax != NULL) {
2112 		ctxt->sax->error = php_libxml_ctx_error;
2113 		ctxt->sax->warning = php_libxml_ctx_warning;
2114 	}
2115 	php_libxml_sanitize_parse_ctxt_options(ctxt);
2116 	if (options) {
2117 		htmlCtxtUseOptions(ctxt, (int)options);
2118 	}
2119 	htmlParseDocument(ctxt);
2120 	xmlDocPtr newdoc = ctxt->myDoc;
2121 	htmlFreeParserCtxt(ctxt);
2122 
2123 	php_dom_finish_loading_document(ZEND_THIS, return_value, newdoc);
2124 }
2125 /* }}} */
2126 
2127 /* {{{ Since: DOM extended */
PHP_METHOD(DOMDocument,loadHTMLFile)2128 PHP_METHOD(DOMDocument, loadHTMLFile)
2129 {
2130 	dom_load_html(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
2131 }
2132 /* }}} end dom_document_load_html_file */
2133 
2134 /* {{{ Since: DOM extended */
PHP_METHOD(DOMDocument,loadHTML)2135 PHP_METHOD(DOMDocument, loadHTML)
2136 {
2137 	dom_load_html(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
2138 }
2139 /* }}} end dom_document_load_html */
2140 
2141 /* {{{ Convenience method to save to file as html */
PHP_METHOD(DOMDocument,saveHTMLFile)2142 PHP_METHOD(DOMDocument, saveHTMLFile)
2143 {
2144 	zval *id;
2145 	xmlDoc *docp;
2146 	size_t file_len;
2147 	int bytes, format;
2148 	dom_object *intern;
2149 	char *file;
2150 	const char *encoding;
2151 
2152 	id = ZEND_THIS;
2153 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &file, &file_len) == FAILURE) {
2154 		RETURN_THROWS();
2155 	}
2156 
2157 	if (file_len == 0) {
2158 		zend_argument_must_not_be_empty_error(1);
2159 		RETURN_THROWS();
2160 	}
2161 
2162 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
2163 
2164 
2165 	encoding = (const char *) htmlGetMetaEncoding(docp);
2166 
2167 	libxml_doc_props const* doc_props = dom_get_doc_props_read_only(intern->document);
2168 	format = doc_props->formatoutput;
2169 	bytes = htmlSaveFileFormat(file, docp, encoding, format);
2170 
2171 	if (bytes == -1) {
2172 		RETURN_FALSE;
2173 	}
2174 	RETURN_LONG(bytes);
2175 }
2176 /* }}} end dom_document_save_html_file */
2177 
2178 /* {{{ Convenience method to output as html */
PHP_METHOD(DOMDocument,saveHTML)2179 PHP_METHOD(DOMDocument, saveHTML)
2180 {
2181 	zval *id, *nodep = NULL;
2182 	xmlDoc *docp;
2183 	xmlNode *node;
2184 	xmlOutputBufferPtr outBuf;
2185 	xmlBufferPtr buf;
2186 	dom_object *intern, *nodeobj;
2187 	int format;
2188 
2189 	id = ZEND_THIS;
2190 	if (zend_parse_parameters(ZEND_NUM_ARGS(),
2191 		"|O!", &nodep, dom_node_class_entry)
2192 		== FAILURE) {
2193 		RETURN_THROWS();
2194 	}
2195 
2196 	DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
2197 
2198 	libxml_doc_props const* doc_props = dom_get_doc_props(intern->document);
2199 	format = doc_props->formatoutput;
2200 
2201 	if (nodep != NULL) {
2202 		/* Dump contents of Node */
2203 		DOM_GET_OBJ(node, nodep, xmlNodePtr, nodeobj);
2204 		if (node->doc != docp) {
2205 			php_dom_throw_error(WRONG_DOCUMENT_ERR, dom_get_strict_error(intern->document));
2206 			RETURN_FALSE;
2207 		}
2208 
2209 		buf = xmlBufferCreate();
2210 		if (!buf) {
2211 			php_error_docref(NULL, E_WARNING, "Could not fetch buffer");
2212 			RETURN_FALSE;
2213 		}
2214 		outBuf = xmlOutputBufferCreateBuffer(buf, NULL);
2215 		if (!outBuf) {
2216 			xmlBufferFree(buf);
2217 			php_error_docref(NULL, E_WARNING, "Could not fetch output buffer");
2218 			RETURN_FALSE;
2219 		}
2220 
2221 		if (node->type == XML_DOCUMENT_FRAG_NODE) {
2222 			for (node = node->children; node; node = node->next) {
2223 				htmlNodeDumpFormatOutput(outBuf, docp, node, NULL, format);
2224 				if (outBuf->error) {
2225 					break;
2226 				}
2227 			}
2228 		} else {
2229 			htmlNodeDumpFormatOutput(outBuf, docp, node, NULL, format);
2230 		}
2231 		if (!outBuf->error) {
2232 			xmlOutputBufferFlush(outBuf);
2233 			const xmlChar *mem = xmlBufferContent(buf);
2234 			if (!mem) {
2235 				RETVAL_FALSE;
2236 			} else {
2237 				int size = xmlBufferLength(buf);
2238 				RETVAL_STRINGL((const char*) mem, size);
2239 			}
2240 		} else {
2241 			php_error_docref(NULL, E_WARNING, "Error dumping HTML node");
2242 			RETVAL_FALSE;
2243 		}
2244 		xmlOutputBufferClose(outBuf);
2245 		xmlBufferFree(buf);
2246 	} else {
2247 		xmlChar *mem = NULL;
2248 		int size = 0;
2249 		htmlDocDumpMemoryFormat(docp, &mem, &size, format);
2250 		if (!size || !mem) {
2251 			RETVAL_FALSE;
2252 		} else {
2253 			RETVAL_STRINGL((const char*) mem, size);
2254 		}
2255 		xmlFree(mem);
2256 	}
2257 
2258 }
2259 /* }}} end dom_document_save_html */
2260 
2261 #endif  /* defined(LIBXML_HTML_ENABLED) */
2262 
2263 /* {{{ Register extended class used to create base node type */
dom_document_register_node_class(INTERNAL_FUNCTION_PARAMETERS,bool modern)2264 static void dom_document_register_node_class(INTERNAL_FUNCTION_PARAMETERS, bool modern)
2265 {
2266 	zend_class_entry *basece = dom_get_node_ce(modern), *ce = NULL;
2267 	dom_object *intern;
2268 
2269 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "CC!", &basece, &ce) == FAILURE) {
2270 		RETURN_THROWS();
2271 	}
2272 
2273 	if (basece->ce_flags & ZEND_ACC_ABSTRACT) {
2274 		zend_argument_value_error(1, "must not be an abstract class");
2275 		RETURN_THROWS();
2276 	}
2277 
2278 	if (ce == NULL || instanceof_function(ce, basece)) {
2279 		if (UNEXPECTED(ce != NULL && (ce->ce_flags & ZEND_ACC_ABSTRACT))) {
2280 			zend_argument_value_error(2, "must not be an abstract class");
2281 			RETURN_THROWS();
2282 		}
2283 		DOM_GET_THIS_INTERN(intern);
2284 		dom_set_doc_classmap(intern->document, basece, ce);
2285 		if (!modern) {
2286 			RETVAL_TRUE;
2287 		}
2288 		return;
2289 	}
2290 
2291 	zend_argument_error(NULL, 2, "must be a class name derived from %s or null, %s given", ZSTR_VAL(basece->name), ZSTR_VAL(ce->name));
2292 	RETURN_THROWS();
2293 }
2294 
PHP_METHOD(DOMDocument,registerNodeClass)2295 PHP_METHOD(DOMDocument, registerNodeClass)
2296 {
2297 	dom_document_register_node_class(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
2298 }
2299 
PHP_METHOD(Dom_Document,registerNodeClass)2300 PHP_METHOD(Dom_Document, registerNodeClass)
2301 {
2302 	dom_document_register_node_class(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
2303 }
2304 /* }}} */
2305 
2306 /* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
2307 Since:
2308 */
PHP_METHOD(DOMDocument,replaceChildren)2309 PHP_METHOD(DOMDocument, replaceChildren)
2310 {
2311 	uint32_t argc = 0;
2312 	zval *args;
2313 	dom_object *intern;
2314 
2315 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
2316 		RETURN_THROWS();
2317 	}
2318 
2319 	DOM_GET_THIS_INTERN(intern);
2320 
2321 	dom_parent_node_replace_children(intern, args, argc);
2322 }
2323 /* }}} */
2324 
2325 #endif  /* HAVE_LIBXML && HAVE_DOM */
2326