Skip to content
Merged
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
10 changes: 9 additions & 1 deletion bin/compile.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ function run(string $filename, string $code, array $options): void
$runtime = new Runtime(Runtime::MODE_AOT);
$queryString = $options['-q'] ?? null;
$postBody = $options['-p'] ?? null;
$scriptFilename = null;
if ('-' !== $filename && 'Command line code' !== $filename) {
$resolved = realpath($filename);
if (false !== $resolved) {
$scriptFilename = $resolved;
}
}
Superglobals::populateFromEnvironment(
$runtime->vmContext,
is_string($queryString) ? $queryString : null,
is_string($postBody) ? $postBody : null
is_string($postBody) ? $postBody : null,
$scriptFilename
);
$block = $runtime->parseAndCompile($code, $filename);
if (! isset($options['-l'])) {
Expand Down
10 changes: 9 additions & 1 deletion bin/vm.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ function run(string $filename, string $code, array $options): void
$runtime = new Runtime();
$queryString = $options['-q'] ?? null;
$postBody = $options['-p'] ?? null;
$scriptFilename = null;
if ('-' !== $filename && 'Command line code' !== $filename) {
$resolved = realpath($filename);
if (false !== $resolved) {
$scriptFilename = $resolved;
}
}
Superglobals::populateFromEnvironment(
$runtime->vmContext,
is_string($queryString) ? $queryString : null,
is_string($postBody) ? $postBody : null
is_string($postBody) ? $postBody : null,
$scriptFilename
);
$block = $runtime->parseAndCompile($code, $filename);
if (! isset($options['-l'])) {
Expand Down
14 changes: 7 additions & 7 deletions docs/bootstrap-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -1287,8 +1287,8 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `lib/JIT/SuperglobalInit.php`

**Warnings** (review for bootstrap subset):
- new Variable (line 121)
- new VMVariable (line 145)
- new Variable (line 123)
- new VMVariable (line 147)
- 7 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/ValueEchoHelper.php`
Expand Down Expand Up @@ -1501,11 +1501,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `lib/Web/Superglobals.php`

**Warnings** (review for bootstrap subset):
- new HashTable (line 411)
- new Variable (line 412)
- new Variable (line 431)
- new Variable (line 481)
- 22 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- new HashTable (line 420)
- new Variable (line 421)
- new Variable (line 440)
- new Variable (line 515)
- 23 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `src/macro_functions.php`

Expand Down
36 changes: 36 additions & 0 deletions lib/AOT/runtime/superglobals_refresh.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,37 @@ static void apply_scheme_and_port(__hashtable__ *server)
set_string_key(server, "SERVER_PORT", port_buf);
}

static void resolve_script_filename(
const char *script_name,
char *out,
size_t out_len
) {
const char *from_env = getenv("SCRIPT_FILENAME");

out[0] = '\0';
if (NULL != from_env && '\0' != from_env[0]) {
strncpy(out, from_env, out_len - 1);
out[out_len - 1] = '\0';

return;
}

{
const char *document_root = getenv("DOCUMENT_ROOT");
size_t root_len;

if (NULL == document_root || '\0' == document_root[0]
|| NULL == script_name || '\0' == script_name[0]) {
return;
}
root_len = strlen(document_root);
while (root_len > 0 && '/' == document_root[root_len - 1]) {
root_len--;
}
snprintf(out, out_len, "%.*s%s", (int) root_len, document_root, script_name);
}
}

static void derive_path_info(const char *script_name, const char *request_uri, char *out, size_t out_len)
{
char path_buf[1024];
Expand Down Expand Up @@ -481,6 +512,7 @@ void __superglobals__refresh(void)
const char *script_name = env_or_empty("SCRIPT_NAME");
const char *request_uri = getenv("REQUEST_URI");
char path_info[512];
char script_filename[1024];
char request_uri_buf[1024];

if (NULL == request_uri || '\0' == request_uri[0]) {
Expand Down Expand Up @@ -516,6 +548,10 @@ void __superglobals__refresh(void)
set_string_key(sg_SERVER, "QUERY_STRING", query_string);
set_string_key(sg_SERVER, "SCRIPT_NAME", script_name);
set_string_key(sg_SERVER, "PHP_SELF", script_name);
resolve_script_filename(script_name, script_filename, sizeof(script_filename));
if ('\0' != script_filename[0]) {
set_string_key(sg_SERVER, "SCRIPT_FILENAME", script_filename);
}
set_string_key(sg_SERVER, "REQUEST_URI", request_uri);
set_string_key(sg_SERVER, "GATEWAY_INTERFACE", "CGI/1.1");
set_string_key(sg_SERVER, "SERVER_SOFTWARE", "PHP-Compiler-AOT");
Expand Down
4 changes: 3 additions & 1 deletion lib/JIT/SuperglobalInit.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ final class SuperglobalInit
/** @var array<string, \PHPLLVM\Value> */
public static array $globals = [];

/** $_SERVER keys repopulated by __superglobals__refresh (issue #201, #235). */
/** $_SERVER keys repopulated by __superglobals__refresh (issue #201, #235, #296, #302). */
private const RUNTIME_SERVER_KEYS = [
'REQUEST_SCHEME',
'HTTPS',
'SERVER_PORT',
'SERVER_NAME',
'DOCUMENT_ROOT',
'SCRIPT_FILENAME',
];

public static function declareRefresh(Context $context): void
Expand Down
7 changes: 7 additions & 0 deletions lib/Web/DevServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public static function handleConnection($conn, string $docroot, callable $handle
return;
}

$scriptFilename = realpath($script);
if (false === $scriptFilename) {
$scriptFilename = $script;
}

$requestUri = $scriptName.$pathInfo;
if ('' !== $query) {
$requestUri .= '?'.$query;
Expand All @@ -115,6 +120,7 @@ public static function handleConnection($conn, string $docroot, callable $handle
'QUERY_STRING' => $query,
'REQUEST_BODY' => $body,
'SCRIPT_NAME' => $scriptName,
'SCRIPT_FILENAME' => $scriptFilename,
'REQUEST_URI' => $requestUri,
'DOCUMENT_ROOT' => $docroot,
];
Expand All @@ -129,6 +135,7 @@ public static function handleConnection($conn, string $docroot, callable $handle
putenv('QUERY_STRING='.$query);
putenv('REQUEST_BODY='.$body);
putenv('SCRIPT_NAME='.$scriptName);
putenv('SCRIPT_FILENAME='.$scriptFilename);
putenv('REQUEST_URI='.$requestUri);
putenv('DOCUMENT_ROOT='.$docroot);
if ('' !== $pathInfo) {
Expand Down
36 changes: 35 additions & 1 deletion lib/Web/Superglobals.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ public static function isSuperglobalName(string $name): bool
public static function populateFromEnvironment(
Context $context,
?string $queryString = null,
?string $postBody = null
?string $postBody = null,
?string $scriptFilename = null
): void {
self::$activeContext = $context;
if (null !== $scriptFilename && '' !== $scriptFilename) {
putenv('SCRIPT_FILENAME='.$scriptFilename);
}
if (null === $queryString) {
$fromEnv = getenv('QUERY_STRING');
$queryString = false === $fromEnv ? '' : $fromEnv;
Expand Down Expand Up @@ -161,6 +165,11 @@ private static function populateServer(
self::setStringEntry($server, 'SCRIPT_NAME', $scriptName);
self::setStringEntry($server, 'PHP_SELF', $scriptName);

$scriptFilename = self::resolveScriptFilename($scriptName);
if ('' !== $scriptFilename) {
self::setStringEntry($server, 'SCRIPT_FILENAME', $scriptFilename);
}

$requestUri = getenv('REQUEST_URI');
if (false === $requestUri || '' === $requestUri) {
$requestUri = $scriptName;
Expand Down Expand Up @@ -456,6 +465,31 @@ private static function setScalarEntry(HashTable $ht, $key, $value): void
}
}

/**
* Resolve absolute filesystem path for the entry script (issue #302).
*/
public static function resolveScriptFilename(?string $scriptName = null): string
{
$fromEnv = getenv('SCRIPT_FILENAME');
if (false !== $fromEnv && '' !== $fromEnv) {
return $fromEnv;
}

if (null === $scriptName) {
$scriptName = getenv('SCRIPT_NAME');
if (false === $scriptName || '' === $scriptName) {
$scriptName = '/index.php';
}
}

$documentRoot = getenv('DOCUMENT_ROOT');
if (false !== $documentRoot && '' !== $documentRoot) {
return rtrim($documentRoot, '/').$scriptName;
}

return '';
}

/**
* Derive PATH_INFO from REQUEST_URI path suffix after SCRIPT_NAME (front-controller pattern).
*/
Expand Down
61 changes: 61 additions & 0 deletions test/aot/RuntimeSuperglobalRefreshTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,60 @@ public function testCookieFromHttpCookieEnv(): void
@unlink($outfile);
}

public function testScriptFilenameFromCgiEnvironment(): void
{
$source = <<<'PHP'
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=UTF-8');
echo $_SERVER['SCRIPT_FILENAME'];
PHP;

$outfile = tempnam(sys_get_temp_dir(), 'phpc_script_fn_');
$this->assertNotFalse($outfile);
unlink($outfile);

$repoRoot = dirname(__DIR__, 2);
$env = $this->llvmProcessEnv($repoRoot);
$descriptorSpec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];

$compile = proc_open(
array_merge(
self::llvmEnvPrefix(),
self::phpCommand(),
[$this->compileBin, '-o', $outfile]
),
$descriptorSpec,
$pipes,
$repoRoot,
$env
);
fwrite($pipes[0], $source);
fclose($pipes[0]);
$compileErr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($compile);
$this->assertFileExists($outfile, trim($compileErr !== false ? $compileErr : ''));

$root = realpath(sys_get_temp_dir());
$this->assertNotFalse($root);
$runEnv = $env;
unset($runEnv['SCRIPT_FILENAME']);
$runEnv['DOCUMENT_ROOT'] = $root;
$runEnv['SCRIPT_NAME'] = '/index.php';
$runEnv['REQUEST_URI'] = '/index.php';
$output = $this->runBinary($outfile, $runEnv);
$expected = $root.'/index.php';
$this->assertStringContainsString($expected, $this->cgiBody($output));

@unlink($outfile);
}

public function testDocumentRootFromCgiEnvironment(): void
{
$source = <<<'PHP'
Expand Down Expand Up @@ -287,6 +341,13 @@ public function testHttpHostFromCgiEnvironment(): void
@unlink($outfile);
}

private function cgiBody(string $output): string
{
$parts = preg_split("/\r?\n\r?\n/", $output, 2);

return $parts[1] ?? $output;
}

/**
* @param array<string, string> $env
*/
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/aot/cases/web_script_filename.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
AOT: SCRIPT_FILENAME from CGI env (issue #302)
--ENV--
REQUEST_METHOD=GET
SCRIPT_NAME=/index.php
REQUEST_URI=/index.php
DOCUMENT_ROOT=/var/www/html
SCRIPT_FILENAME=/var/www/html/index.php
--FILE--
<?php
echo $_SERVER['SCRIPT_FILENAME'];
--EXPECT--
/var/www/html/index.php
--EXPECT_EXIT--
0
5 changes: 5 additions & 0 deletions test/fixtures/web_echo_script_filename.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

declare(strict_types=1);

echo $_SERVER['SCRIPT_FILENAME'];
23 changes: 23 additions & 0 deletions test/real/ServeAotTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ protected function setUp(): void
$this->phpCmd = self::phpCommand();
}

public function testServeAotPopulatesScriptFilename(): void
{
$docroot = $this->makeDocroot([
'script.php' => <<<'PHP'
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=UTF-8');
echo $_SERVER['SCRIPT_FILENAME'];
PHP,
]);
$script = realpath($docroot.'/script.php');
$this->assertNotFalse($script);
$binaryDir = sys_get_temp_dir().'/phpc_serve_aot_sf_'.bin2hex(random_bytes(4));
$this->assertTrue(mkdir($binaryDir));
$binary = $binaryDir.'/app';
$this->compileExample($docroot.'/script.php', $binary);
$response = $this->httpGetAot($docroot, $binary, '/script.php');
$this->assertStringContainsString('HTTP/1.1 200', $response);
$this->assertStringContainsString($script, $response);
@unlink($binary);
@rmdir($binaryDir);
}

public function testServeAotPopulatesDocumentRoot(): void
{
$docroot = $this->makeDocroot([
Expand Down
15 changes: 15 additions & 0 deletions test/real/ServeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ public function testPopulatesDocumentRoot(): void
$this->assertStringContainsString($resolved, $response);
}

public function testPopulatesScriptFilename(): void
{
$docroot = $this->makeDocroot([
'script.php' => <<<'PHP'
<?php
echo $_SERVER['SCRIPT_FILENAME'];
PHP,
]);
$script = realpath($docroot.'/script.php');
$this->assertNotFalse($script);
$response = $this->httpGet($docroot, '/script.php');
$this->assertStringContainsString('HTTP/1.1 200', $response);
$this->assertStringContainsString($script, $response);
}

public function testPopulatesCookieFromRequestHeader(): void
{
$docroot = $this->makeDocroot([
Expand Down
15 changes: 15 additions & 0 deletions test/real/cases/web_script_filename.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Web: SCRIPT_FILENAME from CGI env (issue #302)
--ENV--
REQUEST_METHOD=GET
SCRIPT_NAME=/index.php
REQUEST_URI=/index.php
DOCUMENT_ROOT=/var/www/html
SCRIPT_FILENAME=/var/www/html/index.php
--FILE--
<?php
echo $_SERVER['SCRIPT_FILENAME'];
--EXPECT--
/var/www/html/index.php
--EXPECT_EXIT--
0
Loading
Loading