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