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