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