xref: /PHP-8.3/ext/libxml/libxml.c (revision 4fe82131)
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: Shane Caraveo <shane@php.net>                               |
14    |          Wez Furlong <wez@thebrainroom.com>                          |
15    +----------------------------------------------------------------------+
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 #include "SAPI.h"
24 
25 #include "zend_variables.h"
26 #include "ext/standard/php_string.h"
27 #include "ext/standard/info.h"
28 #include "ext/standard/file.h"
29 
30 #ifdef HAVE_LIBXML
31 
32 #include <libxml/parser.h>
33 #include <libxml/parserInternals.h>
34 #include <libxml/tree.h>
35 #include <libxml/uri.h>
36 #include <libxml/xmlerror.h>
37 #include <libxml/xmlsave.h>
38 #include <libxml/entities.h>
39 #ifdef LIBXML_SCHEMAS_ENABLED
40 #include <libxml/relaxng.h>
41 #include <libxml/xmlschemas.h>
42 #endif
43 
44 #include "php_libxml.h"
45 
46 #define PHP_LIBXML_LOADED_VERSION ((char *)xmlParserVersion)
47 #define PHP_LIBXML_ERROR 0
48 #define PHP_LIBXML_CTX_ERROR 1
49 #define PHP_LIBXML_CTX_WARNING 2
50 
51 #include "libxml_arginfo.h"
52 
53 /* a true global for initialization */
54 static int _php_libxml_initialized = 0;
55 static int _php_libxml_per_request_initialization = 1;
56 static xmlExternalEntityLoader _php_libxml_default_entity_loader;
57 
58 typedef struct _php_libxml_func_handler {
59 	php_libxml_export_node export_func;
60 } php_libxml_func_handler;
61 
62 static HashTable php_libxml_exports;
63 
64 static ZEND_DECLARE_MODULE_GLOBALS(libxml)
65 static PHP_GINIT_FUNCTION(libxml);
66 
67 static zend_class_entry *libxmlerror_class_entry;
68 
69 /* {{{ dynamically loadable module stuff */
70 #ifdef COMPILE_DL_LIBXML
71 #ifdef ZTS
72 ZEND_TSRMLS_CACHE_DEFINE()
73 #endif
74 ZEND_GET_MODULE(libxml)
75 #endif /* COMPILE_DL_LIBXML */
76 /* }}} */
77 
78 /* {{{ function prototypes */
79 static PHP_MINIT_FUNCTION(libxml);
80 static PHP_RINIT_FUNCTION(libxml);
81 static PHP_RSHUTDOWN_FUNCTION(libxml);
82 static PHP_MSHUTDOWN_FUNCTION(libxml);
83 static PHP_MINFO_FUNCTION(libxml);
84 static zend_result php_libxml_post_deactivate(void);
85 
86 /* }}} */
87 
88 zend_module_entry libxml_module_entry = {
89 	STANDARD_MODULE_HEADER,
90 	"libxml",                /* extension name */
91 	ext_functions,           /* extension function list */
92 	PHP_MINIT(libxml),       /* extension-wide startup function */
93 	PHP_MSHUTDOWN(libxml),   /* extension-wide shutdown function */
94 	PHP_RINIT(libxml),       /* per-request startup function */
95 	PHP_RSHUTDOWN(libxml),   /* per-request shutdown function */
96 	PHP_MINFO(libxml),       /* information function */
97 	PHP_LIBXML_VERSION,
98 	PHP_MODULE_GLOBALS(libxml), /* globals descriptor */
99 	PHP_GINIT(libxml),          /* globals ctor */
100 	NULL,                       /* globals dtor */
101 	php_libxml_post_deactivate, /* post deactivate */
102 	STANDARD_MODULE_PROPERTIES_EX
103 };
104 
105 /* }}} */
106 
php_libxml_set_old_ns_list(xmlDocPtr doc,xmlNsPtr first,xmlNsPtr last)107 static void php_libxml_set_old_ns_list(xmlDocPtr doc, xmlNsPtr first, xmlNsPtr last)
108 {
109 	if (UNEXPECTED(doc == NULL)) {
110 		return;
111 	}
112 
113 	ZEND_ASSERT(last->next == NULL);
114 
115 	/* Note: we'll use a prepend strategy instead of append to
116 	 * make sure we don't lose performance when the list is long.
117 	 * As libxml2 could assume the xml node is the first one, we'll place our
118 	 * new entries after the first one. */
119 
120 	if (UNEXPECTED(doc->oldNs == NULL)) {
121 		doc->oldNs = (xmlNsPtr) xmlMalloc(sizeof(xmlNs));
122 		if (doc->oldNs == NULL) {
123 			return;
124 		}
125 		memset(doc->oldNs, 0, sizeof(xmlNs));
126 		doc->oldNs->type = XML_LOCAL_NAMESPACE;
127 		doc->oldNs->href = xmlStrdup(XML_XML_NAMESPACE);
128 		doc->oldNs->prefix = xmlStrdup((const xmlChar *)"xml");
129 	} else {
130 		last->next = doc->oldNs->next;
131 	}
132 	doc->oldNs->next = first;
133 }
134 
php_libxml_set_old_ns(xmlDocPtr doc,xmlNsPtr ns)135 PHP_LIBXML_API void php_libxml_set_old_ns(xmlDocPtr doc, xmlNsPtr ns)
136 {
137 	php_libxml_set_old_ns_list(doc, ns, ns);
138 }
139 
140 /* Function pointer typedef changed in 2.9.8, see https://github.com/GNOME/libxml2/commit/e03f0a199a67017b2f8052354cf732b2b4cae787 */
141 #if LIBXML_VERSION >= 20908
php_libxml_unlink_entity(void * data,void * table,const xmlChar * name)142 static void php_libxml_unlink_entity(void *data, void *table, const xmlChar *name)
143 #else
144 static void php_libxml_unlink_entity(void *data, void *table, xmlChar *name)
145 #endif
146 {
147 	xmlEntityPtr entity = data;
148 	if (entity->_private != NULL) {
149 		xmlHashRemoveEntry(table, name, NULL);
150 	}
151 }
152 
153 /* {{{ internal functions for interoperability */
php_libxml_clear_object(php_libxml_node_object * object)154 static int php_libxml_clear_object(php_libxml_node_object *object)
155 {
156 	if (object->properties) {
157 		object->properties = NULL;
158 	}
159 	php_libxml_decrement_node_ptr(object);
160 	return php_libxml_decrement_doc_ref(object);
161 }
162 
php_libxml_unregister_node(xmlNodePtr nodep)163 static void php_libxml_unregister_node(xmlNodePtr nodep)
164 {
165 	php_libxml_node_object *wrapper;
166 
167 	php_libxml_node_ptr *nodeptr = nodep->_private;
168 
169 	if (nodeptr != NULL) {
170 		wrapper = nodeptr->_private;
171 		if (wrapper) {
172 			php_libxml_clear_object(wrapper);
173 		} else {
174 			if (nodeptr->node != NULL && nodeptr->node->type != XML_DOCUMENT_NODE) {
175 				nodeptr->node->_private = NULL;
176 			}
177 			nodeptr->node = NULL;
178 		}
179 	}
180 }
181 
182 /* Workaround for libxml2 peculiarity */
php_libxml_unlink_entity_decl(xmlEntityPtr entity)183 static void php_libxml_unlink_entity_decl(xmlEntityPtr entity)
184 {
185 	xmlDtdPtr dtd = entity->parent;
186 	if (dtd != NULL) {
187 		if (xmlHashLookup(dtd->entities, entity->name) == entity) {
188 			xmlHashRemoveEntry(dtd->entities, entity->name, NULL);
189 		}
190 		if (xmlHashLookup(dtd->pentities, entity->name) == entity) {
191 			xmlHashRemoveEntry(dtd->pentities, entity->name, NULL);
192 		}
193 	}
194 }
195 
php_libxml_node_free(xmlNodePtr node)196 static void php_libxml_node_free(xmlNodePtr node)
197 {
198 	if(node) {
199 		if (node->_private != NULL) {
200 			((php_libxml_node_ptr *) node->_private)->node = NULL;
201 		}
202 		switch (node->type) {
203 			case XML_ATTRIBUTE_NODE:
204 				xmlFreeProp((xmlAttrPtr) node);
205 				break;
206 			/* libxml2 has a peculiarity where if you unlink an entity it'll only unlink it from the dtd if the
207 			 * dtd is attached to the document. This works around the issue by inspecting the parent directly. */
208 			case XML_ENTITY_DECL: {
209 				xmlEntityPtr entity = (xmlEntityPtr) node;
210 				if (entity->etype != XML_INTERNAL_PREDEFINED_ENTITY) {
211 					php_libxml_unlink_entity_decl(entity);
212 #if LIBXML_VERSION >= 21200
213 					xmlFreeEntity(entity);
214 #else
215 					if (entity->children != NULL && entity->owner && entity == (xmlEntityPtr) entity->children->parent) {
216 						xmlFreeNodeList(entity->children);
217 					}
218 					xmlDictPtr dict = entity->doc != NULL ? entity->doc->dict : NULL;
219 					if (dict == NULL || !xmlDictOwns(dict, entity->name)) {
220 						xmlFree((xmlChar *) entity->name);
221 					}
222 					if (dict == NULL || !xmlDictOwns(dict, entity->ExternalID)) {
223 						xmlFree((xmlChar *) entity->ExternalID);
224 					}
225 					if (dict == NULL || !xmlDictOwns(dict, entity->SystemID)) {
226 						xmlFree((xmlChar *) entity->SystemID);
227 					}
228 					if (dict == NULL || !xmlDictOwns(dict, entity->URI)) {
229 						xmlFree((xmlChar *) entity->URI);
230 					}
231 					if (dict == NULL || !xmlDictOwns(dict, entity->content)) {
232 						xmlFree(entity->content);
233 					}
234 					if (dict == NULL || !xmlDictOwns(dict, entity->orig)) {
235 						xmlFree(entity->orig);
236 					}
237 					xmlFree(entity);
238 #endif
239 				}
240 				break;
241 			}
242 			case XML_NOTATION_NODE: {
243 				/* See create_notation(), these aren't regular XML_NOTATION_NODE, but entities in disguise... */
244 				xmlEntityPtr entity = (xmlEntityPtr) node;
245 				if (node->name != NULL) {
246 					xmlFree((char *) node->name);
247 				}
248 				if (entity->ExternalID != NULL) {
249 					xmlFree((char *) entity->ExternalID);
250 				}
251 				if (entity->SystemID != NULL) {
252 					xmlFree((char *) entity->SystemID);
253 				}
254 				xmlFree(node);
255 				break;
256 			}
257 			case XML_ELEMENT_DECL:
258 			case XML_ATTRIBUTE_DECL:
259 				break;
260 			case XML_NAMESPACE_DECL:
261 				if (node->ns) {
262 					xmlFreeNs(node->ns);
263 					node->ns = NULL;
264 				}
265 				node->type = XML_ELEMENT_NODE;
266 				xmlFreeNode(node);
267 				break;
268 			case XML_DTD_NODE: {
269 				xmlDtdPtr dtd = (xmlDtdPtr) node;
270 				if (dtd->_private == NULL) {
271 					/* There's no userland reference to the dtd,
272 					 * but there might be entities referenced from userland. Unlink those. */
273 					xmlHashScan(dtd->entities, php_libxml_unlink_entity, dtd->entities);
274 					xmlHashScan(dtd->pentities, php_libxml_unlink_entity, dtd->pentities);
275 					/* No unlinking of notations, see remark above at case XML_NOTATION_NODE. */
276 				}
277 				xmlFreeNode(node);
278 				break;
279 			}
280 			case XML_ELEMENT_NODE:
281 				if (node->nsDef && node->doc) {
282 					/* Make the namespace declaration survive the destruction of the holding element.
283 					 * This prevents a use-after-free on the namespace declaration.
284 					 *
285 					 * The main problem is that libxml2 doesn't have a reference count on the namespace declaration.
286 					 * We don't actually need to save the namespace declaration if we know the subtree it belongs to
287 					 * has no references from userland. However, we can't know that without traversing the whole subtree
288 					 * (=> slow), or without adding some subtree metadata (=> also slow).
289 					 * So we have to assume we need to save everything.
290 					 *
291 					 * However, namespace declarations are quite rare in comparison to other node types.
292 					 * Most node types are either elements, text or attributes.
293 					 * And you only need one namespace declaration per namespace (in principle).
294 					 * So I expect the number of namespace declarations to be low for an average XML document.
295 					 *
296 					 * In the worst possible case we have to save all namespace declarations when we for example remove
297 					 * the whole document. But given the above reasoning this likely won't be a lot of declarations even
298 					 * in the worst case.
299 					 * A single declaration only takes about 48 bytes of memory, and I don't expect the worst case to occur
300 					 * very often (why would you remove the whole document?).
301 					 */
302 					xmlNsPtr ns = node->nsDef;
303 					xmlNsPtr last = ns;
304 					while (last->next) {
305 						last = last->next;
306 					}
307 					php_libxml_set_old_ns_list(node->doc, ns, last);
308 					node->nsDef = NULL;
309 				}
310 				xmlFreeNode(node);
311 				break;
312 			default:
313 				xmlFreeNode(node);
314 				break;
315 		}
316 	}
317 }
318 
php_libxml_node_free_list(xmlNodePtr node)319 PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node)
320 {
321 	xmlNodePtr curnode;
322 
323 	if (node != NULL) {
324 		curnode = node;
325 		while (curnode != NULL) {
326 			/* If the _private field is set, there's still a userland reference somewhere. We'll delay freeing in this case. */
327 			if (curnode->_private) {
328 				xmlNodePtr next = curnode->next;
329 				/* Must unlink such that freeing of the parent doesn't free this child. */
330 				xmlUnlinkNode(curnode);
331 				if (curnode->type == XML_ELEMENT_NODE) {
332 					/* This ensures that namespace references in this subtree are defined within this subtree,
333 					 * otherwise a use-after-free would be possible when the original namespace holder gets freed. */
334 #if 0
335 					xmlDOMWrapCtxt dummy_ctxt = {0};
336 					xmlDOMWrapReconcileNamespaces(&dummy_ctxt, curnode, /* options */ 0);
337 #else
338 					/* See php_dom.c */
339 					xmlReconciliateNs(curnode->doc, curnode);
340 #endif
341 				}
342 				/* Skip freeing */
343 				curnode = next;
344 				continue;
345 			}
346 
347 			node = curnode;
348 			switch (node->type) {
349 				/* Skip property freeing for the following types */
350 				case XML_NOTATION_NODE:
351 					break;
352 				case XML_ENTITY_DECL:
353 					php_libxml_unlink_entity_decl((xmlEntityPtr) node);
354 					break;
355 				case XML_ENTITY_REF_NODE:
356 					php_libxml_node_free_list((xmlNodePtr) node->properties);
357 					break;
358 				case XML_ATTRIBUTE_NODE:
359 					if ((node->doc != NULL) && (((xmlAttrPtr) node)->atype == XML_ATTRIBUTE_ID)) {
360 						xmlRemoveID(node->doc, (xmlAttrPtr) node);
361 					}
362 					ZEND_FALLTHROUGH;
363 				case XML_ATTRIBUTE_DECL:
364 				case XML_DTD_NODE:
365 				case XML_DOCUMENT_TYPE_NODE:
366 				case XML_NAMESPACE_DECL:
367 				case XML_TEXT_NODE:
368 					php_libxml_node_free_list(node->children);
369 					break;
370 				default:
371 					php_libxml_node_free_list(node->children);
372 					php_libxml_node_free_list((xmlNodePtr) node->properties);
373 			}
374 
375 			curnode = node->next;
376 			xmlUnlinkNode(node);
377 			php_libxml_unregister_node(node);
378 			php_libxml_node_free(node);
379 		}
380 	}
381 }
382 
383 /* }}} */
384 
385 /* {{{ startup, shutdown and info functions */
PHP_GINIT_FUNCTION(libxml)386 static PHP_GINIT_FUNCTION(libxml)
387 {
388 #if defined(COMPILE_DL_LIBXML) && defined(ZTS)
389 	ZEND_TSRMLS_CACHE_UPDATE();
390 #endif
391 	ZVAL_UNDEF(&libxml_globals->stream_context);
392 	libxml_globals->error_buffer.s = NULL;
393 	libxml_globals->error_list = NULL;
394 	libxml_globals->entity_loader_callback = empty_fcall_info_cache;
395 }
396 
397 /* Channel libxml file io layer through the PHP streams subsystem.
398  * This allows use of ftps:// and https:// urls */
399 
php_libxml_streams_IO_open_wrapper(const char * filename,const char * mode,const int read_only)400 static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char *mode, const int read_only)
401 {
402 	php_stream_statbuf ssbuf;
403 	php_stream_context *context = NULL;
404 	php_stream_wrapper *wrapper = NULL;
405 	char *resolved_path;
406 	const char *path_to_open = NULL;
407 	void *ret_val = NULL;
408 	int isescaped=0;
409 	xmlURI *uri;
410 
411 	if (strstr(filename, "%00")) {
412 		php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes");
413 		return NULL;
414 	}
415 
416 	uri = xmlParseURI(filename);
417 	if (uri && (uri->scheme == NULL ||
418 			(xmlStrncmp(BAD_CAST uri->scheme, BAD_CAST "file", 4) == 0))) {
419 		resolved_path = xmlURIUnescapeString(filename, 0, NULL);
420 		isescaped = 1;
421 #if LIBXML_VERSION >= 20902 && defined(PHP_WIN32)
422 		/* Libxml 2.9.2 prefixes local paths with file:/ instead of file://,
423 			thus the php stream wrapper will fail on a valid case. For this
424 			reason the prefix is rather better cut off. */
425 		{
426 			size_t pre_len = sizeof("file:/") - 1;
427 
428 			if (strncasecmp(resolved_path, "file:/", pre_len) == 0
429 				&& '/' != resolved_path[pre_len]) {
430 				xmlChar *tmp = xmlStrdup(resolved_path + pre_len);
431 				xmlFree(resolved_path);
432 				resolved_path = tmp;
433 			}
434 		}
435 #endif
436 	} else {
437 		resolved_path = (char *)filename;
438 	}
439 
440 	if (uri) {
441 		xmlFreeURI(uri);
442 	}
443 
444 	if (resolved_path == NULL) {
445 		return NULL;
446 	}
447 
448 	/* logic copied from _php_stream_stat, but we only want to fail
449 	   if the wrapper supports stat, otherwise, figure it out from
450 	   the open.  This logic is only to support hiding warnings
451 	   that the streams layer puts out at times, but for libxml we
452 	   may try to open files that don't exist, but it is not a failure
453 	   in xml processing (eg. DTD files)  */
454 	wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, 0);
455 	if (wrapper && read_only && wrapper->wops->url_stat) {
456 		if (wrapper->wops->url_stat(wrapper, path_to_open, PHP_STREAM_URL_STAT_QUIET, &ssbuf, NULL) == -1) {
457 			if (isescaped) {
458 				xmlFree(resolved_path);
459 			}
460 			return NULL;
461 		}
462 	}
463 
464 	context = php_stream_context_from_zval(Z_ISUNDEF(LIBXML(stream_context))? NULL : &LIBXML(stream_context), 0);
465 
466 	ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, REPORT_ERRORS, NULL, context);
467 	if (ret_val) {
468 		/* Prevent from closing this by fclose() */
469 		((php_stream*)ret_val)->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
470 	}
471 	if (isescaped) {
472 		xmlFree(resolved_path);
473 	}
474 	return ret_val;
475 }
476 
php_libxml_streams_IO_open_read_wrapper(const char * filename)477 static void *php_libxml_streams_IO_open_read_wrapper(const char *filename)
478 {
479 	return php_libxml_streams_IO_open_wrapper(filename, "rb", 1);
480 }
481 
php_libxml_streams_IO_open_write_wrapper(const char * filename)482 static void *php_libxml_streams_IO_open_write_wrapper(const char *filename)
483 {
484 	return php_libxml_streams_IO_open_wrapper(filename, "wb", 0);
485 }
486 
php_libxml_streams_IO_read(void * context,char * buffer,int len)487 static int php_libxml_streams_IO_read(void *context, char *buffer, int len)
488 {
489 	return php_stream_read((php_stream*)context, buffer, len);
490 }
491 
php_libxml_streams_IO_write(void * context,const char * buffer,int len)492 static int php_libxml_streams_IO_write(void *context, const char *buffer, int len)
493 {
494 	return php_stream_write((php_stream*)context, buffer, len);
495 }
496 
php_libxml_streams_IO_close(void * context)497 static int php_libxml_streams_IO_close(void *context)
498 {
499 	return php_stream_close((php_stream*)context);
500 }
501 
502 static xmlParserInputBufferPtr
php_libxml_input_buffer_create_filename(const char * URI,xmlCharEncoding enc)503 php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc)
504 {
505 	xmlParserInputBufferPtr ret;
506 	void *context = NULL;
507 
508 	if (LIBXML(entity_loader_disabled)) {
509 		return NULL;
510 	}
511 
512 	if (URI == NULL)
513 		return(NULL);
514 
515 	context = php_libxml_streams_IO_open_read_wrapper(URI);
516 
517 	if (context == NULL) {
518 		return(NULL);
519 	}
520 
521 	/* Check if there's been an external transport protocol with an encoding information */
522 	if (enc == XML_CHAR_ENCODING_NONE) {
523 		php_stream *s  = (php_stream *) context;
524 
525 		if (Z_TYPE(s->wrapperdata) == IS_ARRAY) {
526 			zval *header;
527 
528 			ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) {
529 				const char buf[] = "Content-Type:";
530 				if (Z_TYPE_P(header) == IS_STRING &&
531 						!zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) {
532 					char needle[] = "charset=";
533 					char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header));
534 					char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), strlen(needle));
535 
536 					if (encoding) {
537 						char *end;
538 
539 						encoding += sizeof("charset=")-1;
540 						if (*encoding == '"') {
541 							encoding++;
542 						}
543 						end = strchr(encoding, ';');
544 						if (end == NULL) {
545 							end = encoding + strlen(encoding);
546 						}
547 						end--; /* end == encoding-1 isn't a buffer underrun */
548 						while (*end == ' ' || *end == '\t') {
549 							end--;
550 						}
551 						if (*end == '"') {
552 							end--;
553 						}
554 						if (encoding >= end) continue;
555 						*(end+1) = '\0';
556 						enc = xmlParseCharEncoding(encoding);
557 						if (enc <= XML_CHAR_ENCODING_NONE) {
558 							enc = XML_CHAR_ENCODING_NONE;
559 						}
560 					}
561 					efree(haystack);
562 					break; /* found content-type */
563 				}
564 			} ZEND_HASH_FOREACH_END();
565 		}
566 	}
567 
568 	/* Allocate the Input buffer front-end. */
569 	ret = xmlAllocParserInputBuffer(enc);
570 	if (ret != NULL) {
571 		ret->context = context;
572 		ret->readcallback = php_libxml_streams_IO_read;
573 		ret->closecallback = php_libxml_streams_IO_close;
574 	} else
575 		php_libxml_streams_IO_close(context);
576 
577 	return(ret);
578 }
579 
580 static xmlOutputBufferPtr
php_libxml_output_buffer_create_filename(const char * URI,xmlCharEncodingHandlerPtr encoder,int compression)581 php_libxml_output_buffer_create_filename(const char *URI,
582                               xmlCharEncodingHandlerPtr encoder,
583                               int compression)
584 {
585 	ZEND_IGNORE_VALUE(compression);
586 
587 	xmlOutputBufferPtr ret;
588 	xmlURIPtr puri;
589 	void *context = NULL;
590 	char *unescaped = NULL;
591 
592 	if (URI == NULL)
593 		return(NULL);
594 
595 	if (strstr(URI, "%00")) {
596 		php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes");
597 		return NULL;
598 	}
599 
600 	puri = xmlParseURI(URI);
601 	if (puri != NULL) {
602 		if (puri->scheme != NULL)
603 			unescaped = xmlURIUnescapeString(URI, 0, NULL);
604 		xmlFreeURI(puri);
605 	}
606 
607 	if (unescaped != NULL) {
608 		context = php_libxml_streams_IO_open_write_wrapper(unescaped);
609 		xmlFree(unescaped);
610 	}
611 
612 	/* try with a non-escaped URI this may be a strange filename */
613 	if (context == NULL) {
614 		context = php_libxml_streams_IO_open_write_wrapper(URI);
615 	}
616 
617 	if (context == NULL) {
618 		return(NULL);
619 	}
620 
621 	/* Allocate the Output buffer front-end. */
622 	ret = xmlAllocOutputBuffer(encoder);
623 	if (ret != NULL) {
624 		ret->context = context;
625 		ret->writecallback = php_libxml_streams_IO_write;
626 		ret->closecallback = php_libxml_streams_IO_close;
627 	}
628 
629 	return(ret);
630 }
631 
_php_libxml_free_error(void * ptr)632 static void _php_libxml_free_error(void *ptr)
633 {
634 	/* This will free the libxml alloc'd memory */
635 	xmlResetError((xmlErrorPtr) ptr);
636 }
637 
638 #if LIBXML_VERSION >= 21200
_php_list_set_error_structure(const xmlError * error,const char * msg)639 static void _php_list_set_error_structure(const xmlError *error, const char *msg)
640 #else
641 static void _php_list_set_error_structure(xmlError *error, const char *msg)
642 #endif
643 {
644 	xmlError error_copy;
645 	int ret;
646 
647 
648 	memset(&error_copy, 0, sizeof(xmlError));
649 
650 	if (error) {
651 		ret = xmlCopyError(error, &error_copy);
652 	} else {
653 		error_copy.code = XML_ERR_INTERNAL_ERROR;
654 		error_copy.level = XML_ERR_ERROR;
655 		error_copy.message = (char*)xmlStrdup((const xmlChar*)msg);
656 		ret = 0;
657 	}
658 
659 	if (ret == 0) {
660 		zend_llist_add_element(LIBXML(error_list), &error_copy);
661 	}
662 }
663 
php_libxml_ctx_error_level(int level,void * ctx,const char * msg)664 static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg)
665 {
666 	xmlParserCtxtPtr parser;
667 
668 	parser = (xmlParserCtxtPtr) ctx;
669 
670 	if (parser != NULL && parser->input != NULL) {
671 		if (parser->input->filename) {
672 			php_error_docref(NULL, level, "%s in %s, line: %d", msg, parser->input->filename, parser->input->line);
673 		} else {
674 			php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line);
675 		}
676 	} else {
677 		php_error_docref(NULL, E_WARNING, "%s", msg);
678 	}
679 }
680 
php_libxml_issue_error(int level,const char * msg)681 void php_libxml_issue_error(int level, const char *msg)
682 {
683 	if (LIBXML(error_list)) {
684 		_php_list_set_error_structure(NULL, msg);
685 	} else {
686 		php_error_docref(NULL, level, "%s", msg);
687 	}
688 }
689 
php_libxml_internal_error_handler(int error_type,void * ctx,const char ** msg,va_list ap)690 static void php_libxml_internal_error_handler(int error_type, void *ctx, const char **msg, va_list ap)
691 {
692 	char *buf;
693 	int len, len_iter, output = 0;
694 
695 	len = vspprintf(&buf, 0, *msg, ap);
696 	len_iter = len;
697 
698 	/* remove any trailing \n */
699 	while (len_iter && buf[--len_iter] == '\n') {
700 		buf[len_iter] = '\0';
701 		output = 1;
702 	}
703 
704 	smart_str_appendl(&LIBXML(error_buffer), buf, len);
705 
706 	efree(buf);
707 
708 	if (output == 1) {
709 		if (LIBXML(error_list)) {
710 			_php_list_set_error_structure(NULL, ZSTR_VAL(LIBXML(error_buffer).s));
711 		} else if (!EG(exception)) {
712 			/* Don't throw additional notices/warnings if an exception has already been thrown. */
713 			switch (error_type) {
714 				case PHP_LIBXML_CTX_ERROR:
715 					php_libxml_ctx_error_level(E_WARNING, ctx, ZSTR_VAL(LIBXML(error_buffer).s));
716 					break;
717 				case PHP_LIBXML_CTX_WARNING:
718 					php_libxml_ctx_error_level(E_NOTICE, ctx, ZSTR_VAL(LIBXML(error_buffer).s));
719 					break;
720 				default:
721 					php_error_docref(NULL, E_WARNING, "%s", ZSTR_VAL(LIBXML(error_buffer).s));
722 			}
723 		}
724 		smart_str_free(&LIBXML(error_buffer));
725 	}
726 }
727 
_php_libxml_external_entity_loader(const char * URL,const char * ID,xmlParserCtxtPtr context)728 static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL,
729 		const char *ID, xmlParserCtxtPtr context)
730 {
731 	xmlParserInputPtr	ret			= NULL;
732 	const char			*resource	= NULL;
733 	zval 				*ctxzv, retval;
734 	zval				params[3];
735 
736 	/* no custom user-land callback set up; delegate to original loader */
737 	if (!ZEND_FCC_INITIALIZED(LIBXML(entity_loader_callback))) {
738 		return _php_libxml_default_entity_loader(URL, ID, context);
739 	}
740 
741 	if (ID != NULL) {
742 		ZVAL_STRING(&params[0], ID);
743 	} else {
744 		ZVAL_NULL(&params[0]);
745 	}
746 	if (URL != NULL) {
747 		ZVAL_STRING(&params[1], URL);
748 	} else {
749 		ZVAL_NULL(&params[1]);
750 	}
751 	ctxzv = &params[2];
752 	array_init_size(ctxzv, 4);
753 
754 #define ADD_NULL_OR_STRING_KEY(memb) \
755 	if (context->memb == NULL) { \
756 		add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \
757 	} else { \
758 		add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \
759 				(char *)context->memb); \
760 	}
761 
762 	ADD_NULL_OR_STRING_KEY(directory)
763 	ADD_NULL_OR_STRING_KEY(intSubName)
764 	ADD_NULL_OR_STRING_KEY(extSubURI)
765 	ADD_NULL_OR_STRING_KEY(extSubSystem)
766 
767 #undef ADD_NULL_OR_STRING_KEY
768 
769 	zend_call_known_fcc(&LIBXML(entity_loader_callback), &retval, 3, params, /* named_params */ NULL);
770 
771 	if (Z_ISUNDEF(retval)) {
772 		php_libxml_ctx_error(context,
773 				"Call to user entity loader callback '%s' has failed",
774 				ZSTR_VAL(LIBXML(entity_loader_callback).function_handler->common.function_name));
775 	} else {
776 		if (Z_TYPE(retval) == IS_STRING) {
777 is_string:
778 			resource = Z_STRVAL(retval);
779 		} else if (Z_TYPE(retval) == IS_RESOURCE) {
780 			php_stream *stream;
781 			php_stream_from_zval_no_verify(stream, &retval);
782 			if (stream == NULL) {
783 				php_libxml_ctx_error(context,
784 						"The user entity loader callback '%s' has returned a "
785 						"resource, but it is not a stream",
786 						ZSTR_VAL(LIBXML(entity_loader_callback).function_handler->common.function_name));
787 			} else {
788 				/* TODO: allow storing the encoding in the stream context? */
789 				xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
790 				xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc);
791 				if (pib == NULL) {
792 					php_libxml_ctx_error(context, "Could not allocate parser "
793 							"input buffer");
794 				} else {
795 					/* make stream not being closed when the zval is freed */
796 					GC_ADDREF(stream->res);
797 					pib->context = stream;
798 					pib->readcallback = php_libxml_streams_IO_read;
799 					pib->closecallback = php_libxml_streams_IO_close;
800 
801 					ret = xmlNewIOInputStream(context, pib, enc);
802 					if (ret == NULL) {
803 						xmlFreeParserInputBuffer(pib);
804 					}
805 				}
806 			}
807 		} else if (Z_TYPE(retval) != IS_NULL) {
808 			/* retval not string nor resource nor null; convert to string */
809 			if (try_convert_to_string(&retval)) {
810 				goto is_string;
811 			}
812 		} /* else is null; don't try anything */
813 	}
814 
815 	if (ret == NULL) {
816 		if (resource == NULL) {
817 			if (ID == NULL) {
818 				php_libxml_ctx_error(context,
819 						"Failed to load external entity because the resolver function returned null\n");
820 			} else {
821 				php_libxml_ctx_error(context,
822 						"Failed to load external entity \"%s\"\n", ID);
823 			}
824 		} else {
825 			/* we got the resource in the form of a string; open it */
826 			ret = xmlNewInputFromFile(context, resource);
827 		}
828 	}
829 
830 	zval_ptr_dtor(&params[0]);
831 	zval_ptr_dtor(&params[1]);
832 	zval_ptr_dtor(&params[2]);
833 	zval_ptr_dtor(&retval);
834 	return ret;
835 }
836 
_php_libxml_pre_ext_ent_loader(const char * URL,const char * ID,xmlParserCtxtPtr context)837 static xmlParserInputPtr _php_libxml_pre_ext_ent_loader(const char *URL,
838 		const char *ID, xmlParserCtxtPtr context)
839 {
840 
841 	/* Check whether we're running in a PHP context, since the entity loader
842 	 * we've defined is an application level (true global) setting.
843 	 * If we are, we also want to check whether we've finished activating
844 	 * the modules (RINIT phase). Using our external entity loader during a
845 	 * RINIT should not be problem per se (though during MINIT it is, because
846 	 * we don't even have a resource list by then), but then whether one
847 	 * extension would be using the custom external entity loader or not
848 	 * could depend on extension loading order
849 	 * (if _php_libxml_per_request_initialization */
850 	if (xmlGenericError == php_libxml_error_handler && PG(modules_activated)) {
851 		return _php_libxml_external_entity_loader(URL, ID, context);
852 	} else {
853 		return _php_libxml_default_entity_loader(URL, ID, context);
854 	}
855 }
856 
php_libxml_ctx_error(void * ctx,const char * msg,...)857 PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...)
858 {
859 	va_list args;
860 	va_start(args, msg);
861 	php_libxml_internal_error_handler(PHP_LIBXML_CTX_ERROR, ctx, &msg, args);
862 	va_end(args);
863 }
864 
php_libxml_ctx_warning(void * ctx,const char * msg,...)865 PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...)
866 {
867 	va_list args;
868 	va_start(args, msg);
869 	php_libxml_internal_error_handler(PHP_LIBXML_CTX_WARNING, ctx, &msg, args);
870 	va_end(args);
871 }
872 
873 #if LIBXML_VERSION >= 21200
php_libxml_structured_error_handler(void * userData,const xmlError * error)874 void php_libxml_structured_error_handler(void *userData, const xmlError *error)
875 #else
876 void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error)
877 #endif
878 {
879 	_php_list_set_error_structure(error, NULL);
880 
881 	return;
882 }
883 
php_libxml_error_handler(void * ctx,const char * msg,...)884 PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...)
885 {
886 	va_list args;
887 	va_start(args, msg);
888 	php_libxml_internal_error_handler(PHP_LIBXML_ERROR, ctx, &msg, args);
889 	va_end(args);
890 }
891 
php_libxml_exports_dtor(zval * zv)892 static void php_libxml_exports_dtor(zval *zv)
893 {
894 	free(Z_PTR_P(zv));
895 }
896 
php_libxml_initialize(void)897 PHP_LIBXML_API void php_libxml_initialize(void)
898 {
899 	if (!_php_libxml_initialized) {
900 		/* we should be the only one's to ever init!! */
901 		ZEND_IGNORE_LEAKS_BEGIN();
902 		xmlInitParser();
903 		ZEND_IGNORE_LEAKS_END();
904 
905 		_php_libxml_default_entity_loader = xmlGetExternalEntityLoader();
906 		xmlSetExternalEntityLoader(_php_libxml_pre_ext_ent_loader);
907 
908 		zend_hash_init(&php_libxml_exports, 0, NULL, php_libxml_exports_dtor, 1);
909 
910 		_php_libxml_initialized = 1;
911 	}
912 }
913 
php_libxml_shutdown(void)914 PHP_LIBXML_API void php_libxml_shutdown(void)
915 {
916 	if (_php_libxml_initialized) {
917 #if defined(LIBXML_SCHEMAS_ENABLED) && LIBXML_VERSION < 21000
918 		xmlRelaxNGCleanupTypes();
919 #endif
920 		/* xmlCleanupParser(); */
921 		zend_hash_destroy(&php_libxml_exports);
922 
923 		xmlSetExternalEntityLoader(_php_libxml_default_entity_loader);
924 		_php_libxml_initialized = 0;
925 	}
926 }
927 
php_libxml_switch_context(zval * context,zval * oldcontext)928 PHP_LIBXML_API void php_libxml_switch_context(zval *context, zval *oldcontext)
929 {
930 	if (oldcontext) {
931 		ZVAL_COPY_VALUE(oldcontext, &LIBXML(stream_context));
932 	}
933 	if (context) {
934 		ZVAL_COPY_VALUE(&LIBXML(stream_context), context);
935 	}
936 }
937 
PHP_MINIT_FUNCTION(libxml)938 static PHP_MINIT_FUNCTION(libxml)
939 {
940 	php_libxml_initialize();
941 
942 	register_libxml_symbols(module_number);
943 
944 	libxmlerror_class_entry = register_class_LibXMLError();
945 
946 	if (sapi_module.name) {
947 		static const char * const supported_sapis[] = {
948 			"cgi-fcgi",
949 			"litespeed",
950 			NULL
951 		};
952 		const char * const *sapi_name;
953 
954 		for (sapi_name = supported_sapis; *sapi_name; sapi_name++) {
955 			if (strcmp(sapi_module.name, *sapi_name) == 0) {
956 				_php_libxml_per_request_initialization = 0;
957 				break;
958 			}
959 		}
960 	}
961 
962 	if (!_php_libxml_per_request_initialization) {
963 		/* report errors via handler rather than stderr */
964 		xmlSetGenericErrorFunc(NULL, php_libxml_error_handler);
965 		xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
966 		xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
967 	}
968 
969 	return SUCCESS;
970 }
971 
972 
PHP_RINIT_FUNCTION(libxml)973 static PHP_RINIT_FUNCTION(libxml)
974 {
975 	if (_php_libxml_per_request_initialization) {
976 		/* report errors via handler rather than stderr */
977 		xmlSetGenericErrorFunc(NULL, php_libxml_error_handler);
978 		xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
979 		xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
980 	}
981 
982 	/* Enable the entity loader by default. This ensures that
983 	 * other threads/requests that might have disabled the loader
984 	 * do not affect the current request.
985 	 */
986 	LIBXML(entity_loader_disabled) = 0;
987 
988 	return SUCCESS;
989 }
990 
PHP_RSHUTDOWN_FUNCTION(libxml)991 static PHP_RSHUTDOWN_FUNCTION(libxml)
992 {
993 	if (ZEND_FCC_INITIALIZED(LIBXML(entity_loader_callback))) {
994 		zend_fcc_dtor(&LIBXML(entity_loader_callback));
995 	}
996 
997 	return SUCCESS;
998 }
999 
PHP_MSHUTDOWN_FUNCTION(libxml)1000 static PHP_MSHUTDOWN_FUNCTION(libxml)
1001 {
1002 	if (!_php_libxml_per_request_initialization) {
1003 		xmlSetGenericErrorFunc(NULL, NULL);
1004 
1005 		xmlParserInputBufferCreateFilenameDefault(NULL);
1006 		xmlOutputBufferCreateFilenameDefault(NULL);
1007 	}
1008 	php_libxml_shutdown();
1009 
1010 	return SUCCESS;
1011 }
1012 
php_libxml_post_deactivate(void)1013 static zend_result php_libxml_post_deactivate(void)
1014 {
1015 	/* reset libxml generic error handling */
1016 	if (_php_libxml_per_request_initialization) {
1017 		xmlSetGenericErrorFunc(NULL, NULL);
1018 
1019 		xmlParserInputBufferCreateFilenameDefault(NULL);
1020 		xmlOutputBufferCreateFilenameDefault(NULL);
1021 	}
1022 	xmlSetStructuredErrorFunc(NULL, NULL);
1023 
1024 	/* the steam_context resource will be released by resource list destructor */
1025 	ZVAL_UNDEF(&LIBXML(stream_context));
1026 	smart_str_free(&LIBXML(error_buffer));
1027 	if (LIBXML(error_list)) {
1028 		zend_llist_destroy(LIBXML(error_list));
1029 		efree(LIBXML(error_list));
1030 		LIBXML(error_list) = NULL;
1031 	}
1032 	xmlResetLastError();
1033 
1034 	return SUCCESS;
1035 }
1036 
1037 
PHP_MINFO_FUNCTION(libxml)1038 static PHP_MINFO_FUNCTION(libxml)
1039 {
1040 	php_info_print_table_start();
1041 	php_info_print_table_row(2, "libXML support", "active");
1042 	php_info_print_table_row(2, "libXML Compiled Version", LIBXML_DOTTED_VERSION);
1043 	php_info_print_table_row(2, "libXML Loaded Version", (char *)xmlParserVersion);
1044 	php_info_print_table_row(2, "libXML streams", "enabled");
1045 	php_info_print_table_end();
1046 }
1047 /* }}} */
1048 
1049 /* {{{ Set the streams context for the next libxml document load or write */
PHP_FUNCTION(libxml_set_streams_context)1050 PHP_FUNCTION(libxml_set_streams_context)
1051 {
1052 	zval *arg;
1053 
1054 	ZEND_PARSE_PARAMETERS_START(1, 1)
1055 		Z_PARAM_RESOURCE(arg)
1056 	ZEND_PARSE_PARAMETERS_END();
1057 
1058 	if (!Z_ISUNDEF(LIBXML(stream_context))) {
1059 		zval_ptr_dtor(&LIBXML(stream_context));
1060 		ZVAL_UNDEF(&LIBXML(stream_context));
1061 	}
1062 	ZVAL_COPY(&LIBXML(stream_context), arg);
1063 }
1064 /* }}} */
1065 
1066 /* {{{ Disable libxml errors and allow user to fetch error information as needed */
PHP_FUNCTION(libxml_use_internal_errors)1067 PHP_FUNCTION(libxml_use_internal_errors)
1068 {
1069 	xmlStructuredErrorFunc current_handler;
1070 	bool use_errors, use_errors_is_null = 1, retval;
1071 
1072 	ZEND_PARSE_PARAMETERS_START(0, 1)
1073 		Z_PARAM_OPTIONAL
1074 		Z_PARAM_BOOL_OR_NULL(use_errors, use_errors_is_null)
1075 	ZEND_PARSE_PARAMETERS_END();
1076 
1077 	current_handler = xmlStructuredError;
1078 	if (current_handler && current_handler == php_libxml_structured_error_handler) {
1079 		retval = 1;
1080 	} else {
1081 		retval = 0;
1082 	}
1083 
1084 	if (use_errors_is_null) {
1085 		RETURN_BOOL(retval);
1086 	}
1087 
1088 	if (use_errors == 0) {
1089 		xmlSetStructuredErrorFunc(NULL, NULL);
1090 		if (LIBXML(error_list)) {
1091 			zend_llist_destroy(LIBXML(error_list));
1092 			efree(LIBXML(error_list));
1093 			LIBXML(error_list) = NULL;
1094 		}
1095 	} else {
1096 		xmlSetStructuredErrorFunc(NULL, php_libxml_structured_error_handler);
1097 		if (LIBXML(error_list) == NULL) {
1098 			LIBXML(error_list) = (zend_llist *) emalloc(sizeof(zend_llist));
1099 			zend_llist_init(LIBXML(error_list), sizeof(xmlError), _php_libxml_free_error, 0);
1100 		}
1101 	}
1102 	RETURN_BOOL(retval);
1103 }
1104 /* }}} */
1105 
1106 /* {{{ Retrieve last error from libxml */
PHP_FUNCTION(libxml_get_last_error)1107 PHP_FUNCTION(libxml_get_last_error)
1108 {
1109 	ZEND_PARSE_PARAMETERS_NONE();
1110 
1111 	const xmlError *error = xmlGetLastError();
1112 
1113 	if (error) {
1114 		object_init_ex(return_value, libxmlerror_class_entry);
1115 		add_property_long(return_value, "level", error->level);
1116 		add_property_long(return_value, "code", error->code);
1117 		add_property_long(return_value, "column", error->int2);
1118 		if (error->message) {
1119 			add_property_string(return_value, "message", error->message);
1120 		} else {
1121 			add_property_stringl(return_value, "message", "", 0);
1122 		}
1123 		if (error->file) {
1124 			add_property_string(return_value, "file", error->file);
1125 		} else {
1126 			add_property_stringl(return_value, "file", "", 0);
1127 		}
1128 		add_property_long(return_value, "line", error->line);
1129 	} else {
1130 		RETURN_FALSE;
1131 	}
1132 }
1133 /* }}} */
1134 
1135 /* {{{ Retrieve array of errors */
PHP_FUNCTION(libxml_get_errors)1136 PHP_FUNCTION(libxml_get_errors)
1137 {
1138 
1139 	xmlErrorPtr error;
1140 
1141 	ZEND_PARSE_PARAMETERS_NONE();
1142 
1143 	if (LIBXML(error_list)) {
1144 
1145 		array_init(return_value);
1146 		error = zend_llist_get_first(LIBXML(error_list));
1147 
1148 		while (error != NULL) {
1149 			zval z_error;
1150 
1151 			object_init_ex(&z_error, libxmlerror_class_entry);
1152 			add_property_long_ex(&z_error, "level", sizeof("level") - 1, error->level);
1153 			add_property_long_ex(&z_error, "code", sizeof("code") - 1, error->code);
1154 			add_property_long_ex(&z_error, "column", sizeof("column") - 1, error->int2 );
1155 			if (error->message) {
1156 				add_property_string_ex(&z_error, "message", sizeof("message") - 1, error->message);
1157 			} else {
1158 				add_property_stringl_ex(&z_error, "message", sizeof("message") - 1, "", 0);
1159 			}
1160 			if (error->file) {
1161 				add_property_string_ex(&z_error, "file", sizeof("file") - 1, error->file);
1162 			} else {
1163 				add_property_stringl_ex(&z_error, "file", sizeof("file") - 1, "", 0);
1164 			}
1165 			add_property_long_ex(&z_error, "line", sizeof("line") - 1, error->line);
1166 			add_next_index_zval(return_value, &z_error);
1167 
1168 			error = zend_llist_get_next(LIBXML(error_list));
1169 		}
1170 	} else {
1171 		RETURN_EMPTY_ARRAY();
1172 	}
1173 }
1174 /* }}} */
1175 
1176 /* {{{ Clear last error from libxml */
PHP_FUNCTION(libxml_clear_errors)1177 PHP_FUNCTION(libxml_clear_errors)
1178 {
1179 	ZEND_PARSE_PARAMETERS_NONE();
1180 
1181 	xmlResetLastError();
1182 	if (LIBXML(error_list)) {
1183 		zend_llist_clean(LIBXML(error_list));
1184 	}
1185 }
1186 /* }}} */
1187 
php_libxml_disable_entity_loader(bool disable)1188 PHP_LIBXML_API bool php_libxml_disable_entity_loader(bool disable) /* {{{ */
1189 {
1190 	bool old = LIBXML(entity_loader_disabled);
1191 
1192 	LIBXML(entity_loader_disabled) = disable;
1193 	return old;
1194 } /* }}} */
1195 
1196 /* {{{ Disable/Enable ability to load external entities */
PHP_FUNCTION(libxml_disable_entity_loader)1197 PHP_FUNCTION(libxml_disable_entity_loader)
1198 {
1199 	bool disable = 1;
1200 
1201 	ZEND_PARSE_PARAMETERS_START(0, 1)
1202 		Z_PARAM_OPTIONAL
1203 		Z_PARAM_BOOL(disable)
1204 	ZEND_PARSE_PARAMETERS_END();
1205 
1206 	RETURN_BOOL(php_libxml_disable_entity_loader(disable));
1207 }
1208 /* }}} */
1209 
1210 /* {{{ Changes the default external entity loader */
PHP_FUNCTION(libxml_set_external_entity_loader)1211 PHP_FUNCTION(libxml_set_external_entity_loader)
1212 {
1213 	zend_fcall_info			fci;
1214 	zend_fcall_info_cache	fcc;
1215 
1216 	ZEND_PARSE_PARAMETERS_START(1, 1)
1217 		Z_PARAM_FUNC_OR_NULL(fci, fcc)
1218 	ZEND_PARSE_PARAMETERS_END();
1219 
1220 	/* Unset old callback if it's defined */
1221 	if (ZEND_FCC_INITIALIZED(LIBXML(entity_loader_callback))) {
1222 		zend_fcc_dtor(&LIBXML(entity_loader_callback));
1223 	}
1224 	if (ZEND_FCI_INITIALIZED(fci)) { /* argument not null */
1225 		if (!ZEND_FCC_INITIALIZED(fcc)) {
1226 			zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL);
1227 			/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
1228 			 * with it outselves. It is important that it is not refetched on every call,
1229 			 * because calls may occur from different scopes. */
1230 		}
1231 		zend_fcc_dup(&LIBXML(entity_loader_callback), &fcc);
1232 	}
1233 	RETURN_TRUE;
1234 }
1235 /* }}} */
1236 
1237 /* {{{ Get the current external entity loader, or null if the default loader is installer. */
PHP_FUNCTION(libxml_get_external_entity_loader)1238 PHP_FUNCTION(libxml_get_external_entity_loader)
1239 {
1240 	ZEND_PARSE_PARAMETERS_NONE();
1241 
1242 	if (ZEND_FCC_INITIALIZED(LIBXML(entity_loader_callback))) {
1243 		zval tmp;
1244 		zend_get_callable_zval_from_fcc(&LIBXML(entity_loader_callback), &tmp);
1245 		RETVAL_COPY(&tmp);
1246 		zval_ptr_dtor(&tmp);
1247 		return;
1248 	}
1249 	RETURN_NULL();
1250 }
1251 /* }}} */
1252 
1253 /* {{{ Common functions shared by extensions */
php_libxml_xmlCheckUTF8(const unsigned char * s)1254 int php_libxml_xmlCheckUTF8(const unsigned char *s)
1255 {
1256 	size_t i;
1257 	unsigned char c;
1258 
1259 	for (i = 0; (c = s[i++]);) {
1260 		if ((c & 0x80) == 0) {
1261 		} else if ((c & 0xe0) == 0xc0) {
1262 			if ((s[i++] & 0xc0) != 0x80) {
1263 				return 0;
1264 			}
1265 		} else if ((c & 0xf0) == 0xe0) {
1266 			if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) {
1267 				return 0;
1268 			}
1269 		} else if ((c & 0xf8) == 0xf0) {
1270 			if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) {
1271 				return 0;
1272 			}
1273 		} else {
1274 			return 0;
1275 		}
1276 	}
1277 	return 1;
1278 }
1279 
php_libxml_register_export(zend_class_entry * ce,php_libxml_export_node export_function)1280 zval *php_libxml_register_export(zend_class_entry *ce, php_libxml_export_node export_function)
1281 {
1282 	php_libxml_func_handler export_hnd;
1283 
1284 	/* Initialize in case this module hasn't been loaded yet */
1285 	php_libxml_initialize();
1286 	export_hnd.export_func = export_function;
1287 
1288 	return zend_hash_add_mem(&php_libxml_exports, ce->name, &export_hnd, sizeof(export_hnd));
1289 }
1290 
php_libxml_import_node(zval * object)1291 PHP_LIBXML_API xmlNodePtr php_libxml_import_node(zval *object)
1292 {
1293 	zend_class_entry *ce = NULL;
1294 	xmlNodePtr node = NULL;
1295 	php_libxml_func_handler *export_hnd;
1296 
1297 	if (Z_TYPE_P(object) == IS_OBJECT) {
1298 		ce = Z_OBJCE_P(object);
1299 		while (ce->parent != NULL) {
1300 			ce = ce->parent;
1301 		}
1302 		if ((export_hnd = zend_hash_find_ptr(&php_libxml_exports, ce->name))) {
1303 			node = export_hnd->export_func(object);
1304 		}
1305 	}
1306 	return node;
1307 }
1308 
php_libxml_increment_node_ptr(php_libxml_node_object * object,xmlNodePtr node,void * private_data)1309 PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data)
1310 {
1311 	int ret_refcount = -1;
1312 
1313 	if (object != NULL && node != NULL) {
1314 		if (object->node != NULL) {
1315 			if (object->node->node == node) {
1316 				return object->node->refcount;
1317 			} else {
1318 				php_libxml_decrement_node_ptr(object);
1319 			}
1320 		}
1321 		if (node->_private != NULL) {
1322 			object->node = node->_private;
1323 			ret_refcount = ++object->node->refcount;
1324 			/* Only dom uses _private */
1325 			if (object->node->_private == NULL) {
1326 				object->node->_private = private_data;
1327 			}
1328 		} else {
1329 			object->node = emalloc(sizeof(php_libxml_node_ptr));
1330 			ret_refcount = 1;
1331 			object->node->node = node;
1332 			object->node->refcount = 1;
1333 			object->node->_private = private_data;
1334 			node->_private = object->node;
1335 		}
1336 	}
1337 
1338 	return ret_refcount;
1339 }
1340 
php_libxml_decrement_node_ptr(php_libxml_node_object * object)1341 PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object)
1342 {
1343 	int ret_refcount = -1;
1344 	php_libxml_node_ptr *obj_node;
1345 
1346 	if (object != NULL && object->node != NULL) {
1347 		obj_node = (php_libxml_node_ptr *) object->node;
1348 		ret_refcount = --obj_node->refcount;
1349 		if (ret_refcount == 0) {
1350 			if (obj_node->node != NULL) {
1351 				obj_node->node->_private = NULL;
1352 			}
1353 			efree(obj_node);
1354 		}
1355 		object->node = NULL;
1356 	}
1357 
1358 	return ret_refcount;
1359 }
1360 
php_libxml_increment_doc_ref(php_libxml_node_object * object,xmlDocPtr docp)1361 PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp)
1362 {
1363 	int ret_refcount = -1;
1364 
1365 	if (object->document != NULL) {
1366 		object->document->refcount++;
1367 		ret_refcount = object->document->refcount;
1368 	} else if (docp != NULL) {
1369 		ret_refcount = 1;
1370 		object->document = emalloc(sizeof(php_libxml_ref_obj));
1371 		object->document->ptr = docp;
1372 		object->document->refcount = ret_refcount;
1373 		object->document->doc_props = NULL;
1374 		object->document->cache_tag.modification_nr = 1; /* iterators start at 0, such that they will start in an uninitialised state */
1375 	}
1376 
1377 	return ret_refcount;
1378 }
1379 
php_libxml_decrement_doc_ref(php_libxml_node_object * object)1380 PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object)
1381 {
1382 	int ret_refcount = -1;
1383 
1384 	if (object != NULL && object->document != NULL) {
1385 		ret_refcount = --object->document->refcount;
1386 		if (ret_refcount == 0) {
1387 			if (object->document->ptr != NULL) {
1388 				xmlFreeDoc((xmlDoc *) object->document->ptr);
1389 			}
1390 			if (object->document->doc_props != NULL) {
1391 				if (object->document->doc_props->classmap) {
1392 					zend_hash_destroy(object->document->doc_props->classmap);
1393 					FREE_HASHTABLE(object->document->doc_props->classmap);
1394 				}
1395 				efree(object->document->doc_props);
1396 			}
1397 			efree(object->document);
1398 		}
1399 		object->document = NULL;
1400 	}
1401 
1402 	return ret_refcount;
1403 }
1404 
php_libxml_node_free_resource(xmlNodePtr node)1405 PHP_LIBXML_API void php_libxml_node_free_resource(xmlNodePtr node)
1406 {
1407 	if (!node) {
1408 		return;
1409 	}
1410 
1411 	switch (node->type) {
1412 		case XML_DOCUMENT_NODE:
1413 		case XML_HTML_DOCUMENT_NODE:
1414 			break;
1415 		case XML_ENTITY_REF_NODE:
1416 			/* Entity reference nodes are special: their children point to entity declarations,
1417 			 * but they don't own the declarations and therefore shouldn't free the children.
1418 			 * Moreover, there can be more than one reference node for a single entity declarations. */
1419 			php_libxml_unregister_node(node);
1420 			if (node->parent == NULL) {
1421 				php_libxml_node_free(node);
1422 			}
1423 			break;
1424 		default:
1425 			if (node->parent == NULL || node->type == XML_NAMESPACE_DECL) {
1426 				php_libxml_node_free_list((xmlNodePtr) node->children);
1427 				switch (node->type) {
1428 					/* Skip property freeing for the following types */
1429 					case XML_ATTRIBUTE_DECL:
1430 					case XML_DTD_NODE:
1431 					case XML_DOCUMENT_TYPE_NODE:
1432 					case XML_ENTITY_DECL:
1433 					case XML_ATTRIBUTE_NODE:
1434 					case XML_NAMESPACE_DECL:
1435 					case XML_TEXT_NODE:
1436 						break;
1437 					default:
1438 						php_libxml_node_free_list((xmlNodePtr) node->properties);
1439 				}
1440 				php_libxml_unregister_node(node);
1441 				php_libxml_node_free(node);
1442 			} else {
1443 				php_libxml_unregister_node(node);
1444 			}
1445 	}
1446 }
1447 
php_libxml_node_decrement_resource(php_libxml_node_object * object)1448 PHP_LIBXML_API void php_libxml_node_decrement_resource(php_libxml_node_object *object)
1449 {
1450 	if (object != NULL && object->node != NULL) {
1451 		php_libxml_node_ptr *obj_node = (php_libxml_node_ptr *) object->node;
1452 		xmlNodePtr nodep = obj_node->node;
1453 		int ret_refcount = php_libxml_decrement_node_ptr(object);
1454 		if (ret_refcount == 0) {
1455 			php_libxml_node_free_resource(nodep);
1456 		} else {
1457 			if (object == obj_node->_private) {
1458 				obj_node->_private = NULL;
1459 			}
1460 		}
1461 	}
1462 	if (object != NULL && object->document != NULL) {
1463 		/* Safe to call as if the resource were freed then doc pointer is NULL */
1464 		php_libxml_decrement_doc_ref(object);
1465 	}
1466 }
1467 /* }}} */
1468 
1469 #if defined(PHP_WIN32) && defined(COMPILE_DL_LIBXML)
DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)1470 PHP_LIBXML_API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
1471 {
1472 	return xmlDllMain(hinstDLL, fdwReason, lpvReserved);
1473 }
1474 #endif
1475 
1476 #endif
1477