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