diff --git a/flight/template/View.php b/flight/template/View.php index 24a5724..2e81ae3 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -222,17 +222,19 @@ class View */ private function relativeStaysInside(string $file): bool { - $segments = \preg_split('#[\\/]+#', $file, -1, \PREG_SPLIT_NO_EMPTY); - if ($segments === false) { - return false; - } - + $segments = \explode('/', \str_replace('\\', '/', $file)); $depth = 0; + foreach ($segments as $segment) { - if ($segment === '.') { + if ($segment === '' || $segment === '.') { continue; } + // A drive letter or colon is an absolute jump, not a view name. + if (\strpos($segment, ':') !== false) { + return false; + } + if ($segment === '..') { if ($depth === 0) { return false; diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 60162fb..ac360d2 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -138,14 +138,11 @@ class ViewTest extends TestCase public function testRejectsTemplateThatLeavesViewsDirectory(): void { - $outside = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'flight-view-outside-' . uniqid(); + $outside = dirname($this->view->path) . DIRECTORY_SEPARATOR . 'flight-view-outside-' . uniqid(); mkdir($outside); $note = $outside . DIRECTORY_SEPARATOR . 'note.php'; file_put_contents($note, 'view->path); - $relative = $this->relativePathFrom($views, $note); - $relative = preg_replace('/\.php$/', '', $relative); + $relative = '..' . DIRECTORY_SEPARATOR . basename($outside) . DIRECTORY_SEPARATOR . 'note'; try { $this->expectException(Exception::class); @@ -157,6 +154,13 @@ class ViewTest extends TestCase } } + public function testRejectsEmbeddedDriveLetter(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Template path is not allowed.'); + $this->view->getTemplate('layouts' . DIRECTORY_SEPARATOR . 'C:' . DIRECTORY_SEPARATOR . 'outside'); + } + public function testRejectsTemplateThatResolvesOutsideViewsDirectory(): void { $root = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'flight-view-root-' . uniqid(); @@ -194,20 +198,6 @@ class ViewTest extends TestCase $view->render('hello'); } - private function relativePathFrom(string $fromDir, string $toFile): string - { - $from = explode(DIRECTORY_SEPARATOR, rtrim($fromDir, DIRECTORY_SEPARATOR)); - $to = explode(DIRECTORY_SEPARATOR, $toFile); - $file = array_pop($to); - - while ($from !== [] && $to !== [] && $from[0] === $to[0]) { - array_shift($from); - array_shift($to); - } - - $up = array_fill(0, count($from), '..'); - return implode(DIRECTORY_SEPARATOR, array_merge($up, $to, [$file])); - } private function removeDir(string $dir): void {