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