Skip to content

Commit 151a62d

Browse files
committed
ext/standard: http(s) wrapper corrupts the basic auth header on percent-encoded userinfo.
php_url_decode() returns the shorter decoded length but ZSTR_LEN() is left untouched, so smart_str_append() carries the stale [decoded][NUL][undecoded tail] bytes into the base64 credentials. Fix GH-22171
1 parent 7aa91a8 commit 151a62d

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
GH-22171 (http(s) stream wrapper sends a corrupted Authorization header for percent-encoded userinfo)
3+
--SKIPIF--
4+
<?php require 'server.inc'; http_server_skipif(); ?>
5+
--INI--
6+
allow_url_fopen=1
7+
--FILE--
8+
<?php
9+
require 'server.inc';
10+
11+
function probe(string $label, string $userinfo, string $expected): void
12+
{
13+
$responses = array(
14+
"data://text/plain,HTTP/1.0 200 Ok\r\n\r\n",
15+
);
16+
17+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
18+
19+
$url = preg_replace('#^http://#', 'http://' . $userinfo . '@', $uri);
20+
file_get_contents($url);
21+
22+
http_server_kill($pid);
23+
24+
fseek($output, 0, SEEK_SET);
25+
$output = stream_get_contents($output);
26+
27+
if (preg_match('/^Authorization:\s*Basic\s+(\S+)/mi', $output, $m)) {
28+
$decoded = base64_decode($m[1]);
29+
} else {
30+
$decoded = '<no Authorization header>';
31+
}
32+
33+
echo "=== {$label} ===", PHP_EOL;
34+
echo " decoded : ", addcslashes($decoded, "\0..\37"), PHP_EOL;
35+
echo " result : ", ($decoded === $expected ? "OK" : "CORRUPT"), PHP_EOL;
36+
}
37+
38+
probe('user only', '%76%6f%72%74%66%75', 'vortfu:');
39+
probe('user + password', '%76%6f%72%74%66%75:%70%61%73%73%77%6f%72%64', 'vortfu:password');
40+
?>
41+
--EXPECT--
42+
=== user only ===
43+
decoded : vortfu:
44+
result : OK
45+
=== user + password ===
46+
decoded : vortfu:password
47+
result : OK

0 commit comments

Comments
 (0)