summaryrefslogtreecommitdiff
path: root/ext/libxml
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2012-02-01 21:25:15 +0100
committerOndřej Surý <ondrej@sury.org>2012-02-01 21:25:15 +0100
commit96fb2ff5760132a915766f1d9ec7c63001feacd8 (patch)
tree160904a89a8f3522fa4e47632db101b045e7814a /ext/libxml
parent8f1428d29ef91d74b4d272af171675f2971eb15b (diff)
downloadphp-96fb2ff5760132a915766f1d9ec7c63001feacd8.tar.gz
Imported Upstream version 5.4.0~rc6upstream/5.4.0_rc6
Diffstat (limited to 'ext/libxml')
-rw-r--r--ext/libxml/config.w322
-rw-r--r--ext/libxml/libxml.c262
-rw-r--r--ext/libxml/php_libxml.h4
-rw-r--r--ext/libxml/tests/libxml_set_external_entity_loader_basic.phpt48
-rw-r--r--ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt39
-rw-r--r--ext/libxml/tests/libxml_set_external_entity_loader_variation1.phpt72
-rw-r--r--ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt45
7 files changed, 458 insertions, 14 deletions
diff --git a/ext/libxml/config.w32 b/ext/libxml/config.w32
index 13f9347d4..92fd344e5 100644
--- a/ext/libxml/config.w32
+++ b/ext/libxml/config.w32
@@ -1,4 +1,4 @@
-// $Id: config.w32 306344 2010-12-13 18:43:10Z pajoye $
+// $Id: config.w32 306241 2010-12-11 22:18:10Z pajoye $
// vim:ft=javascript
ARG_WITH("libxml", "LibXML support", "yes");
diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
index 3aef65f4e..aaf4cad58 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -26,6 +26,7 @@
#endif
#include "php.h"
+#include "SAPI.h"
#define PHP_XML_INTERNAL
#include "zend_variables.h"
@@ -53,6 +54,8 @@
/* a true global for initialization */
static int _php_libxml_initialized = 0;
+static int _php_libxml_per_request_initialization = 1;
+static xmlExternalEntityLoader _php_libxml_default_entity_loader;
typedef struct _php_libxml_func_handler {
php_libxml_export_node export_func;
@@ -68,6 +71,7 @@ static PHP_FUNCTION(libxml_use_internal_errors);
static PHP_FUNCTION(libxml_get_last_error);
static PHP_FUNCTION(libxml_clear_errors);
static PHP_FUNCTION(libxml_get_errors);
+static PHP_FUNCTION(libxml_set_external_entity_loader);
static PHP_FUNCTION(libxml_disable_entity_loader);
static zend_class_entry *libxmlerror_class_entry;
@@ -109,6 +113,9 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_disable_entity_loader, 0, 0, 0)
ZEND_ARG_INFO(0, disable)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_set_external_entity_loader, 0, 0, 1)
+ ZEND_ARG_INFO(0, resolver_function)
+ZEND_END_ARG_INFO()
/* }}} */
/* {{{ extension definition structures */
@@ -119,6 +126,7 @@ static const zend_function_entry libxml_functions[] = {
PHP_FE(libxml_clear_errors, arginfo_libxml_clear_errors)
PHP_FE(libxml_get_errors, arginfo_libxml_get_errors)
PHP_FE(libxml_disable_entity_loader, arginfo_libxml_disable_entity_loader)
+ PHP_FE(libxml_set_external_entity_loader, arginfo_libxml_set_external_entity_loader)
PHP_FE_END
};
@@ -261,6 +269,18 @@ static PHP_GINIT_FUNCTION(libxml)
libxml_globals->stream_context = NULL;
libxml_globals->error_buffer.c = NULL;
libxml_globals->error_list = NULL;
+ libxml_globals->entity_loader.fci.size = 0;
+}
+
+static void _php_libxml_destroy_fci(zend_fcall_info *fci)
+{
+ if (fci->size > 0) {
+ zval_ptr_dtor(&fci->function_name);
+ if (fci->object_ptr != NULL) {
+ zval_ptr_dtor(&fci->object_ptr);
+ }
+ fci->size = 0;
+ }
}
/* Channel libxml file io layer through the PHP streams subsystem.
@@ -278,8 +298,9 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char
TSRMLS_FETCH();
- uri = xmlParseURI((xmlChar *)filename);
- if (uri && (uri->scheme == NULL || (xmlStrncmp(uri->scheme, "file", 4) == 0))) {
+ uri = xmlParseURI(filename);
+ if (uri && (uri->scheme == NULL ||
+ (xmlStrncmp(BAD_CAST uri->scheme, BAD_CAST "file", 4) == 0))) {
resolved_path = xmlURIUnescapeString(filename, 0, NULL);
isescaped = 1;
} else {
@@ -300,7 +321,7 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char
that the streams layer puts out at times, but for libxml we
may try to open files that don't exist, but it is not a failure
in xml processing (eg. DTD files) */
- wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, ENFORCE_SAFE_MODE TSRMLS_CC);
+ wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, 0 TSRMLS_CC);
if (wrapper && read_only && wrapper->wops->url_stat) {
if (wrapper->wops->url_stat(wrapper, path_to_open, PHP_STREAM_URL_STAT_QUIET, &ssbuf, NULL TSRMLS_CC) == -1) {
if (isescaped) {
@@ -311,8 +332,8 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char
}
context = php_stream_context_from_zval(LIBXML(stream_context), 0);
-
- ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, ENFORCE_SAFE_MODE|REPORT_ERRORS, NULL, context);
+
+ ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, REPORT_ERRORS, NULL, context);
if (isescaped) {
xmlFree(resolved_path);
}
@@ -528,6 +549,143 @@ static void php_libxml_internal_error_handler(int error_type, void *ctx, const c
}
}
+static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL,
+ const char *ID, xmlParserCtxtPtr context)
+{
+ xmlParserInputPtr ret = NULL;
+ const char *resource = NULL;
+ zval *public = NULL,
+ *system = NULL,
+ *ctxzv = NULL,
+ **params[] = {&public, &system, &ctxzv},
+ *retval_ptr = NULL;
+ int retval;
+ zend_fcall_info *fci;
+ TSRMLS_FETCH();
+
+ fci = &LIBXML(entity_loader).fci;
+
+ if (fci->size == 0) {
+ /* no custom user-land callback set up; delegate to original loader */
+ return _php_libxml_default_entity_loader(URL, ID, context);
+ }
+
+ ALLOC_INIT_ZVAL(public);
+ if (ID != NULL) {
+ ZVAL_STRING(public, ID, 1);
+ }
+ ALLOC_INIT_ZVAL(system);
+ if (URL != NULL) {
+ ZVAL_STRING(system, URL, 1);
+ }
+ MAKE_STD_ZVAL(ctxzv);
+ array_init_size(ctxzv, 4);
+
+#define ADD_NULL_OR_STRING_KEY(memb) \
+ if (context->memb == NULL) { \
+ add_assoc_null_ex(ctxzv, #memb, sizeof(#memb)); \
+ } else { \
+ add_assoc_string_ex(ctxzv, #memb, sizeof(#memb), \
+ (char *)context->memb, 1); \
+ }
+
+ ADD_NULL_OR_STRING_KEY(directory)
+ ADD_NULL_OR_STRING_KEY(intSubName)
+ ADD_NULL_OR_STRING_KEY(extSubURI)
+ ADD_NULL_OR_STRING_KEY(extSubSystem)
+
+#undef ADD_NULL_OR_STRING_KEY
+
+ fci->retval_ptr_ptr = &retval_ptr;
+ fci->params = params;
+ fci->param_count = sizeof(params)/sizeof(*params);
+ fci->no_separation = 1;
+
+ retval = zend_call_function(fci, &LIBXML(entity_loader).fcc TSRMLS_CC);
+ if (retval != SUCCESS || fci->retval_ptr_ptr == NULL) {
+ php_libxml_ctx_error(context,
+ "Call to user entity loader callback '%s' has failed",
+ fci->function_name);
+ } else {
+ retval_ptr = *fci->retval_ptr_ptr;
+ if (retval_ptr == NULL) {
+ php_libxml_ctx_error(context,
+ "Call to user entity loader callback '%s' has failed; "
+ "probably it has thrown an exception",
+ fci->function_name);
+ } else if (Z_TYPE_P(retval_ptr) == IS_STRING) {
+is_string:
+ resource = Z_STRVAL_P(retval_ptr);
+ } else if (Z_TYPE_P(retval_ptr) == IS_RESOURCE) {
+ php_stream *stream;
+ php_stream_from_zval_no_verify(stream, &retval_ptr);
+ if (stream == NULL) {
+ php_libxml_ctx_error(context,
+ "The user entity loader callback '%s' has returned a "
+ "resource, but it is not a stream",
+ fci->function_name);
+ } else {
+ /* TODO: allow storing the encoding in the stream context? */
+ xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
+ xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc);
+ if (pib == NULL) {
+ php_libxml_ctx_error(context, "Could not allocate parser "
+ "input buffer");
+ } else {
+ /* make stream not being closed when the zval is freed */
+ zend_list_addref(stream->rsrc_id);
+ pib->context = stream;
+ pib->readcallback = php_libxml_streams_IO_read;
+ pib->closecallback = php_libxml_streams_IO_close;
+
+ ret = xmlNewIOInputStream(context, pib, enc);
+ if (ret == NULL) {
+ xmlFreeParserInputBuffer(pib);
+ }
+ }
+ }
+ } else if (Z_TYPE_P(retval_ptr) != IS_NULL) {
+ /* retval not string nor resource nor null; convert to string */
+ SEPARATE_ZVAL(&retval_ptr);
+ convert_to_string(retval_ptr);
+ goto is_string;
+ } /* else is null; don't try anything */
+ }
+
+ if (ret == NULL) {
+ if (resource == NULL) {
+ if (ID == NULL) {
+ ID = "NULL";
+ }
+ php_libxml_ctx_error(context,
+ "Failed to load external entity \"%s\"\n", ID);
+ } else {
+ /* we got the resource in the form of a string; open it */
+ ret = xmlNewInputFromFile(context, resource);
+ }
+ }
+
+ zval_ptr_dtor(&public);
+ zval_ptr_dtor(&system);
+ zval_ptr_dtor(&ctxzv);
+ if (retval_ptr != NULL) {
+ zval_ptr_dtor(&retval_ptr);
+ }
+ return ret;
+}
+
+static xmlParserInputPtr _php_libxml_pre_ext_ent_loader(const char *URL,
+ const char *ID, xmlParserCtxtPtr context)
+{
+ /* Check whether we're running in a PHP context, since the entity loader
+ * we've defined is an application level (true global) setting */
+ if (xmlGenericError == php_libxml_error_handler) {
+ return _php_libxml_external_entity_loader(URL, ID, context);
+ } else {
+ return _php_libxml_default_entity_loader(URL, ID, context);
+ }
+}
+
PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...)
{
va_list args;
@@ -565,6 +723,9 @@ PHP_LIBXML_API void php_libxml_initialize(void)
if (!_php_libxml_initialized) {
/* we should be the only one's to ever init!! */
xmlInitParser();
+
+ _php_libxml_default_entity_loader = xmlGetExternalEntityLoader();
+ xmlSetExternalEntityLoader(_php_libxml_pre_ext_ent_loader);
zend_hash_init(&php_libxml_exports, 0, NULL, NULL, 1);
@@ -580,6 +741,8 @@ PHP_LIBXML_API void php_libxml_shutdown(void)
#endif
xmlCleanupParser();
zend_hash_destroy(&php_libxml_exports);
+
+ xmlSetExternalEntityLoader(_php_libxml_default_entity_loader);
_php_libxml_initialized = 0;
}
}
@@ -616,6 +779,7 @@ static PHP_MINIT_FUNCTION(libxml)
REGISTER_LONG_CONSTANT("LIBXML_NSCLEAN", XML_PARSE_NSCLEAN, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("LIBXML_NOCDATA", XML_PARSE_NOCDATA, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("LIBXML_NONET", XML_PARSE_NONET, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("LIBXML_PEDANTIC", XML_PARSE_PEDANTIC, CONST_CS | CONST_PERSISTENT);
#if LIBXML_VERSION >= 20621
REGISTER_LONG_CONSTANT("LIBXML_COMPACT", XML_PARSE_COMPACT, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("LIBXML_NOXMLDECL", XML_SAVE_NO_DECL, CONST_CS | CONST_PERSISTENT);
@@ -625,6 +789,15 @@ static PHP_MINIT_FUNCTION(libxml)
#endif
REGISTER_LONG_CONSTANT("LIBXML_NOEMPTYTAG", LIBXML_SAVE_NOEMPTYTAG, CONST_CS | CONST_PERSISTENT);
+ /* Additional constants for use with loading html */
+#if LIBXML_VERSION >= 20707
+ REGISTER_LONG_CONSTANT("LIBXML_HTML_NOIMPLIED", HTML_PARSE_NOIMPLIED, CONST_CS | CONST_PERSISTENT);
+#endif
+
+#if LIBXML_VERSION >= 20708
+ REGISTER_LONG_CONSTANT("LIBXML_HTML_NODEFDTD", HTML_PARSE_NODEFDTD, CONST_CS | CONST_PERSISTENT);
+#endif
+
/* Error levels */
REGISTER_LONG_CONSTANT("LIBXML_ERR_NONE", XML_ERR_NONE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("LIBXML_ERR_WARNING", XML_ERR_WARNING, CONST_CS | CONST_PERSISTENT);
@@ -634,22 +807,55 @@ static PHP_MINIT_FUNCTION(libxml)
INIT_CLASS_ENTRY(ce, "LibXMLError", NULL);
libxmlerror_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
+ if (sapi_module.name) {
+ static const char * const supported_sapis[] = {
+ "cgi-fcgi",
+ "fpm-fcgi",
+ "litespeed",
+ NULL
+ };
+ const char * const *sapi_name;
+
+ for (sapi_name = supported_sapis; *sapi_name; sapi_name++) {
+ if (strcmp(sapi_module.name, *sapi_name) == 0) {
+ _php_libxml_per_request_initialization = 0;
+ break;
+ }
+ }
+ }
+
+ if (!_php_libxml_per_request_initialization) {
+ /* report errors via handler rather than stderr */
+ xmlSetGenericErrorFunc(NULL, php_libxml_error_handler);
+ xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
+ xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
+ }
+
return SUCCESS;
}
static PHP_RINIT_FUNCTION(libxml)
{
- /* report errors via handler rather than stderr */
- xmlSetGenericErrorFunc(NULL, php_libxml_error_handler);
- xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
- xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
+ if (_php_libxml_per_request_initialization) {
+ /* report errors via handler rather than stderr */
+ xmlSetGenericErrorFunc(NULL, php_libxml_error_handler);
+ xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
+ xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
+ }
return SUCCESS;
}
static PHP_MSHUTDOWN_FUNCTION(libxml)
{
+ if (!_php_libxml_per_request_initialization) {
+ xmlSetGenericErrorFunc(NULL, NULL);
+ xmlSetStructuredErrorFunc(NULL, NULL);
+
+ xmlParserInputBufferCreateFilenameDefault(NULL);
+ xmlOutputBufferCreateFilenameDefault(NULL);
+ }
php_libxml_shutdown();
return SUCCESS;
@@ -659,11 +865,13 @@ static PHP_MSHUTDOWN_FUNCTION(libxml)
static PHP_RSHUTDOWN_FUNCTION(libxml)
{
/* reset libxml generic error handling */
- xmlSetGenericErrorFunc(NULL, NULL);
- xmlSetStructuredErrorFunc(NULL, NULL);
+ if (_php_libxml_per_request_initialization) {
+ xmlSetGenericErrorFunc(NULL, NULL);
+ xmlSetStructuredErrorFunc(NULL, NULL);
- xmlParserInputBufferCreateFilenameDefault(NULL);
- xmlOutputBufferCreateFilenameDefault(NULL);
+ xmlParserInputBufferCreateFilenameDefault(NULL);
+ xmlOutputBufferCreateFilenameDefault(NULL);
+ }
if (LIBXML(stream_context)) {
zval_ptr_dtor(&LIBXML(stream_context));
@@ -676,6 +884,8 @@ static PHP_RSHUTDOWN_FUNCTION(libxml)
LIBXML(error_list) = NULL;
}
xmlResetLastError();
+
+ _php_libxml_destroy_fci(&LIBXML(entity_loader).fci);
return SUCCESS;
}
@@ -858,6 +1068,32 @@ static PHP_FUNCTION(libxml_disable_entity_loader)
}
/* }}} */
+/* {{{ proto void libxml_set_external_entity_loader(callback resolver_function)
+ Changes the default external entity loader */
+static PHP_FUNCTION(libxml_set_external_entity_loader)
+{
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f!", &fci, &fcc)
+ == FAILURE) {
+ return;
+ }
+
+ _php_libxml_destroy_fci(&LIBXML(entity_loader).fci);
+
+ if (fci.size > 0) { /* argument not null */
+ LIBXML(entity_loader).fci = fci;
+ Z_ADDREF_P(fci.function_name);
+ if (fci.object_ptr != NULL) {
+ Z_ADDREF_P(fci.object_ptr);
+ }
+ LIBXML(entity_loader).fcc = fcc;
+ }
+
+ RETURN_TRUE;
+}
+/* }}} */
+
/* {{{ Common functions shared by extensions */
int php_libxml_xmlCheckUTF8(const unsigned char *s)
{
diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
index 36ec885fe..3259a61f6 100644
--- a/ext/libxml/php_libxml.h
+++ b/ext/libxml/php_libxml.h
@@ -43,6 +43,10 @@ ZEND_BEGIN_MODULE_GLOBALS(libxml)
zval *stream_context;
smart_str error_buffer;
zend_llist *error_list;
+ struct _php_libxml_entity_resolver {
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ } entity_loader;
ZEND_END_MODULE_GLOBALS(libxml)
typedef struct _libxml_doc_props {
diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_basic.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_basic.phpt
new file mode 100644
index 000000000..51ab77705
--- /dev/null
+++ b/ext/libxml/tests/libxml_set_external_entity_loader_basic.phpt
@@ -0,0 +1,48 @@
+--TEST--
+libxml_set_external_entity_loader() basic test
+--SKIPIF--
+<?php if (!extension_loaded('dom')) die('skip'); ?>
+--FILE--
+<?php
+$xml = <<<XML
+<!DOCTYPE foo PUBLIC "-//FOO/BAR" "http://example.com/foobar">
+<foo>bar</foo>
+XML;
+
+$dtd = <<<DTD
+<!ELEMENT foo (#PCDATA)>
+DTD;
+
+libxml_set_external_entity_loader(
+ function ($public, $system, $context) use($dtd){
+ var_dump($public);
+ var_dump($system);
+ var_dump($context);
+ $f = fopen("php://temp", "r+");
+ fwrite($f, $dtd);
+ rewind($f);
+ return $f;
+ }
+);
+
+$dd = new DOMDocument;
+$r = $dd->loadXML($xml);
+var_dump($dd->validate());
+
+echo "Done.\n";
+
+--EXPECT--
+string(10) "-//FOO/BAR"
+string(25) "http://example.com/foobar"
+array(4) {
+ ["directory"]=>
+ NULL
+ ["intSubName"]=>
+ NULL
+ ["extSubURI"]=>
+ NULL
+ ["extSubSystem"]=>
+ NULL
+}
+bool(true)
+Done.
diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt
new file mode 100644
index 000000000..5ed079d8d
--- /dev/null
+++ b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt
@@ -0,0 +1,39 @@
+--TEST--
+libxml_set_external_entity_loader() error: bad arguments
+--SKIPIF--
+<?php if (!extension_loaded('dom')) die('skip'); ?>
+--FILE--
+<?php
+$xml = <<<XML
+<!DOCTYPE foo PUBLIC "-//FOO/BAR" "http://example.com/foobar">
+<foo>bar</foo>
+XML;
+
+$dd = new DOMDocument;
+$r = $dd->loadXML($xml);
+
+var_dump(libxml_set_external_entity_loader([]));
+var_dump(libxml_set_external_entity_loader());
+var_dump(libxml_set_external_entity_loader(function() {}, 2));
+
+var_dump(libxml_set_external_entity_loader(function($a, $b, $c, $d) {}));
+var_dump($dd->validate());
+
+echo "Done.\n";
+
+--EXPECTF--
+Warning: libxml_set_external_entity_loader() expects parameter 1 to be a valid callback, array must have exactly two members in %s on line %d
+NULL
+
+Warning: libxml_set_external_entity_loader() expects exactly 1 parameter, 0 given in %s on line %d
+NULL
+
+Warning: libxml_set_external_entity_loader() expects exactly 1 parameter, 2 given in %s on line %d
+NULL
+bool(true)
+
+Warning: Missing argument 4 for {closure}() in %s on line %d
+
+Warning: DOMDocument::validate(): Could not load the external subset "http://example.com/foobar" in %s on line %d
+bool(false)
+Done.
diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation1.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation1.phpt
new file mode 100644
index 000000000..c9c45940b
--- /dev/null
+++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation1.phpt
@@ -0,0 +1,72 @@
+--TEST--
+libxml_set_external_entity_loader() variation: resolve externals and entities
+--SKIPIF--
+<?php if (!extension_loaded('dom')) die('skip'); ?>
+--FILE--
+<?php
+chdir(__DIR__);
+$xml = <<<XML
+<!DOCTYPE foo PUBLIC "-//FOO/BAR" "http://example.com/foobar">
+<foo>bar&fooz;</foo>
+XML;
+
+$dtd = <<<DTD
+<!ELEMENT foo (#PCDATA)>
+<!ENTITY % fooentity PUBLIC
+ "-//FOO/ENTITY"
+ "fooentity.ent">
+%fooentity;
+DTD;
+
+$entity = <<<ENT
+<!ENTITY fooz "baz">
+ENT;
+
+libxml_set_external_entity_loader(
+ function ($public, $system, $context) use($dtd,$entity){
+ static $first = true;
+ var_dump($public);
+ var_dump($system);
+ var_dump($context);
+ $f = fopen("php://temp", "r+");
+ fwrite($f, $first ? $dtd : $entity);
+ $first = false;
+ rewind($f);
+ return $f;
+ }
+);
+
+$dd = new DOMDocument;
+$dd->resolveExternals = true;
+$r = $dd->loadXML($xml);
+var_dump($dd->validate());
+
+echo "Done.\n";
+
+--EXPECTF--
+string(10) "-//FOO/BAR"
+string(25) "http://example.com/foobar"
+array(4) {
+ ["directory"]=>
+ string(%d) "%s"
+ ["intSubName"]=>
+ string(3) "foo"
+ ["extSubURI"]=>
+ string(25) "http://example.com/foobar"
+ ["extSubSystem"]=>
+ string(10) "-//FOO/BAR"
+}
+string(13) "-//FOO/ENTITY"
+string(32) "http://example.com/fooentity.ent"
+array(4) {
+ ["directory"]=>
+ string(%d) "%s"
+ ["intSubName"]=>
+ string(3) "foo"
+ ["extSubURI"]=>
+ string(25) "http://example.com/foobar"
+ ["extSubSystem"]=>
+ string(10) "-//FOO/BAR"
+}
+bool(true)
+Done.
diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
new file mode 100644
index 000000000..b6251bea6
--- /dev/null
+++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
@@ -0,0 +1,45 @@
+--TEST--
+libxml_set_external_entity_loader() variation: restore original handler; returning NULL
+--SKIPIF--
+<?php if (!extension_loaded('dom')) die('skip'); ?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . "/foobar.dtd");
+--FILE--
+<?php
+chdir(__DIR__);
+$xml = <<<XML
+<!DOCTYPE foo PUBLIC "-//FOO/BAR" "foobar.dtd">
+<foo>bar</foo>
+XML;
+
+$dtd = <<<DTD
+<!ELEMENT foo (#PCDATA)>
+DTD;
+
+
+libxml_set_external_entity_loader(
+ function ($public, $system, $context) {
+ var_dump($public,$system);
+ return null;
+ }
+);
+
+$dd = new DOMDocument;
+$r = $dd->loadXML($xml);
+var_dump($dd->validate());
+
+libxml_set_external_entity_loader(NULL);
+file_put_contents(__DIR__ . "/foobar.dtd", $dtd);
+var_dump($dd->validate());
+
+echo "Done.\n";
+
+--EXPECTF--
+string(10) "-//FOO/BAR"
+string(%d) "%sfoobar.dtd"
+
+Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d
+bool(false)
+bool(true)
+Done.