Skip to content

Commit 584d38c

Browse files
committed
Add XMLDocument::createFromStream()
1 parent d987b6d commit 584d38c

7 files changed

+206
-59
lines changed

ext/dom/document.c

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,17 @@ const char *dom_get_valid_file_path(const char *source, char *resolved_path, int
13451345
}
13461346
/* }}} */
13471347

1348-
xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source, size_t source_len, size_t options, xmlCharEncodingHandlerPtr encoding) /* {{{ */
1348+
static int dom_stream_read(void *context, char *buffer, int len)
1349+
{
1350+
zend_resource *resource = context;
1351+
if (EXPECTED(resource->ptr)) {
1352+
php_stream *stream = resource->ptr;
1353+
return php_stream_read(stream, buffer, len);
1354+
}
1355+
return -1;
1356+
}
1357+
1358+
xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, dom_source_union source, size_t options, xmlCharEncodingHandlerPtr encoding, const char *override_document_uri) /* {{{ */
13491359
{
13501360
xmlDocPtr ret;
13511361
xmlParserCtxtPtr ctxt = NULL;
@@ -1371,16 +1381,18 @@ xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source,
13711381
xmlInitParser();
13721382

13731383
if (mode == DOM_LOAD_FILE) {
1374-
if (CHECK_NULL_PATH(source, source_len)) {
1384+
if (CHECK_NULL_PATH(source.str, source.str_len)) {
13751385
zend_argument_value_error(1, "must not contain any null bytes");
13761386
return NULL;
13771387
}
1378-
const char *file_dest = dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
1388+
const char *file_dest = dom_get_valid_file_path(source.str, resolved_path, MAXPATHLEN);
13791389
if (file_dest) {
13801390
ctxt = xmlCreateFileParserCtxt(file_dest);
13811391
}
1392+
} else if (mode == DOM_LOAD_STRING) {
1393+
ctxt = xmlCreateMemoryParserCtxt(source.str, source.str_len);
13821394
} else {
1383-
ctxt = xmlCreateMemoryParserCtxt(source, source_len);
1395+
ctxt = xmlCreateIOParserCtxt(NULL, NULL, dom_stream_read, NULL, source.stream->res, XML_CHAR_ENCODING_NONE);
13841396
}
13851397

13861398
if (ctxt == NULL) {
@@ -1393,7 +1405,7 @@ xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source,
13931405
}
13941406

13951407
/* If loading from memory, we need to set the base directory for the document */
1396-
if (mode != DOM_LOAD_FILE) {
1408+
if (mode == DOM_LOAD_STRING) {
13971409
#ifdef HAVE_GETCWD
13981410
directory = VCWD_GETCWD(resolved_path, MAXPATHLEN);
13991411
#elif defined(HAVE_GETWD)
@@ -1410,6 +1422,11 @@ xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source,
14101422
}
14111423
ctxt->directory = (char *) xmlCanonicPath((const xmlChar *) resolved_path);
14121424
}
1425+
} else if (override_document_uri) {
1426+
if(ctxt->directory != NULL) {
1427+
xmlFree(ctxt->directory);
1428+
}
1429+
ctxt->directory = (char *) xmlCanonicPath((const xmlChar *) override_document_uri);
14131430
}
14141431

14151432
ctxt->vctxt.error = php_libxml_ctx_error;
@@ -1507,21 +1524,20 @@ static void php_dom_finish_loading_document(zval *this, zval *return_value, xmlD
15071524
RETURN_TRUE;
15081525
}
15091526

1510-
static void dom_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode)
1527+
static void dom_legacy_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode)
15111528
{
1512-
char *source;
1513-
size_t source_len;
1529+
dom_source_union source;
15141530
zend_long options = 0;
15151531

1516-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &source, &source_len, &options) == FAILURE) {
1532+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &source.str, &source.str_len, &options) == FAILURE) {
15171533
RETURN_THROWS();
15181534
}
15191535

1520-
if (!source_len) {
1536+
if (!source.str_len) {
15211537
zend_argument_must_not_be_empty_error(1);
15221538
RETURN_THROWS();
15231539
}
1524-
if (ZEND_SIZE_T_INT_OVFL(source_len)) {
1540+
if (ZEND_SIZE_T_INT_OVFL(source.str_len)) {
15251541
php_error_docref(NULL, E_WARNING, "Input string is too long");
15261542
RETURN_FALSE;
15271543
}
@@ -1530,7 +1546,7 @@ static void dom_parse_document(INTERNAL_FUNCTION_PARAMETERS, int mode)
15301546
RETURN_FALSE;
15311547
}
15321548

1533-
xmlDocPtr newdoc = dom_document_parser(ZEND_THIS, mode, source, source_len, options, NULL);
1549+
xmlDocPtr newdoc = dom_document_parser(ZEND_THIS, mode, source, options, NULL, NULL);
15341550
if (newdoc == DOM_DOCUMENT_MALFORMED) {
15351551
newdoc = NULL;
15361552
}
@@ -1542,7 +1558,7 @@ Since: DOM Level 3
15421558
*/
15431559
PHP_METHOD(DOMDocument, load)
15441560
{
1545-
dom_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
1561+
dom_legacy_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_FILE);
15461562
}
15471563
/* }}} end dom_document_load */
15481564

@@ -1551,7 +1567,7 @@ Since: DOM Level 3
15511567
*/
15521568
PHP_METHOD(DOMDocument, loadXML)
15531569
{
1554-
dom_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
1570+
dom_legacy_parse_document(INTERNAL_FUNCTION_PARAM_PASSTHRU, DOM_LOAD_STRING);
15551571
}
15561572
/* }}} end dom_document_loadxml */
15571573

ext/dom/php_dom.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,22 @@ void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document
179179
void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document);
180180

181181
typedef enum {
182-
DOM_LOAD_STRING = 0,
183-
DOM_LOAD_FILE = 1,
182+
DOM_LOAD_STRING,
183+
DOM_LOAD_FILE,
184+
DOM_LOAD_STREAM,
184185
} dom_load_mode;
185186

187+
typedef union {
188+
struct {
189+
const char *str;
190+
size_t str_len;
191+
};
192+
php_stream *stream;
193+
} dom_source_union;
194+
186195
#define DOM_DOCUMENT_MALFORMED ((xmlDocPtr) -1)
187196

188-
xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, const char *source, size_t source_len, size_t options, xmlCharEncodingHandlerPtr encoding);
197+
xmlDocPtr dom_document_parser(zval *id, dom_load_mode mode, dom_source_union source, size_t options, xmlCharEncodingHandlerPtr encoding, const char *override_document_uri);
189198

190199
/* parentnode */
191200
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);

ext/dom/php_dom.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,9 @@ public static function createEmpty(string $version = "1.0", string $encoding = "
20682068

20692069
public static function createFromFile(string $path, int $options = 0, ?string $overrideEncoding = null): XMLDocument {}
20702070

2071+
/** @param resource $stream */
2072+
public static function createFromStream($stream, ?string $documentURI = null, int $options = 0, ?string $overrideEncoding = null): XMLDocument {}
2073+
20712074
public static function createFromString(string $source, int $options = 0, ?string $overrideEncoding = null): XMLDocument {}
20722075

20732076
/**

ext/dom/php_dom_arginfo.h

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Dom\XMLDocument::createFromStream() - from memory
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$tmp = fopen("php://memory", "r+");
9+
fwrite($tmp, "<root/>");
10+
rewind($tmp);
11+
$dom1 = Dom\XMLDocument::createFromStream($tmp);
12+
rewind($tmp);
13+
$dom2 = Dom\XMLDocument::createFromStream($tmp, "http://example.com");
14+
fclose($tmp);
15+
16+
var_dump($dom1->documentURI);
17+
var_dump($dom2->documentURI);
18+
19+
echo $dom1->saveXml(), "\n";
20+
echo $dom2->saveXml(), "\n";
21+
22+
?>
23+
--EXPECT--
24+
string(11) "about:blank"
25+
string(18) "http://example.com"
26+
<?xml version="1.0" encoding="UTF-8"?>
27+
<root/>
28+
<?xml version="1.0" encoding="UTF-8"?>
29+
<root/>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Dom\HTMLDocument::createFromStream() - broken stream
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
class MyStream {
9+
public $context;
10+
private bool $first = true;
11+
12+
public function stream_read(int $count): string|false {
13+
if ($this->first) {
14+
$this->first = false;
15+
return "<root><child>";
16+
}
17+
throw new Error("broken");
18+
}
19+
20+
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path) {
21+
return true;
22+
}
23+
24+
public function stream_close(): void {
25+
}
26+
27+
public function stream_eof(): bool {
28+
return !$this->first;
29+
}
30+
}
31+
32+
stream_wrapper_register("foo", MyStream::class);
33+
34+
$tmp = fopen("foo://", "r+");
35+
try {
36+
$dom = Dom\XMLDocument::createFromStream($tmp);
37+
} catch (Error $e) {
38+
echo $e->getMessage(), "\n";
39+
}
40+
fclose($tmp);
41+
42+
?>
43+
--EXPECT--
44+
broken

0 commit comments

Comments
 (0)