Skip to content

Commit f7c7948

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

File tree

4 files changed

+74
-82
lines changed

4 files changed

+74
-82
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ PHP NEWS
2424
- Standard:
2525
. Fix memory leak in array_diff() with custom type checks. (ndossche)
2626

27+
- XML:
28+
. Fixed bug GH-20439 (xml_set_default_handler() does not properly handle
29+
special characters in attributes when passing data to callback). (ndossche)
30+
2731
06 Nov 2025, PHP 8.5.0RC4
2832

2933
- Core:

ext/xml/compat.c

Lines changed: 24 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,36 @@ qualify_namespace(XML_Parser parser, const xmlChar *name, const xmlChar *URI, xm
3434
}
3535
}
3636

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

4365
if (parser->h_start_element == NULL) {
44-
if (parser->h_default) {
45-
int attno = 0;
46-
47-
qualified_name = xmlStrncatNew((xmlChar *)"<", name, xmlStrlen(name));
48-
if (attributes) {
49-
while (attributes[attno] != NULL) {
50-
int att_len;
51-
char *att_string, *att_name, *att_value;
52-
53-
att_name = (char *)attributes[attno++];
54-
att_value = (char *)attributes[attno++];
55-
56-
att_len = spprintf(&att_string, 0, " %s=\"%s\"", att_name, att_value);
57-
58-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)att_string, att_len);
59-
efree(att_string);
60-
}
61-
62-
}
63-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)">", 1);
64-
parser->h_default(parser->user, (const XML_Char *) qualified_name, xmlStrlen(qualified_name));
65-
xmlFree(qualified_name);
66-
}
66+
start_element_emit_default(parser);
6767
return;
6868
}
6969

@@ -89,65 +89,7 @@ start_element_handler_ns(void *user, const xmlChar *name, const xmlChar *prefix,
8989
}
9090

9191
if (parser->h_start_element == NULL) {
92-
if (parser->h_default) {
93-
94-
if (prefix) {
95-
qualified_name = xmlStrncatNew((xmlChar *)"<", prefix, xmlStrlen(prefix));
96-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)":", 1);
97-
qualified_name = xmlStrncat(qualified_name, name, xmlStrlen(name));
98-
} else {
99-
qualified_name = xmlStrncatNew((xmlChar *)"<", name, xmlStrlen(name));
100-
}
101-
102-
if (namespaces) {
103-
int i, j;
104-
for (i = 0,j = 0;j < nb_namespaces;j++) {
105-
int ns_len;
106-
char *ns_string, *ns_prefix, *ns_url;
107-
108-
ns_prefix = (char *) namespaces[i++];
109-
ns_url = (char *) namespaces[i++];
110-
111-
if (ns_prefix) {
112-
ns_len = spprintf(&ns_string, 0, " xmlns:%s=\"%s\"", ns_prefix, ns_url);
113-
} else {
114-
ns_len = spprintf(&ns_string, 0, " xmlns=\"%s\"", ns_url);
115-
}
116-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)ns_string, ns_len);
117-
118-
efree(ns_string);
119-
}
120-
}
121-
122-
if (attributes) {
123-
for (i = 0; i < nb_attributes; i += 1) {
124-
int att_len;
125-
char *att_string, *att_name, *att_value, *att_prefix, *att_valueend;
126-
127-
att_name = (char *) attributes[y++];
128-
att_prefix = (char *)attributes[y++];
129-
y++;
130-
att_value = (char *)attributes[y++];
131-
att_valueend = (char *)attributes[y++];
132-
133-
if (att_prefix) {
134-
att_len = spprintf(&att_string, 0, " %s:%s=\"", att_prefix, att_name);
135-
} else {
136-
att_len = spprintf(&att_string, 0, " %s=\"", att_name);
137-
}
138-
139-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)att_string, att_len);
140-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)att_value, att_valueend - att_value);
141-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)"\"", 1);
142-
143-
efree(att_string);
144-
}
145-
146-
}
147-
qualified_name = xmlStrncat(qualified_name, (xmlChar *)">", 1);
148-
parser->h_default(parser->user, (const XML_Char *) qualified_name, xmlStrlen(qualified_name));
149-
xmlFree(qualified_name);
150-
}
92+
start_element_emit_default(parser);
15193
return;
15294
}
15395
qualify_namespace(parser, name, URI, &qualified_name);

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)