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
8 changes: 4 additions & 4 deletions docs/bootstrap-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -1501,10 +1501,10 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `lib/Web/Superglobals.php`

**Warnings** (review for bootstrap subset):
- new HashTable (line 406)
- new Variable (line 407)
- new Variable (line 426)
- new Variable (line 476)
- 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

### `src/macro_functions.php`
Expand Down
8 changes: 8 additions & 0 deletions lib/AOT/runtime/superglobals_refresh.c
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,14 @@ void __superglobals__refresh(void)
set_string_key(sg_SERVER, "GATEWAY_INTERFACE", "CGI/1.1");
set_string_key(sg_SERVER, "SERVER_SOFTWARE", "PHP-Compiler-AOT");

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

if (NULL != document_root && '\0' != document_root[0]) {
set_string_key(sg_SERVER, "DOCUMENT_ROOT", document_root);
}
}

derive_path_info(script_name, request_uri, path_info, sizeof(path_info));
if ('\0' != path_info[0]) {
set_string_key(sg_SERVER, "PATH_INFO", path_info);
Expand Down
2 changes: 2 additions & 0 deletions lib/Web/DevServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public static function handleConnection($conn, string $docroot, callable $handle
'REQUEST_BODY' => $body,
'SCRIPT_NAME' => $scriptName,
'REQUEST_URI' => $requestUri,
'DOCUMENT_ROOT' => $docroot,
];
if ('' !== $pathInfo) {
$cgiEnv['PATH_INFO'] = $pathInfo;
Expand All @@ -129,6 +130,7 @@ public static function handleConnection($conn, string $docroot, callable $handle
putenv('REQUEST_BODY='.$body);
putenv('SCRIPT_NAME='.$scriptName);
putenv('REQUEST_URI='.$requestUri);
putenv('DOCUMENT_ROOT='.$docroot);
if ('' !== $pathInfo) {
putenv('PATH_INFO='.$pathInfo);
} else {
Expand Down
5 changes: 5 additions & 0 deletions lib/Web/Superglobals.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ private static function populateServer(
self::setStringEntry($server, 'GATEWAY_INTERFACE', 'CGI/1.1');
self::setStringEntry($server, 'SERVER_SOFTWARE', 'PHP-Compiler-VM');

$documentRoot = getenv('DOCUMENT_ROOT');
if (false !== $documentRoot && '' !== $documentRoot) {
self::setStringEntry($server, 'DOCUMENT_ROOT', $documentRoot);
}

foreach (array_merge($_ENV, $_SERVER) as $key => $value) {
if (!is_string($key) || !is_string($value)) {
continue;
Expand Down
52 changes: 52 additions & 0 deletions test/aot/RuntimeSuperglobalRefreshTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,58 @@ public function testCookieFromHttpCookieEnv(): void
@unlink($outfile);
}

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

$outfile = tempnam(sys_get_temp_dir(), 'phpc_docroot_');
$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;
$runEnv['DOCUMENT_ROOT'] = $root;
$runEnv['SCRIPT_NAME'] = '/index.php';
$runEnv['REQUEST_URI'] = '/index.php';
$output = $this->runBinary($outfile, $runEnv);
$this->assertStringContainsString($root, $output);

@unlink($outfile);
}

public function testHttpHostFromCgiEnvironment(): void
{
$source = <<<'PHP'
Expand Down
14 changes: 14 additions & 0 deletions test/fixtures/aot/cases/web_document_root.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
AOT: DOCUMENT_ROOT from CGI env (issue #296)
--ENV--
REQUEST_METHOD=GET
SCRIPT_NAME=/index.php
REQUEST_URI=/index.php
DOCUMENT_ROOT=/var/www/html
--FILE--
<?php
echo $_SERVER['DOCUMENT_ROOT'];
--EXPECT--
/var/www/html
--EXPECT_EXIT--
0
55 changes: 53 additions & 2 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 testServeAotPopulatesDocumentRoot(): void
{
$docroot = $this->makeDocroot([
'docroot.php' => <<<'PHP'
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=UTF-8');
echo $_SERVER['DOCUMENT_ROOT'];
PHP,
]);
$resolved = realpath($docroot);
$this->assertNotFalse($resolved);
$binaryDir = sys_get_temp_dir().'/phpc_serve_aot_dr_'.bin2hex(random_bytes(4));
$this->assertTrue(mkdir($binaryDir));
$binary = $binaryDir.'/app';
$this->compileExample($docroot.'/docroot.php', $binary);
$response = $this->httpGetAot($docroot, $binary, '/docroot.php');
$this->assertStringContainsString('HTTP/1.1 200', $response);
$this->assertStringContainsString($resolved, $response);
@unlink($binary);
@rmdir($binaryDir);
}

public function testServeAot001SimpleWeb(): void
{
$docroot = $this->repoRoot.'/examples/001-SimpleWeb';
Expand All @@ -57,7 +80,8 @@ private function compileExample(string $source, string $outfile): void
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$env = $this->llvmEnv();
$env = $this->baseEnv();
LlvmToolchain::applyProcessEnv($env, $this->repoRoot);
$compile = proc_open(
array_merge(
self::llvmEnvPrefix(),
Expand All @@ -73,7 +97,12 @@ private function compileExample(string $source, string $outfile): void
$err = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($compile);
$exitCode = proc_close($compile);
$this->assertSame(
0,
$exitCode,
'compile.php failed: '.trim($err !== false ? $err : '')
);
$this->assertFileExists($outfile, trim($err !== false ? $err : ''));
}

Expand Down Expand Up @@ -133,6 +162,25 @@ private function llvmEnv(): array
return $env;
}

/**
* @param array<string, string> $files relative path => contents
*/
private function makeDocroot(array $files): string
{
$dir = sys_get_temp_dir().'/phpc_serve_aot_'.bin2hex(random_bytes(4));
$this->assertTrue(mkdir($dir));
foreach ($files as $name => $contents) {
$path = $dir.'/'.$name;
$parent = dirname($path);
if (!is_dir($parent)) {
mkdir($parent, 0777, true);
}
file_put_contents($path, $contents);
}

return $dir;
}

private function findFreePort(): int
{
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
Expand Down Expand Up @@ -167,6 +215,9 @@ private static function phpCommand(): array
$phpEnv = getenv('PHP_COMPILER_PHP');
if (false !== $phpEnv && '' !== $phpEnv) {
$cmd = preg_split('/\s+/', $phpEnv);
if (!str_contains($cmd[0], '/')) {
$cmd[0] = PHP_BINARY;
}
} else {
$cmd = [PHP_BINARY];
}
Expand Down
15 changes: 15 additions & 0 deletions test/real/ServeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ public function testPopulatesHttpServerHeaders(): void
$this->assertStringContainsString('example.test|1', $response);
}

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

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

declare(strict_types=1);

namespace PHPCompiler;

use PHPCompiler\Web\Superglobals;
use PHPUnit\Framework\TestCase;

/**
* Issue #296: $_SERVER['DOCUMENT_ROOT'] from CGI DOCUMENT_ROOT env.
*/
final class SuperglobalsDocumentRootTest extends TestCase
{
private Runtime $runtime;

protected function setUp(): void
{
$this->runtime = new Runtime();
}

protected function tearDown(): void
{
putenv('DOCUMENT_ROOT');
unset($_SERVER['DOCUMENT_ROOT']);
}

public function testDocumentRootFromEnvironment(): void
{
$root = realpath(sys_get_temp_dir());
$this->assertNotFalse($root);
putenv('DOCUMENT_ROOT='.$root);

Superglobals::populateFromEnvironment($this->runtime->vmContext, '', '');

$server = $this->runtime->vmContext->getSuperglobal('_SERVER')->toArray();
$this->assertSame($root, $this->readServer($server, 'DOCUMENT_ROOT'));
}

public function testDocumentRootOmittedWhenUnset(): void
{
putenv('DOCUMENT_ROOT');

Superglobals::populateFromEnvironment($this->runtime->vmContext, '', '');

$server = $this->runtime->vmContext->getSuperglobal('_SERVER')->toArray();
$this->assertSame('', $this->readServer($server, 'DOCUMENT_ROOT'));
}

private function readServer(\PHPCompiler\VM\HashTable $server, string $key): string
{
$var = $server->find($key);
if (null === $var) {
return '';
}
$resolved = $var->resolveIndirect();
if (\PHPCompiler\VM\Variable::TYPE_STRING !== $resolved->type) {
return '';
}

return $resolved->toString();
}
}
Loading