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