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