Skip to content

Commit af46c3f

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix GH-20439: xml_set_default_handler() does not properly handle special characters in attributes when passing data to callback (#20453)
2 parents 27a2caa + f7c7948 commit af46c3f

File tree

3 files changed

+70
-77
lines changed

3 files changed

+70
-77
lines changed

ext/xml/compat.c

Lines changed: 24 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -37,36 +37,35 @@ qualify_namespace(XML_Parser parser, const xmlChar *name, const xmlChar *URI)
3737
}
3838
}
3939

40+
static void start_element_emit_default(XML_Parser parser)
41+
{
42+
if (parser->h_default) {
43+
/* Grammar does not allow embedded '<' and '>' in elements, so we can seek to the start and end positions.
44+
* Since the parser in the current mode mode is non-progressive, it contains the entire input. */
45+
const xmlChar *cur = parser->parser->input->cur;
46+
const xmlChar *end = cur;
47+
for (const xmlChar *base = parser->parser->input->base; cur > base && *cur != '<'; cur--);
48+
if (*end == '/') {
49+
/* BC: Keep split between start & end element.
50+
* TODO: In the future this could be aligned with expat and only emit a start event, or vice versa.
51+
* See gh20439_2.phpt */
52+
xmlChar *tmp = BAD_CAST estrndup((const char *) cur, end - cur + 1);
53+
tmp[end - cur] = '>';
54+
parser->h_default(parser->user, tmp, end - cur + 1);
55+
efree(tmp);
56+
} else {
57+
parser->h_default(parser->user, cur, end - cur + 1);
58+
}
59+
}
60+
}
61+
4062
static void
4163
start_element_handler(void *user, const xmlChar *name, const xmlChar **attributes)
4264
{
4365
XML_Parser parser = (XML_Parser) user;
4466

4567
if (parser->h_start_element == NULL) {
46-
if (parser->h_default) {
47-
int attno = 0;
48-
smart_string qualified_name = {0};
49-
50-
smart_string_appendc(&qualified_name, '<');
51-
smart_string_appends(&qualified_name, (const char *) name);
52-
53-
if (attributes) {
54-
while (attributes[attno] != NULL) {
55-
const char *att_name = (const char *) attributes[attno++];
56-
const char *att_value = (const char *) attributes[attno++];
57-
58-
smart_string_appendc(&qualified_name, ' ');
59-
smart_string_appends(&qualified_name, att_name);
60-
smart_string_appends(&qualified_name, "=\"");
61-
smart_string_appends(&qualified_name, att_value);
62-
smart_string_appendc(&qualified_name, '"');
63-
}
64-
}
65-
smart_string_appendc(&qualified_name, '>');
66-
smart_string_0(&qualified_name);
67-
parser->h_default(parser->user, (const XML_Char *) qualified_name.c, qualified_name.len);
68-
smart_string_free(&qualified_name);
69-
}
68+
start_element_emit_default(parser);
7069
return;
7170
}
7271

@@ -92,59 +91,7 @@ start_element_handler_ns(void *user, const xmlChar *name, const xmlChar *prefix,
9291
}
9392

9493
if (parser->h_start_element == NULL) {
95-
if (parser->h_default) {
96-
smart_string qualified_name = {0};
97-
smart_string_appendc(&qualified_name, '<');
98-
if (prefix) {
99-
smart_string_appends(&qualified_name, (const char *) prefix);
100-
smart_string_appendc(&qualified_name, ':');
101-
}
102-
smart_string_appends(&qualified_name, (const char *) name);
103-
104-
if (namespaces) {
105-
int i, j;
106-
for (i = 0,j = 0;j < nb_namespaces;j++) {
107-
const char *ns_prefix = (const char *) namespaces[i++];
108-
const char *ns_url = (const char *) namespaces[i++];
109-
110-
if (ns_prefix) {
111-
smart_string_appends(&qualified_name, " xmlns:");
112-
smart_string_appends(&qualified_name, ns_prefix);
113-
smart_string_appends(&qualified_name, "=\"");
114-
} else {
115-
smart_string_appends(&qualified_name, " xmlns=\"");
116-
}
117-
118-
smart_string_appends(&qualified_name, ns_url);
119-
smart_string_appendc(&qualified_name, '"');
120-
}
121-
}
122-
123-
if (attributes) {
124-
for (i = 0; i < nb_attributes; i += 1) {
125-
const char *att_name = (const char *) attributes[y++];
126-
const char *att_prefix = (const char *)attributes[y++];
127-
y++;
128-
const char *att_value = (const char *)attributes[y++];
129-
const char *att_valueend = (const char *)attributes[y++];
130-
131-
smart_string_appendc(&qualified_name, ' ');
132-
if (att_prefix) {
133-
smart_string_appends(&qualified_name, att_prefix);
134-
smart_string_appendc(&qualified_name, ':');
135-
}
136-
smart_string_appends(&qualified_name, att_name);
137-
smart_string_appends(&qualified_name, "=\"");
138-
139-
smart_string_appendl(&qualified_name, att_value, att_valueend - att_value);
140-
smart_string_appendc(&qualified_name, '"');
141-
}
142-
143-
}
144-
smart_string_appendc(&qualified_name, '>');
145-
parser->h_default(parser->user, (const XML_Char *) qualified_name.c, qualified_name.len);
146-
smart_string_free(&qualified_name);
147-
}
94+
start_element_emit_default(parser);
14895
return;
14996
}
15097
qualified_name = qualify_namespace(parser, name, URI);

ext/xml/tests/gh20439_1.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
GH-20439 (xml_set_default_handler() does not properly handle special characters in attributes when passing data to callback)
3+
--EXTENSIONS--
4+
xml
5+
--FILE--
6+
<?php
7+
8+
$x = xml_parser_create_ns('utf-8');
9+
xml_set_default_handler($x, function( $_parser, $data ) { var_dump($data); });
10+
11+
$input = "<!-- xxx --><foo attr1='\"&lt;&quot;&#9;&#x0A;&#x0D;&#13;𐍅' attr2=\"&quot;&lt;\"></foo>";
12+
$inputs = str_split($input);
13+
14+
// Test chunked parser wrt non-progressive parser
15+
foreach ($inputs as $input) {
16+
xml_parse($x, $input, false);
17+
}
18+
xml_parse($x, "", true);
19+
20+
?>
21+
--EXPECT--
22+
string(12) "<!-- xxx -->"
23+
string(71) "<foo attr1='"&lt;&quot;&#9;&#x0A;&#x0D;&#13;𐍅' attr2="&quot;&lt;">"
24+
string(6) "</foo>"

ext/xml/tests/gh20439_2.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-20439 (xml_set_default_handler() does not properly handle special characters in attributes when passing data to callback) - closing solidus variant
3+
--EXTENSIONS--
4+
xml
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/libxml_expat_skipif.inc';
8+
skipif(want_expat: false);
9+
?>
10+
--FILE--
11+
<?php
12+
13+
$x = xml_parser_create_ns('utf-8');
14+
xml_set_default_handler($x, function( $_parser, $data ) { var_dump($data); });
15+
16+
$input = "<ns:test xmlns:ns='urn:x' />";
17+
xml_parse($x, $input, true);
18+
19+
?>
20+
--EXPECT--
21+
string(29) "<ns:test xmlns:ns='urn:x' >"
22+
string(10) "</ns:test>"

0 commit comments

Comments
 (0)