diff --git a/ext/standard/tests/http/http_auto_decode.phpt b/ext/standard/tests/http/http_auto_decode.phpt new file mode 100644 index 000000000000..c1fa2a64011e --- /dev/null +++ b/ext/standard/tests/http/http_auto_decode.phpt @@ -0,0 +1,61 @@ +--TEST-- +http.auto_decode stream context option controls chunked response decoding +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses); + +function test_auto_decode($auto_decode) { + global $uri; + $ctx = null; + if ($auto_decode !== null) { + $ctx = stream_context_create(['http' => ['auto_decode' => $auto_decode]]); + } + $body = file_get_contents($uri, false, $ctx); + + $has_te = false; + foreach (http_get_last_response_headers() as $h) { + if (stripos($h, 'Transfer-Encoding:') === 0) { + $has_te = true; + break; + } + } + + return [addcslashes($body, "\r\n"), $has_te]; +} + +var_dump(test_auto_decode(null)); +var_dump(test_auto_decode(true)); +var_dump(test_auto_decode(false)); + +http_server_kill($pid); +?> +--EXPECT-- +array(2) { + [0]=> + string(4) "abcd" + [1]=> + bool(false) +} +array(2) { + [0]=> + string(4) "abcd" + [1]=> + bool(false) +} +array(2) { + [0]=> + string(31) "2\r\nab\r\n2\r\ncd\r\n0\r\n\r\n" + [1]=> + bool(true) +} diff --git a/ext/standard/tests/http/http_location_realloc.phpt b/ext/standard/tests/http/http_location_realloc.phpt new file mode 100644 index 000000000000..df4e1057c337 --- /dev/null +++ b/ext/standard/tests/http/http_location_realloc.phpt @@ -0,0 +1,35 @@ +--TEST-- +HTTP response with two Location headers (second longer) triggers erealloc +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$ctx = stream_context_create(['http' => ['follow_location' => 0]]); +$result = file_get_contents("$uri/", false, $ctx); +var_dump($result); +var_dump(http_get_last_response_headers()); + +http_server_kill($pid); +?> +--EXPECT-- +string(0) "" +array(4) { + [0]=> + string(30) "HTTP/1.1 301 Moved Permanently" + [1]=> + string(16) "Location: /short" + [2]=> + string(40) "Location: /a_much_longer_path_than_short" + [3]=> + string(17) "Content-Length: 0" +} diff --git a/ext/standard/tests/http/http_proxy_auth_removed.phpt b/ext/standard/tests/http/http_proxy_auth_removed.phpt new file mode 100644 index 000000000000..5c541e299505 --- /dev/null +++ b/ext/standard/tests/http/http_proxy_auth_removed.phpt @@ -0,0 +1,72 @@ +--TEST-- +Proxy-Authorization header removed from request after CONNECT tunnel +--EXTENSIONS-- +openssl +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + [ + 'proxy' => "tcp://$host:$port", + 'header' => [ + "Proxy-Authorization: Basic Zm9vOmJhcg==", + ], + ], + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], +]); + +file_get_contents("https://www.php.net/", false, $ctx); + +http_server_kill($server['pid']); +?> +--EXPECT-- +CONNECT contains Proxy-Authorization: bool(true) +Proxied request contains Proxy-Authorization: bool(false) diff --git a/ext/standard/tests/http/http_proxy_ssl_connect_01.phpt b/ext/standard/tests/http/http_proxy_ssl_connect_01.phpt new file mode 100644 index 000000000000..0d647b79d02f --- /dev/null +++ b/ext/standard/tests/http/http_proxy_ssl_connect_01.phpt @@ -0,0 +1,48 @@ +--TEST-- +HTTP proxy SSL CONNECT with Proxy-Authorization header (string, multi-line) +--EXTENSIONS-- +openssl +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$host = parse_url($uri, PHP_URL_HOST); +$port = parse_url($uri, PHP_URL_PORT); + +$ctx = stream_context_create([ + 'http' => [ + 'proxy' => "tcp://$host:$port", + 'header' => "X-Custom: test\r\nProxy-Authorization: Basic dXNlcjpwYXNz", + ], + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], +]); +@$result = file_get_contents("https://www.php.net/test", false, $ctx); +var_dump($result); + +http_server_kill($pid); + +rewind($output); +$request = stream_get_contents($output); +var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0')); +var_dump(str_contains($request, 'Proxy-Authorization: Basic dXNlcjpwYXNz')); +var_dump(str_contains($request, 'X-Custom: test')); +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(false) diff --git a/ext/standard/tests/http/http_proxy_ssl_connect_02.phpt b/ext/standard/tests/http/http_proxy_ssl_connect_02.phpt new file mode 100644 index 000000000000..bff063e2a0ae --- /dev/null +++ b/ext/standard/tests/http/http_proxy_ssl_connect_02.phpt @@ -0,0 +1,46 @@ +--TEST-- +HTTP proxy SSL CONNECT without Proxy-Authorization header (FAILURE path) +--EXTENSIONS-- +openssl +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$host = parse_url($uri, PHP_URL_HOST); +$port = parse_url($uri, PHP_URL_PORT); + +$ctx = stream_context_create([ + 'http' => [ + 'proxy' => "tcp://$host:$port", + 'header' => "X-Custom: test\r\nX-Other: value", + ], + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], +]); +@$result = file_get_contents("https://www.php.net/test", false, $ctx); +var_dump($result); + +http_server_kill($pid); + +rewind($output); +$request = stream_get_contents($output); +var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0')); +var_dump(str_contains($request, 'Proxy-Authorization')); +?> +--EXPECT-- +bool(false) +bool(true) +bool(false) diff --git a/ext/standard/tests/http/http_proxy_ssl_connect_03.phpt b/ext/standard/tests/http/http_proxy_ssl_connect_03.phpt new file mode 100644 index 000000000000..90a19798f6df --- /dev/null +++ b/ext/standard/tests/http/http_proxy_ssl_connect_03.phpt @@ -0,0 +1,48 @@ +--TEST-- +HTTP proxy SSL CONNECT with Proxy-Authorization header (array) +--EXTENSIONS-- +openssl +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$host = parse_url($uri, PHP_URL_HOST); +$port = parse_url($uri, PHP_URL_PORT); + +$ctx = stream_context_create([ + 'http' => [ + 'proxy' => "tcp://$host:$port", + 'header' => ["X-Custom: test", "Proxy-Authorization: Basic abc123"], + ], + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], +]); +@$result = file_get_contents("https://www.php.net/test", false, $ctx); +var_dump($result); + +http_server_kill($pid); + +rewind($output); +$request = stream_get_contents($output); +var_dump(str_contains($request, 'CONNECT www.php.net:443 HTTP/1.0')); +var_dump(str_contains($request, 'Proxy-Authorization: Basic abc123')); +var_dump(str_contains($request, 'X-Custom')); +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(false) diff --git a/ext/standard/tests/http/http_redirect_removes_post_headers.phpt b/ext/standard/tests/http/http_redirect_removes_post_headers.phpt new file mode 100644 index 000000000000..6e0927016ec6 --- /dev/null +++ b/ext/standard/tests/http/http_redirect_removes_post_headers.phpt @@ -0,0 +1,74 @@ +--TEST-- +POST Content-Type and Content-Length headers removed on redirect except for 307/308 +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + [ + 'method' => 'POST', + 'content' => 'test=data', + 'follow_location' => 1, + 'max_redirects' => 3, + 'header' => + "Content-Type: application/x-www-form-urlencoded\r\n" . + "Content-Length: 9\r\n", + ], +]); + +foreach ($status_codes as $code) { + file_get_contents($server['uri'], false, $context); +} + +http_server_kill($server['pid']); + +rewind($output); +$contents = stream_get_contents($output); + +foreach ($status_codes as $code) { + if (!preg_match("~(GET|POST) /$code-redirected .*?\r\n\r\n~s", $contents, $matches)) { + die("fail redirect request for $code not found\n"); + } + echo "Redirect request for $code has Content-Type: "; + var_dump(stripos($matches[0], 'content-type') !== false); + echo "Redirect request for $code has Content-Length: "; + var_dump(stripos($matches[0], 'content-length') !== false); +} + +?> +--EXPECT-- +Redirect request for 301 has Content-Type: bool(false) +Redirect request for 301 has Content-Length: bool(false) +Redirect request for 302 has Content-Type: bool(false) +Redirect request for 302 has Content-Length: bool(false) +Redirect request for 303 has Content-Type: bool(false) +Redirect request for 303 has Content-Length: bool(false) +Redirect request for 307 has Content-Type: bool(true) +Redirect request for 307 has Content-Length: bool(true) +Redirect request for 308 has Content-Type: bool(true) +Redirect request for 308 has Content-Length: bool(true) diff --git a/ext/standard/tests/http/http_relative_redirect.phpt b/ext/standard/tests/http/http_relative_redirect.phpt new file mode 100644 index 000000000000..9c3036da5a11 --- /dev/null +++ b/ext/standard/tests/http/http_relative_redirect.phpt @@ -0,0 +1,186 @@ +--TEST-- +http wrapper resolves a relative redirect Location against the request path +--DESCRIPTION-- +This test tests the current behavior, which is clearly not the correct behavior in some cases. +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +foreach ($uri_parsers as $uri_parser) { + echo "# URI parser: $uri_parser\n"; + foreach ($froms as $from) { + foreach ($tos as $to) { + ftruncate($output, 0); + + $ctx = stream_context_create(['http' => [ + 'follow_location' => true, + 'uri_parser_class' => $uri_parser, + ]]); + $body = @file_get_contents($uri . $from, false, $ctx); + rewind($output); + + if ($body === false) { + $result = "failed"; + // Remove request from responses queue + file_get_contents($uri); + } else { + $requests = stream_get_contents($output); + preg_match_all('~GET (.*) HTTP/1.~', $requests, $matches); + $result = $matches[1][1]; + } + echo "Redirect from '$from' to '$to': $result\n"; + } + } +} + +http_server_kill($pid); +?> +--EXPECT-- +# URI parser: +Redirect from '/dir/page' to '/dir/page': /dir/page +Redirect from '/dir/page' to 'dir/page': /dir//dir/page +Redirect from '/dir/page' to 'a': /a +Redirect from '/dir/page' to 'other': /dir//other +Redirect from '/dir/page' to '': / +Redirect from '/dir/page' to '../../../foo': /dir//../../../foo +Redirect from '/dir/page' to 'space bar': /dir//space bar +Redirect from '/a' to '/dir/page': /dir/page +Redirect from '/a' to 'dir/page': /dir/page +Redirect from '/a' to 'a': /a +Redirect from '/a' to 'other': /other +Redirect from '/a' to '': / +Redirect from '/a' to '../../../foo': /../../../foo +Redirect from '/a' to 'space bar': /space bar +Redirect from '/other' to '/dir/page': /dir/page +Redirect from '/other' to 'dir/page': /dir/page +Redirect from '/other' to 'a': /a +Redirect from '/other' to 'other': /other +Redirect from '/other' to '': / +Redirect from '/other' to '../../../foo': /../../../foo +Redirect from '/other' to 'space bar': /space bar +Redirect from '' to '/dir/page': /dir/page +Redirect from '' to 'dir/page': /dir/page +Redirect from '' to 'a': /a +Redirect from '' to 'other': /other +Redirect from '' to '': / +Redirect from '' to '../../../foo': /../../../foo +Redirect from '' to 'space bar': /space bar +Redirect from '/../../../foo' to '/dir/page': /dir/page +Redirect from '/../../../foo' to 'dir/page': /../../..//dir/page +Redirect from '/../../../foo' to 'a': /a +Redirect from '/../../../foo' to 'other': /../../..//other +Redirect from '/../../../foo' to '': / +Redirect from '/../../../foo' to '../../../foo': /../../..//../../../foo +Redirect from '/../../../foo' to 'space bar': /../../..//space bar +Redirect from '/space bar' to '/dir/page': /dir/page +Redirect from '/space bar' to 'dir/page': /dir/page +Redirect from '/space bar' to 'a': /a +Redirect from '/space bar' to 'other': /other +Redirect from '/space bar' to '': / +Redirect from '/space bar' to '../../../foo': /../../../foo +Redirect from '/space bar' to 'space bar': /space bar +# URI parser: Uri\Rfc3986\Uri +Redirect from '/dir/page' to '/dir/page': /dir/page +Redirect from '/dir/page' to 'dir/page': /dir//dir/page +Redirect from '/dir/page' to 'a': /a +Redirect from '/dir/page' to 'other': /dir//other +Redirect from '/dir/page' to '': / +Redirect from '/dir/page' to '../../../foo': /dir//../../../foo +Redirect from '/dir/page' to 'space bar': failed +Redirect from '/a' to '/dir/page': /dir/page +Redirect from '/a' to 'dir/page': /dir/page +Redirect from '/a' to 'a': /a +Redirect from '/a' to 'other': /other +Redirect from '/a' to '': / +Redirect from '/a' to '../../../foo': /../../../foo +Redirect from '/a' to 'space bar': failed +Redirect from '/other' to '/dir/page': /dir/page +Redirect from '/other' to 'dir/page': /dir/page +Redirect from '/other' to 'a': /a +Redirect from '/other' to 'other': /other +Redirect from '/other' to '': / +Redirect from '/other' to '../../../foo': /../../../foo +Redirect from '/other' to 'space bar': failed +Redirect from '' to '/dir/page': /dir/page +Redirect from '' to 'dir/page': /dir/page +Redirect from '' to 'a': /a +Redirect from '' to 'other': /other +Redirect from '' to '': / +Redirect from '' to '../../../foo': /../../../foo +Redirect from '' to 'space bar': failed +Redirect from '/../../../foo' to '/dir/page': /dir/page +Redirect from '/../../../foo' to 'dir/page': /../../..//dir/page +Redirect from '/../../../foo' to 'a': /a +Redirect from '/../../../foo' to 'other': /../../..//other +Redirect from '/../../../foo' to '': / +Redirect from '/../../../foo' to '../../../foo': /../../..//../../../foo +Redirect from '/../../../foo' to 'space bar': failed +Redirect from '/space bar' to '/dir/page': failed +Redirect from '/space bar' to 'dir/page': failed +Redirect from '/space bar' to 'a': failed +Redirect from '/space bar' to 'other': failed +Redirect from '/space bar' to '': failed +Redirect from '/space bar' to '../../../foo': failed +Redirect from '/space bar' to 'space bar': failed +# URI parser: Uri\WhatWg\Url +Redirect from '/dir/page' to '/dir/page': /dir/page +Redirect from '/dir/page' to 'dir/page': /dir//dir/page +Redirect from '/dir/page' to 'a': /a +Redirect from '/dir/page' to 'other': /dir//other +Redirect from '/dir/page' to '': / +Redirect from '/dir/page' to '../../../foo': /foo +Redirect from '/dir/page' to 'space bar': /dir//space%20bar +Redirect from '/a' to '/dir/page': /dir/page +Redirect from '/a' to 'dir/page': /dir/page +Redirect from '/a' to 'a': /a +Redirect from '/a' to 'other': /other +Redirect from '/a' to '': / +Redirect from '/a' to '../../../foo': /foo +Redirect from '/a' to 'space bar': /space%20bar +Redirect from '/other' to '/dir/page': /dir/page +Redirect from '/other' to 'dir/page': /dir/page +Redirect from '/other' to 'a': /a +Redirect from '/other' to 'other': /other +Redirect from '/other' to '': / +Redirect from '/other' to '../../../foo': /foo +Redirect from '/other' to 'space bar': /space%20bar +Redirect from '' to '/dir/page': /dir/page +Redirect from '' to 'dir/page': /dir/page +Redirect from '' to 'a': /a +Redirect from '' to 'other': /other +Redirect from '' to '': / +Redirect from '' to '../../../foo': /foo +Redirect from '' to 'space bar': /space%20bar +Redirect from '/../../../foo' to '/dir/page': /dir/page +Redirect from '/../../../foo' to 'dir/page': /dir/page +Redirect from '/../../../foo' to 'a': /a +Redirect from '/../../../foo' to 'other': /other +Redirect from '/../../../foo' to '': / +Redirect from '/../../../foo' to '../../../foo': /foo +Redirect from '/../../../foo' to 'space bar': /space%20bar +Redirect from '/space bar' to '/dir/page': /dir/page +Redirect from '/space bar' to 'dir/page': /dir/page +Redirect from '/space bar' to 'a': /a +Redirect from '/space bar' to 'other': /other +Redirect from '/space bar' to '': / +Redirect from '/space bar' to '../../../foo': /foo +Redirect from '/space bar' to 'space bar': /space%20bar diff --git a/ext/standard/tests/http/http_sets_default_request_headers.phpt b/ext/standard/tests/http/http_sets_default_request_headers.phpt new file mode 100644 index 000000000000..c86cea6cfae1 --- /dev/null +++ b/ext/standard/tests/http/http_sets_default_request_headers.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test from and user-agent headers are set from ini and ctx +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +from=ini_from@php.net +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$ctx = stream_context_create([ + 'http' => [ + 'user_agent' => 'SomeAgent', + 'content' => 'payload', + ], +]); +file_get_contents($uri, false, $ctx); + +http_server_kill($pid); + +rewind($output); +$request = stream_get_contents($output); + +var_dump(str_contains($request, 'User-Agent: SomeAgent')); +var_dump(str_contains($request, 'From: ini_from@php.net')); +var_dump(str_contains($request, 'Content-Type: application/x-www-form-urlencoded')); +var_dump(str_contains($request, 'Content-Length: 7')); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/http/http_user_header_precedence.phpt b/ext/standard/tests/http/http_user_header_precedence.phpt new file mode 100644 index 000000000000..b5e562972bb4 --- /dev/null +++ b/ext/standard/tests/http/http_user_header_precedence.phpt @@ -0,0 +1,54 @@ +--TEST-- +Header precedence when headers are supplied in multiple ways +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +from=ini_from@example.com +--FILE-- + $pid, 'uri' => $uri] = http_server($responses, $output); + +$ctx = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "User-Agent: MyCustomUA\r\n" + . "From: me@example.com\r\n" + . "Content-Length: 5\r\n" + . "Content-Type: application/json\r\n" + . "X-Custom: test", + 'user_agent' => 'AutoGeneratedUA', + 'content' => '0123456789', + ], +]); +file_get_contents($uri, false, $ctx); + +http_server_kill($pid); + +rewind($output); +$request = stream_get_contents($output); + +var_dump(str_contains($request, 'User-Agent: MyCustomUA')); +var_dump(str_contains($request, 'AutoGeneratedUA')); +var_dump(substr_count($request, 'User-Agent:')); +var_dump(str_contains($request, 'From: me@example.com')); +var_dump(str_contains($request, 'ini_from@example.com')); +var_dump(substr_count($request, 'From:')); +var_dump(str_contains($request, 'Content-Length: 5')); +var_dump(substr_count($request, 'Content-Length:')); +?> +--EXPECT-- +bool(true) +bool(false) +int(1) +bool(true) +bool(false) +int(1) +bool(true) +int(1)