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