Skip to content

Commit

Permalink
Fix PathInfo parsing in PHP router
Browse files Browse the repository at this point in the history
  • Loading branch information
cedric-anne committed Nov 28, 2023
1 parent 20e93ff commit d4a8ae8
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 14 deletions.
45 changes: 31 additions & 14 deletions src/Http/ProxyRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,47 @@ final class ProxyRouter
*/
private ?string $pathinfo;

public function __construct(string $root_dir, string $path)
public function __construct(string $root_dir, string $uri)
{
$this->root_dir = $root_dir;

$path = preg_replace('/\/{2,}/', '/', $path); // remove duplicates `/`

$path_matches = [];
if (
preg_match('/^(?<path>.+\.[^\/]+)(?<pathinfo>\/.*)$/', $path, $path_matches) === 1
&& is_file($this->root_dir . $path_matches['path'])
) {
// Separate path and pathinfo.
$path = $path_matches['path'];
$pathinfo = $path_matches['pathinfo'];
} else {
$uri = preg_replace('/\/{2,}/', '/', $uri); // remove duplicates `/`

$path = null;
$pathinfo = null;

// Parse URI to find requested script and PathInfo
$slash_pos = 0;
while ($slash_pos !== false && ($dot_pos = strpos($uri, '.', $slash_pos)) !== false) {
$slash_pos = strpos($uri, '/', $dot_pos);
$filepath = substr($uri, 0, $slash_pos !== false ? $slash_pos : strlen($uri));
if (is_file($this->root_dir . $filepath)) {
$path = $filepath;

$pathinfo = substr($uri, strlen($filepath));
if ($pathinfo !== '') {
// On any regular PHP script that is directly served by Apache, `$_SERVER['PATH_INFO']`
// contains decoded URL.
// We have to reproduce this decoding operation to prevent issues with endoded chars.
$pathinfo = urldecode($pathinfo);
} else {
$pathinfo = null;
}
break;
}
}

if ($path === null) {
// Fallback to requested URI
$path = $uri;

// Clean trailing `/`.
$path = rtrim($path, '/');

// If URI matches a directory path, consider `index.php` is the requested script.
if (is_dir($this->root_dir . $path) && is_file($this->root_dir . $path . '/index.php')) {
$path .= '/index.php';
}

$pathinfo = null;
}

$this->path = $path;
Expand Down
43 changes: 43 additions & 0 deletions tests/units/Glpi/Http/ProxyRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ protected function targetProvider(): iterable
'common.js' => 'console.log("ok");',
],
'marketplace' => [
'myplugin' => [
'some.dir' => [
'file.test.php' => '<?php //a PHP script in a dir that contains a dot',
],
],
'mystaleplugin' => [
'front' => [
'page.php5' => '<?php //a PHP5 script',
Expand All @@ -67,6 +72,7 @@ protected function targetProvider(): iterable
],
],
'apirest.php' => '<php echo(1);',
'caldav.php' => '<php echo(1);',
'index.php' => '<php echo(1);',
]
);
Expand Down Expand Up @@ -113,6 +119,13 @@ protected function targetProvider(): iterable
];

// Path to an existing file, but containing an extra PathInfo
yield [
'path' => '/apirest.php/',
'target_path' => '/apirest.php',
'target_pathinfo' => '/',
'target_file' => vfsStream::url('glpi/apirest.php'),
'is_php_script' => true,
];
yield [
'path' => '/apirest.php/initSession/',
'target_path' => '/apirest.php',
Expand All @@ -127,6 +140,20 @@ protected function targetProvider(): iterable
'target_file' => vfsStream::url('glpi/apirest.php'),
'is_php_script' => true,
];
yield [
'path' => '/apirest.php/GlpiPlugin%5CNamespace%5CUnexemple/',
'target_path' => '/apirest.php',
'target_pathinfo' => '/GlpiPlugin\Namespace\Unexemple/',
'target_file' => vfsStream::url('glpi/apirest.php'),
'is_php_script' => true,
];
yield [
'path' => '/caldav.php/calendars/users/J.DUPONT/calendar/',
'target_path' => '/caldav.php',
'target_pathinfo' => '/calendars/users/J.DUPONT/calendar/',
'target_file' => vfsStream::url('glpi/caldav.php'),
'is_php_script' => true,
];

// Path to an existing directory that have a `index.php` script.
yield [
Expand All @@ -146,6 +173,22 @@ protected function targetProvider(): iterable
'is_php_script' => false,
];

// Path to a PHP script in a directory that has a dot in its name.
yield [
'path' => '/marketplace/myplugin/some.dir/file.test.php',
'target_path' => '/marketplace/myplugin/some.dir/file.test.php',
'target_pathinfo' => null,
'target_file' => vfsStream::url('glpi/marketplace/myplugin/some.dir/file.test.php'),
'is_php_script' => true,
];
yield [
'path' => '/marketplace/myplugin/some.dir/file.test.php/path/to/item',
'target_path' => '/marketplace/myplugin/some.dir/file.test.php',
'target_pathinfo' => '/path/to/item',
'target_file' => vfsStream::url('glpi/marketplace/myplugin/some.dir/file.test.php'),
'is_php_script' => true,
];

// Path to a `.php5` script.
yield [
'path' => '/marketplace/mystaleplugin/front/page.php5',
Expand Down

0 comments on commit d4a8ae8

Please sign in to comment.