Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.29

- CLI:
. Fixed built-in server rejecting long request paths, or paths received in
several pieces, as malformed. (Ilia Alshanetsky)

- Core:
. Fixed AST printing of interface extends list as implements. (Jorg Sowa)
. Fixed AST printing dropping fully qualified and namespace-relative prefixes
Expand Down
27 changes: 17 additions & 10 deletions sapi/cli/php_cli_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -1624,21 +1624,22 @@ static void normalize_vpath(char **retval, size_t *retval_len, const char *vpath
/* {{{ php_cli_server_client_read_request */
static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser)
{
return 0;
php_cli_server_client *client = parser->data;
return client->request_read;
}

static int php_cli_server_client_read_request_on_path(php_http_parser *parser, const char *at, size_t length)
{
php_cli_server_client *client = parser->data;
{
char *vpath;
size_t vpath_len;
if (UNEXPECTED(client->request.vpath != NULL)) {
return 1;
}
normalize_vpath(&vpath, &vpath_len, at, length, 1);
client->request.vpath = vpath;
client->request.vpath_len = vpath_len;
if (EXPECTED(client->request.vpath == NULL)) {
client->request.vpath = pestrndup(at, length, true);
client->request.vpath_len = length;
} else {
ZEND_ASSERT(length <= PHP_HTTP_MAX_HEADER_SIZE && PHP_HTTP_MAX_HEADER_SIZE - length >= client->request.vpath_len);
client->request.vpath = perealloc(client->request.vpath, client->request.vpath_len + length + 1, true);
memcpy(client->request.vpath + client->request.vpath_len, at, length);
client->request.vpath_len += length;
client->request.vpath[client->request.vpath_len] = '\0';
}
return 0;
}
Expand Down Expand Up @@ -1822,6 +1823,12 @@ static int php_cli_server_client_read_request_on_message_complete(php_http_parse
{
php_cli_server_client *client = parser->data;
client->request.protocol_version = parser->http_major * 100 + parser->http_minor;
if (client->request.vpath) {
char *vpath = client->request.vpath;
normalize_vpath(&client->request.vpath, &client->request.vpath_len,
vpath, client->request.vpath_len, true);
pefree(vpath, true);
}
php_cli_server_request_translate_vpath(client->server, &client->request, client->server->document_root, client->server->document_root_len);
if (client->request.vpath) {
const char *vpath = client->request.vpath;
Expand Down
4 changes: 2 additions & 2 deletions sapi/cli/tests/bug73630a.phpt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
--TEST--
Bug #73630 (Built-in Weberver - overwrite $_SERVER['request_uri'])
--DESCRIPTION--
Check that too long paths result in invalid request
Check that paths exceeding the header size limit result in invalid request
--SKIPIF--
<?php
include "skipif.inc";
Expand All @@ -17,7 +17,7 @@ php_cli_server_start($code);

$host = PHP_CLI_SERVER_HOSTNAME;
$fp = php_cli_server_connect();
$path = "/" . str_repeat("x", 16400) . "//example.com";
$path = "/" . str_repeat("x", 81920) . "//example.com";
var_dump(file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "$path"));
?>
--EXPECTF--
Expand Down
72 changes: 72 additions & 0 deletions sapi/cli/tests/cli_server_fragmented_path.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--TEST--
CLI server normalizes complete paths spanning multiple reads
--SKIPIF--
<?php
include 'skipif.inc';
?>
--FILE--
<?php
include 'php_cli_server.inc';
php_cli_server_start(<<<'PHP'
echo $_SERVER['SCRIPT_NAME'], "\n", $_SERVER['QUERY_STRING'], "\n", $_SERVER['REQUEST_URI'];
PHP, null);

foreach ([
'unsplit' => ['/a/../%69ndex.php?check=ok'],
'percent escape' => ['/a/../%', '69ndex.php?check=ok'],
'percent digit' => ['/a/../%6', '9ndex.php?check=ok'],
'parent segment' => ['/a/.', './index.php?check=ok'],
'encoded parent segment' => ['/a/%2e%', '2e/index.php?check=ok'],
'encoded slash' => ['/a/..%2', 'findex.php?check=ok'],
'query boundary' => ['/index.php', '?check=', 'ok'],
'large path' => ['/' . str_repeat('./', 9000) . 'index.php?check=ok'],
] as $name => $parts) {
$socket = php_cli_server_connect();
fwrite($socket, 'GET ');
foreach ($parts as $i => $part) {
fwrite($socket, $part);
if ($i < count($parts) - 1) {
// Let the server process this fragment before sending the next one.
$control = php_cli_server_connect();
fwrite($control, "GET /index.php?control HTTP/1.0\r\n\r\n");
$response = stream_get_contents($control);
fclose($control);
if (!str_starts_with($response, 'HTTP/1.0 200 OK')) {
die("Control request failed\n");
}
}
}
fwrite($socket, " HTTP/1.0\r\n\r\n");
$response = stream_get_contents($socket);
fclose($socket);
$body = explode("\r\n\r\n", $response, 2)[1] ?? '';
echo $name, "\n";
var_dump(str_starts_with($response, 'HTTP/1.0 200 OK'));
var_dump($body === "/index.php\ncheck=ok\n" . implode('', $parts));
}
?>
--EXPECT--
unsplit
bool(true)
bool(true)
percent escape
bool(true)
bool(true)
percent digit
bool(true)
bool(true)
parent segment
bool(true)
bool(true)
encoded parent segment
bool(true)
bool(true)
encoded slash
bool(true)
bool(true)
query boundary
bool(true)
bool(true)
large path
bool(true)
bool(true)
25 changes: 25 additions & 0 deletions sapi/cli/tests/cli_server_pipelined_requests.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
CLI server does not merge pipelined requests into one request
--SKIPIF--
<?php
include 'skipif.inc';
?>
--FILE--
<?php
include 'php_cli_server.inc';
php_cli_server_start('echo "[", $_SERVER["REQUEST_URI"], "|", $_SERVER["QUERY_STRING"], "]";');

$socket = php_cli_server_connect();
fwrite($socket, "GET /first.php?one HTTP/1.1\r\nHost: localhost\r\n\r\n"
. "GET /second.php?two HTTP/1.1\r\nHost: localhost\r\n\r\n");
$response = stream_get_contents($socket);
fclose($socket);

preg_match_all('/\[([^]]*)\]/', $response, $matches);
var_dump(array_diff($matches[1], ['/first.php?one|one', '/second.php?two|two']));
var_dump(file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/third.php?three'));
?>
--EXPECT--
array(0) {
}
string(24) "[/third.php?three|three]"
Loading