xref: /PHP-8.4/ext/dom/domimplementation.c (revision 5853cdb7)
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 
28 /*
29 * class DOMImplementation
30 *
31 * URL: https://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-102161490
32 * Since:
33 */
34 
35 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-5CED94D7
36 Since:
37 */
PHP_METHOD(DOMImplementation,hasFeature)38 PHP_METHOD(DOMImplementation, hasFeature)
39 {
40 	zend_string *feature, *version;
41 
42 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &feature, &version) == FAILURE) {
43 		RETURN_THROWS();
44 	}
45 
46 	RETURN_BOOL(dom_has_feature(feature, version));
47 }
48 /* }}} end dom_domimplementation_has_feature */
49 
50 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocType
51 Modern URL: https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype
52 Since: DOM Level 2
53 */
PHP_METHOD(DOMImplementation,createDocumentType)54 PHP_METHOD(DOMImplementation, createDocumentType)
55 {
56 	xmlDtd *doctype;
57 	size_t name_len = 0, publicid_len = 0, systemid_len = 0;
58 	char *name = NULL, *publicid = NULL, *systemid = NULL;
59 	xmlChar *pch1 = NULL, *pch2 = NULL, *localname = NULL;
60 	xmlURIPtr uri;
61 
62 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ss", &name, &name_len, &publicid, &publicid_len, &systemid, &systemid_len) == FAILURE) {
63 		RETURN_THROWS();
64 	}
65 
66 	if (name_len == 0) {
67 		zend_argument_must_not_be_empty_error(1);
68 		RETURN_THROWS();
69 	}
70 
71 	if (publicid_len > 0) {
72 		pch1 = BAD_CAST publicid;
73 	}
74 	if (systemid_len > 0) {
75 		pch2 = BAD_CAST systemid;
76 	}
77 
78 	if (strstr(name, "%00")) {
79 		php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes");
80 		RETURN_FALSE;
81 	}
82 
83 	uri = xmlParseURI(name);
84 	if (uri != NULL && uri->opaque != NULL) {
85 		localname = xmlStrdup(BAD_CAST uri->opaque);
86 		if (xmlStrchr(localname, (xmlChar) ':') != NULL) {
87 			php_dom_throw_error(NAMESPACE_ERR, true);
88 			xmlFreeURI(uri);
89 			xmlFree(localname);
90 			RETURN_FALSE;
91 		}
92 	} else {
93 		localname = xmlStrdup(BAD_CAST name);
94 	}
95 
96 	if (uri) {
97 		xmlFreeURI(uri);
98 	}
99 
100 	doctype = xmlCreateIntSubset(NULL, localname, pch1, pch2);
101 	xmlFree(localname);
102 
103 	if (doctype == NULL) {
104 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
105 		RETURN_THROWS();
106 	}
107 
108 	DOM_RET_OBJ((xmlNodePtr) doctype, NULL);
109 }
110 
PHP_METHOD(Dom_Implementation,createDocumentType)111 PHP_METHOD(Dom_Implementation, createDocumentType)
112 {
113 	size_t name_len, publicid_len = 0, systemid_len = 0;
114 	const char *name, *publicid = NULL, *systemid = NULL;
115 
116 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppp", &name, &name_len, &publicid, &publicid_len, &systemid, &systemid_len) != SUCCESS) {
117 		RETURN_THROWS();
118 	}
119 
120 	/* 1. Validate qualifiedName. */
121 	if (xmlValidateQName(BAD_CAST name, 0) != 0) {
122 		php_dom_throw_error(NAMESPACE_ERR, true);
123 		RETURN_THROWS();
124 	}
125 
126 	/* 2. Return a new doctype, with qualifiedName as its name, publicId as its public ID, and systemId as its system ID ... */
127 	xmlDtdPtr doctype = xmlCreateIntSubset(
128 		NULL,
129 		BAD_CAST name,
130 		publicid_len ? BAD_CAST publicid : NULL,
131 		systemid_len ? BAD_CAST systemid : NULL
132 	);
133 	if (UNEXPECTED(doctype == NULL)) {
134 		php_dom_throw_error(INVALID_STATE_ERR, true);
135 		RETURN_THROWS();
136 	}
137 
138 	php_dom_instantiate_object_helper(
139 		return_value,
140 		dom_modern_documenttype_class_entry,
141 		(xmlNodePtr) doctype,
142 		NULL
143 	);
144 }
145 /* }}} end dom_domimplementation_create_document_type */
146 
147 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocument
148 Since: DOM Level 2
149 */
PHP_METHOD(DOMImplementation,createDocument)150 PHP_METHOD(DOMImplementation, createDocument)
151 {
152 	zval *node = NULL;
153 	xmlDoc *docp;
154 	xmlNode *nodep;
155 	xmlDtdPtr doctype = NULL;
156 	xmlNsPtr nsptr = NULL;
157 	int errorcode = 0;
158 	size_t uri_len = 0, name_len = 0;
159 	char *uri = NULL, *name = NULL;
160 	char *prefix = NULL, *localname = NULL;
161 	dom_object *doctobj;
162 
163 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!sO!", &uri, &uri_len, &name, &name_len, &node, dom_documenttype_class_entry) == FAILURE) {
164 		RETURN_THROWS();
165 	}
166 
167 	if (node != NULL) {
168 		DOM_GET_OBJ(doctype, node, xmlDtdPtr, doctobj);
169 		if (doctype->type == XML_DOCUMENT_TYPE_NODE) {
170 			zend_argument_value_error(3, "is an invalid DocumentType object");
171 			RETURN_THROWS();
172 		}
173 		if (doctype->doc != NULL) {
174 			/* As the new document is the context node, and the default for strict error checking
175 			 * is true, this will always throw. */
176 			php_dom_throw_error(WRONG_DOCUMENT_ERR, true);
177 			RETURN_THROWS();
178 		}
179 	} else {
180 		doctobj = NULL;
181 	}
182 
183 	if (name_len > 0) {
184 		errorcode = dom_check_qname(name, &localname, &prefix, 1, name_len);
185 		if (errorcode == 0 && uri_len > 0
186 			&& ((nsptr = xmlNewNs(NULL, BAD_CAST uri, BAD_CAST prefix)) == NULL)
187 		) {
188 			errorcode = NAMESPACE_ERR;
189 		}
190 	}
191 
192 	if (prefix != NULL) {
193 		xmlFree(prefix);
194 	}
195 
196 	if (errorcode != 0) {
197 		if (localname != NULL) {
198 			xmlFree(localname);
199 		}
200 		php_dom_throw_error(errorcode, true);
201 		RETURN_THROWS();
202 	}
203 
204 	/* currently letting libxml2 set the version string */
205 	docp = xmlNewDoc(NULL);
206 	if (!docp) {
207 		if (localname != NULL) {
208 			xmlFree(localname);
209 		}
210 		/* See above for strict error checking argument. */
211 		php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
212 		RETURN_THROWS();
213 	}
214 
215 	if (doctype != NULL) {
216 		docp->intSubset = doctype;
217 		doctype->parent = docp;
218 		doctype->doc = docp;
219 		docp->children = (xmlNodePtr) doctype;
220 		docp->last = (xmlNodePtr) doctype;
221 	}
222 
223 	if (localname != NULL) {
224 		nodep = xmlNewDocNode(docp, nsptr, BAD_CAST localname, NULL);
225 		if (!nodep) {
226 			if (doctype != NULL) {
227 				docp->intSubset = NULL;
228 				doctype->parent = NULL;
229 				doctype->doc = NULL;
230 				docp->children = NULL;
231 				docp->last = NULL;
232 			}
233 			xmlFreeDoc(docp);
234 			xmlFree(localname);
235 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
236 			RETURN_THROWS();
237 		}
238 
239 		nodep->nsDef = nsptr;
240 
241 		xmlDocSetRootElement(docp, nodep);
242 		xmlFree(localname);
243 	}
244 
245 	DOM_RET_OBJ((xmlNodePtr) docp, NULL);
246 
247 	if (doctobj != NULL) {
248 		doctobj->document = ((dom_object *)((php_libxml_node_ptr *)docp->_private)->_private)->document;
249 		php_libxml_increment_doc_ref((php_libxml_node_object *)doctobj, docp);
250 	}
251 }
252 
PHP_METHOD(Dom_Implementation,createDocument)253 PHP_METHOD(Dom_Implementation, createDocument)
254 {
255 	zval *dtd = NULL;
256 	xmlDtdPtr doctype = NULL;
257 	zend_string *uri = NULL, *qualified_name = zend_empty_string;
258 	dom_object *doctobj;
259 
260 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "P!P|O!", &uri, &qualified_name, &dtd, dom_modern_documenttype_class_entry) != SUCCESS) {
261 		RETURN_THROWS();
262 	}
263 
264 	if (dtd != NULL) {
265 		DOM_GET_OBJ(doctype, dtd, xmlDtdPtr, doctobj);
266 	}
267 
268 	xmlDocPtr document = NULL;
269 	xmlChar *localname = NULL, *prefix = NULL;
270 	php_dom_private_data *private_data = php_dom_private_data_create();
271 	php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data);
272 
273 	/* 1. Let document be a new XMLDocument. */
274 	document = xmlNewDoc(BAD_CAST "1.0");
275 	if (UNEXPECTED(document == NULL)) {
276 		goto oom;
277 	}
278 	document->encoding = xmlStrdup(BAD_CAST "UTF-8");
279 
280 	/* 2. Let element be null. */
281 	xmlNodePtr element = NULL;
282 
283 	/* 3. If qualifiedName is not the empty string, then set element to the result of running the internal createElementNS steps. */
284 	if (ZSTR_LEN(qualified_name) != 0) {
285 		int errorcode = dom_validate_and_extract(uri, qualified_name, &localname, &prefix);
286 
287 		if (EXPECTED(errorcode == 0)) {
288 			xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
289 			element = xmlNewDocNode(document, ns, localname, NULL);
290 			if (UNEXPECTED(element == NULL)) {
291 				goto oom;
292 			}
293 			xmlFree(localname);
294 			xmlFree(prefix);
295 			localname = NULL;
296 			prefix = NULL;
297 		} else {
298 			php_dom_throw_error(errorcode, /* strict */ true);
299 			goto error;
300 		}
301 	}
302 
303 	/* 8. Return document.
304 	 *    => This is done here already to gain access to the dom_object */
305 	dom_object *intern = php_dom_instantiate_object_helper(
306 		return_value,
307 		dom_xml_document_class_entry,
308 		(xmlNodePtr) document,
309 		NULL
310 	);
311 	dom_set_xml_class(intern->document);
312 	intern->document->private_data = php_dom_libxml_private_data_header(private_data);
313 
314 	/* 4. If doctype is non-null, append doctype to document. */
315 	if (doctype != NULL) {
316 		php_dom_adopt_node((xmlNodePtr) doctype, intern, document);
317 		xmlAddChild((xmlNodePtr) document, (xmlNodePtr) doctype);
318 		doctype->doc = document;
319 		document->intSubset = (xmlDtdPtr) doctype;
320 		ZEND_ASSERT(doctype->parent == document);
321 	}
322 
323 	/* 5. If element is non-null, append element to document. */
324 	if (element != NULL) {
325 		xmlAddChild((xmlNodePtr) document, element);
326 	}
327 
328 	/* 6. document’s origin is this’s associated document’s origin.
329 	 *    => We don't store the origin in ext/dom. */
330 
331 	/* 7. document’s content type is determined by namespace:
332 	 *    => We don't store the content type in ext/dom. */
333 
334 	return;
335 
336 oom:
337 	php_dom_throw_error(INVALID_STATE_ERR, true);
338 error:
339 	xmlFree(localname);
340 	xmlFree(prefix);
341 	xmlFreeDoc(document);
342 	php_dom_private_data_destroy(private_data);
343 	RETURN_THROWS();
344 }
345 /* }}} end dom_domimplementation_create_document */
346 
347 /* {{{ URL: https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument */
PHP_METHOD(Dom_Implementation,createHTMLDocument)348 PHP_METHOD(Dom_Implementation, createHTMLDocument)
349 {
350 	const char *title = NULL;
351 	size_t title_len = 0;
352 
353 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p!", &title, &title_len) != SUCCESS) {
354 		RETURN_THROWS();
355 	}
356 
357 	/* 1. Let doc be a new document that is an HTML document. */
358 	xmlDocPtr doc = php_dom_create_html_doc();
359 	if (UNEXPECTED(doc == NULL)) {
360 		php_dom_throw_error(INVALID_STATE_ERR, true);
361 		RETURN_THROWS();
362 	}
363 	doc->encoding = xmlStrdup(BAD_CAST "UTF-8");
364 
365 	/* 2. Set doc’s content type to "text/html".
366 	 *    => We don't store the content type in ext/dom. */
367 
368 	/* 3. Append a new doctype, with "html" as its name and with its node document set to doc, to doc. */
369 	xmlDtdPtr dtd = xmlCreateIntSubset(doc, BAD_CAST "html", NULL, NULL);
370 
371 	php_dom_private_data *private_data = php_dom_private_data_create();
372 	php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data);
373 	xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(ns_mapper);
374 
375 	/* 4. Append the result of creating an element given doc, html, and the HTML namespace, to doc. */
376 	xmlNodePtr html_element = xmlNewDocRawNode(doc, html_ns, BAD_CAST "html", NULL);
377 	xmlAddChild((xmlNodePtr) doc, html_element);
378 
379 	/* 5. Append the result of creating an element given doc, head, and the HTML namespace, to the html element created earlier. */
380 	xmlNodePtr head_element = xmlNewDocRawNode(doc, html_ns, BAD_CAST "head", NULL);
381 	xmlAddChild(html_element, head_element);
382 
383 	/* 6. If title is given: */
384 	xmlNodePtr title_element = NULL;
385 	if (title != NULL) {
386 		/* 6.1. Append the result of creating an element given doc, title, and the HTML namespace, to the head element created earlier. */
387 		/* 6.2. Append the result of creating a text node given doc and title, to the title element created earlier. */
388 		title_element = xmlNewDocRawNode(doc, html_ns, BAD_CAST "title", BAD_CAST title);
389 		xmlAddChild(head_element, title_element);
390 	}
391 
392 	/* 7. Append the result of creating an element given doc, body, and the HTML namespace, to the html element created earlier. */
393 	xmlNodePtr body_element = xmlNewDocRawNode(doc, html_ns, BAD_CAST "body", NULL);
394 	xmlAddChild(html_element, body_element);
395 
396 	/* 8. doc’s origin is this’s associated document’s origin.
397 	 *    => We don't store the origin in ext/dom. */
398 
399 	if (UNEXPECTED(dtd == NULL || html_element == NULL || head_element == NULL || (title != NULL && title_element == NULL) || body_element == NULL)) {
400 		php_dom_throw_error(INVALID_STATE_ERR, true);
401 		xmlFreeDoc(doc);
402 		php_dom_private_data_destroy(private_data);
403 		RETURN_THROWS();
404 	}
405 
406 	/* 9. Return doc. */
407 	dom_object *intern = php_dom_instantiate_object_helper(
408 		return_value,
409 		dom_html_document_class_entry,
410 		(xmlNodePtr) doc,
411 		NULL
412 	);
413 	dom_set_xml_class(intern->document);
414 	intern->document->private_data = php_dom_libxml_private_data_header(private_data);
415 }
416 /* }}} */
417 
418 #endif
419