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