]> granicus.if.org Git - php/commitdiff
- Added libxml_set_external_entity_loader().
authorGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 29 Aug 2011 05:00:26 +0000 (05:00 +0000)
committerGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 29 Aug 2011 05:00:26 +0000 (05:00 +0000)
UPGRADING
ext/libxml/libxml.c
ext/libxml/php_libxml.h
ext/libxml/tests/libxml_set_external_entity_loader_basic.phpt [new file with mode: 0644]
ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt [new file with mode: 0644]
ext/libxml/tests/libxml_set_external_entity_loader_variation1.phpt [new file with mode: 0644]
ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt [new file with mode: 0644]

index 9e6200ce1512be5b09444069e10fc7838fb0b561..3fb307b078a21618e31771ad41331ab6dde411c8 100755 (executable)
--- a/UPGRADING
+++ b/UPGRADING
@@ -415,6 +415,9 @@ UPGRADE NOTES - PHP X.Y
          - ldap_control_paged_results()
          - ldap_control_paged_results_response()
 
+       - libxml
+            - libxml_set_external_entity_loader()
+
      f. New global constants
 
        - JSON_PRETTY_PRINT
index 25988a6d6014cbc1e5c3f881ed53cadc49b7dc14..9863043b5da880f54130370fdf348e3f637e97f3 100644 (file)
@@ -70,6 +70,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;
@@ -111,6 +112,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 */
@@ -121,6 +125,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
 };
 
@@ -263,6 +268,19 @@ static PHP_GINIT_FUNCTION(libxml)
        libxml_globals->stream_context = NULL;
        libxml_globals->error_buffer.c = NULL;
        libxml_globals->error_list = NULL;
+       libxml_globals->defaultEntityLoader = 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.
@@ -280,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 {
@@ -669,7 +688,7 @@ static PHP_MINIT_FUNCTION(libxml)
                xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
                xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
        }
-
+       
        return SUCCESS;
 }
 
@@ -723,6 +742,8 @@ static PHP_RSHUTDOWN_FUNCTION(libxml)
                LIBXML(error_list) = NULL;
        }
        xmlResetLastError();
+       
+       _php_libxml_destroy_fci(&LIBXML(entity_loader).fci);
 
        return SUCCESS;
 }
@@ -905,6 +926,156 @@ static PHP_FUNCTION(libxml_disable_entity_loader)
 }
 /* }}} */
 
+static xmlParserInputPtr _php_libxml_user_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;
+       TSRMLS_FETCH();
+       zend_fcall_info         *fci = &LIBXML(entity_loader).fci;
+       
+       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;
+}
+
+/* {{{ 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;
+       }
+       
+       if (fci.size > 0) { /* argument not null */
+               /* save for later invocations with NULL */
+               if (LIBXML(defaultEntityLoader) == NULL) {
+                       LIBXML(defaultEntityLoader) = xmlGetExternalEntityLoader();
+               }
+               _php_libxml_destroy_fci(&LIBXML(entity_loader).fci);
+               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;
+               xmlSetExternalEntityLoader(_php_libxml_user_entity_loader);
+       } else {
+               xmlSetExternalEntityLoader(LIBXML(defaultEntityLoader));
+       }
+       
+       RETURN_TRUE;
+}
+/* }}} */
+
 /* {{{ Common functions shared by extensions */
 int php_libxml_xmlCheckUTF8(const unsigned char *s)
 {
index fca992e22b6861990cc38532c613c937732ab19b..8fe7fe4ad66a855f729d7f53ea7d5c9515274f1b 100644 (file)
@@ -43,6 +43,11 @@ ZEND_BEGIN_MODULE_GLOBALS(libxml)
        zval *stream_context;
        smart_str error_buffer;
        zend_llist *error_list;
+       xmlExternalEntityLoader defaultEntityLoader; /* saved here to allow it restored */
+       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 (file)
index 0000000..51ab777
--- /dev/null
@@ -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 (file)
index 0000000..5ed079d
--- /dev/null
@@ -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 (file)
index 0000000..1bb88d6
--- /dev/null
@@ -0,0 +1,71 @@
+--TEST--
+libxml_set_external_entity_loader() variation: resolve externals and entities
+--SKIPIF--
+<?php if (!extension_loaded('dom')) die('skip'); ?>
+--FILE--
+<?php
+$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(36) "%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(36) "%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 (file)
index 0000000..bec04aa
--- /dev/null
@@ -0,0 +1,44 @@
+--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
+$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(46) "%sfoobar.dtd"
+
+Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d
+bool(false)
+bool(true)
+Done.