Skip to content

Commit 51d26b3

Browse files
authored
ext/standard: add tests for HTTP fopen wrapper (#23557)
1 parent 1d82b6f commit 51d26b3

10 files changed

Lines changed: 664 additions & 0 deletions
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
http.auto_decode stream context option controls chunked response decoding
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+
$chunked = "data://text/plain,HTTP/1.1 200 OK\r\n"
12+
. "Transfer-Encoding: chunked\r\n\r\n"
13+
. "2\r\nab\r\n2\r\ncd\r\n0\r\n\r\n";
14+
15+
$responses = [$chunked, $chunked, $chunked];
16+
['pid' => $pid, 'uri' => $uri] = http_server($responses);
17+
18+
function test_auto_decode($auto_decode) {
19+
global $uri;
20+
$ctx = null;
21+
if ($auto_decode !== null) {
22+
$ctx = stream_context_create(['http' => ['auto_decode' => $auto_decode]]);
23+
}
24+
$body = file_get_contents($uri, false, $ctx);
25+
26+
$has_te = false;
27+
foreach (http_get_last_response_headers() as $h) {
28+
if (stripos($h, 'Transfer-Encoding:') === 0) {
29+
$has_te = true;
30+
break;
31+
}
32+
}
33+
34+
return [addcslashes($body, "\r\n"), $has_te];
35+
}
36+
37+
var_dump(test_auto_decode(null));
38+
var_dump(test_auto_decode(true));
39+
var_dump(test_auto_decode(false));
40+
41+
http_server_kill($pid);
42+
?>
43+
--EXPECT--
44+
array(2) {
45+
[0]=>
46+
string(4) "abcd"
47+
[1]=>
48+
bool(false)
49+
}
50+
array(2) {
51+
[0]=>
52+
string(4) "abcd"
53+
[1]=>
54+
bool(false)
55+
}
56+
array(2) {
57+
[0]=>
58+
string(31) "2\r\nab\r\n2\r\ncd\r\n0\r\n\r\n"
59+
[1]=>
60+
bool(true)
61+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
HTTP response with two Location headers (second longer) triggers erealloc
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+
$responses = array(
12+
"data://text/plain,HTTP/1.1 301 Moved Permanently\r\nLocation: /short\r\nLocation: /a_much_longer_path_than_short\r\nContent-Length: 0\r\n\r\n",
13+
);
14+
15+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
16+
17+
$ctx = stream_context_create(['http' => ['follow_location' => 0]]);
18+
$result = file_get_contents("$uri/", false, $ctx);
19+
var_dump($result);
20+
var_dump(http_get_last_response_headers());
21+
22+
http_server_kill($pid);
23+
?>
24+
--EXPECT--
25+
string(0) ""
26+
array(4) {
27+
[0]=>
28+
string(30) "HTTP/1.1 301 Moved Permanently"
29+
[1]=>
30+
string(16) "Location: /short"
31+
[2]=>
32+
string(40) "Location: /a_much_longer_path_than_short"
33+
[3]=>
34+
string(17) "Content-Length: 0"
35+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Proxy-Authorization header removed from request after CONNECT tunnel
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php require_once 'server.inc'; http_server_skipif(); ?>
7+
--INI--
8+
allow_url_fopen=1
9+
--FILE--
10+
<?php
11+
require_once 'server.inc';
12+
13+
$server = http_server_init($output);
14+
15+
if (is_resource($server)) {
16+
$conn = stream_socket_accept($server);
17+
18+
/* Read CONNECT request */
19+
$req = '';
20+
while (!str_contains($req, "\r\n\r\n")) {
21+
$req .= fread($conn, 1024);
22+
}
23+
24+
echo "CONNECT contains Proxy-Authorization: ";
25+
var_dump(stripos($req, 'Proxy-Authorization:') !== false);
26+
27+
fwrite($conn, "HTTP/1.1 200 Connection established\r\n\r\n");
28+
fflush($conn);
29+
30+
stream_context_set_option($conn, 'ssl', 'local_cert', __DIR__ . '/../../../openssl/tests/sni_server.pem');
31+
stream_socket_enable_crypto($conn, true, STREAM_CRYPTO_METHOD_TLS_SERVER) or die('fail TLS handshake');
32+
33+
/* Read tunneled request */
34+
$req2 = '';
35+
while (!str_contains($req2, "\r\n\r\n")) {
36+
$req2 .= fread($conn, 1024);
37+
}
38+
39+
/* Must be removed */
40+
echo "Proxied request contains Proxy-Authorization: ";
41+
var_dump(stripos($req2, 'Proxy-Authorization:') !== false);
42+
43+
fwrite($conn,
44+
"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
45+
);
46+
47+
exit;
48+
}
49+
50+
$host = parse_url($server['uri'], PHP_URL_HOST);
51+
$port = parse_url($server['uri'], PHP_URL_PORT);
52+
53+
$ctx = stream_context_create([
54+
'http' => [
55+
'proxy' => "tcp://$host:$port",
56+
'header' => [
57+
"Proxy-Authorization: Basic Zm9vOmJhcg==",
58+
],
59+
],
60+
'ssl' => [
61+
'verify_peer' => false,
62+
'verify_peer_name' => false,
63+
],
64+
]);
65+
66+
file_get_contents("https://www.php.net/", false, $ctx);
67+
68+
http_server_kill($server['pid']);
69+
?>
70+
--EXPECT--
71+
CONNECT contains Proxy-Authorization: bool(true)
72+
Proxied request contains Proxy-Authorization: bool(false)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
HTTP proxy SSL CONNECT with Proxy-Authorization header (string, multi-line)
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php require 'server.inc'; http_server_skipif(); ?>
7+
--INI--
8+
allow_url_fopen=1
9+
--FILE--
10+
<?php
11+
require 'server.inc';
12+
13+
$responses = array(
14+
"data://text/plain,HTTP/1.0 200 Connection established\r\n\r\n",
15+
"data://text/plain,",
16+
);
17+
18+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
19+
20+
$host = parse_url($uri, PHP_URL_HOST);
21+
$port = parse_url($uri, PHP_URL_PORT);
22+
23+
$ctx = stream_context_create([
24+
'http' => [
25+
'proxy' => "tcp://$host:$port",
26+
'header' => "X-Custom: test\r\nProxy-Authorization: Basic dXNlcjpwYXNz",
27+
],
28+
'ssl' => [
29+
'verify_peer' => false,
30+
'verify_peer_name' => false,
31+
],
32+
]);
33+
@$result = file_get_contents("https://www.php.net/test", false, $ctx);
34+
var_dump($result);
35+
36+
http_server_kill($pid);
37+
38+
rewind($output);
39+
$request = stream_get_contents($output);
40+
var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0'));
41+
var_dump(str_contains($request, 'Proxy-Authorization: Basic dXNlcjpwYXNz'));
42+
var_dump(str_contains($request, 'X-Custom: test'));
43+
?>
44+
--EXPECT--
45+
bool(false)
46+
bool(true)
47+
bool(true)
48+
bool(false)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
HTTP proxy SSL CONNECT without Proxy-Authorization header (FAILURE path)
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php require 'server.inc'; http_server_skipif(); ?>
7+
--INI--
8+
allow_url_fopen=1
9+
--FILE--
10+
<?php
11+
require 'server.inc';
12+
13+
$responses = array(
14+
"data://text/plain,HTTP/1.0 200 Connection established\r\n\r\n",
15+
"data://text/plain,",
16+
);
17+
18+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
19+
20+
$host = parse_url($uri, PHP_URL_HOST);
21+
$port = parse_url($uri, PHP_URL_PORT);
22+
23+
$ctx = stream_context_create([
24+
'http' => [
25+
'proxy' => "tcp://$host:$port",
26+
'header' => "X-Custom: test\r\nX-Other: value",
27+
],
28+
'ssl' => [
29+
'verify_peer' => false,
30+
'verify_peer_name' => false,
31+
],
32+
]);
33+
@$result = file_get_contents("https://www.php.net/test", false, $ctx);
34+
var_dump($result);
35+
36+
http_server_kill($pid);
37+
38+
rewind($output);
39+
$request = stream_get_contents($output);
40+
var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0'));
41+
var_dump(str_contains($request, 'Proxy-Authorization'));
42+
?>
43+
--EXPECT--
44+
bool(false)
45+
bool(true)
46+
bool(false)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
HTTP proxy SSL CONNECT with Proxy-Authorization header (array)
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php require 'server.inc'; http_server_skipif(); ?>
7+
--INI--
8+
allow_url_fopen=1
9+
--FILE--
10+
<?php
11+
require 'server.inc';
12+
13+
$responses = array(
14+
"data://text/plain,HTTP/1.0 200 Connection established\r\n\r\n",
15+
"data://text/plain,",
16+
);
17+
18+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
19+
20+
$host = parse_url($uri, PHP_URL_HOST);
21+
$port = parse_url($uri, PHP_URL_PORT);
22+
23+
$ctx = stream_context_create([
24+
'http' => [
25+
'proxy' => "tcp://$host:$port",
26+
'header' => ["X-Custom: test", "Proxy-Authorization: Basic abc123"],
27+
],
28+
'ssl' => [
29+
'verify_peer' => false,
30+
'verify_peer_name' => false,
31+
],
32+
]);
33+
@$result = file_get_contents("https://www.php.net/test", false, $ctx);
34+
var_dump($result);
35+
36+
http_server_kill($pid);
37+
38+
rewind($output);
39+
$request = stream_get_contents($output);
40+
var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0'));
41+
var_dump(str_contains($request, 'Proxy-Authorization: Basic abc123'));
42+
var_dump(str_contains($request, 'X-Custom'));
43+
?>
44+
--EXPECT--
45+
bool(false)
46+
bool(true)
47+
bool(true)
48+
bool(false)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
POST Content-Type and Content-Length headers removed on redirect except for 307/308
3+
--SKIPIF--
4+
<?php
5+
require_once 'server.inc';
6+
http_server_skipif();
7+
?>
8+
--INI--
9+
allow_url_fopen=1
10+
--FILE--
11+
<?php
12+
require_once 'server.inc';
13+
14+
$status_codes = [301, 302, 303, 307, 308];
15+
16+
$responses = [];
17+
foreach ($status_codes as $code) {
18+
$responses[] = "data://text/plain,"
19+
. "HTTP/1.1 $code Redirect\r\n"
20+
. "Location: /$code-redirected\r\n"
21+
. "Content-Length: 0\r\n"
22+
. "\r\n";
23+
24+
$responses[] = "data://text/plain,"
25+
. "HTTP/1.1 200 OK\r\n"
26+
. "Content-Length: 0\r\n"
27+
. "\r\n";
28+
}
29+
30+
$server = http_server($responses, $output);
31+
32+
$context = stream_context_create([
33+
'http' => [
34+
'method' => 'POST',
35+
'content' => 'test=data',
36+
'follow_location' => 1,
37+
'max_redirects' => 3,
38+
'header' =>
39+
"Content-Type: application/x-www-form-urlencoded\r\n" .
40+
"Content-Length: 9\r\n",
41+
],
42+
]);
43+
44+
foreach ($status_codes as $code) {
45+
file_get_contents($server['uri'], false, $context);
46+
}
47+
48+
http_server_kill($server['pid']);
49+
50+
rewind($output);
51+
$contents = stream_get_contents($output);
52+
53+
foreach ($status_codes as $code) {
54+
if (!preg_match("~(GET|POST) /$code-redirected .*?\r\n\r\n~s", $contents, $matches)) {
55+
die("fail redirect request for $code not found\n");
56+
}
57+
echo "Redirect request for $code has Content-Type: ";
58+
var_dump(stripos($matches[0], 'content-type') !== false);
59+
echo "Redirect request for $code has Content-Length: ";
60+
var_dump(stripos($matches[0], 'content-length') !== false);
61+
}
62+
63+
?>
64+
--EXPECT--
65+
Redirect request for 301 has Content-Type: bool(false)
66+
Redirect request for 301 has Content-Length: bool(false)
67+
Redirect request for 302 has Content-Type: bool(false)
68+
Redirect request for 302 has Content-Length: bool(false)
69+
Redirect request for 303 has Content-Type: bool(false)
70+
Redirect request for 303 has Content-Length: bool(false)
71+
Redirect request for 307 has Content-Type: bool(true)
72+
Redirect request for 307 has Content-Length: bool(true)
73+
Redirect request for 308 has Content-Type: bool(true)
74+
Redirect request for 308 has Content-Length: bool(true)

0 commit comments

Comments
 (0)