From fefd3dd8954ee06b626dfa38f0299105a6b00c4e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 21 Oct 2023 14:19:20 +0900 Subject: [PATCH] Fix path normalization problem --- common/framework/Template.php | 39 +++++++++++++------ .../parsers/template/TemplateParser_v2.php | 5 +++ tests/_data/template/incl/scopetest1.html | 3 ++ tests/_data/template/incl/scopetest2.html | 2 +- tests/_data/template/js/test.js | 3 ++ tests/_data/template/v2varscope.html | 6 +-- tests/unit/framework/TemplateTest.php | 13 +++++++ .../parsers/TemplateParserV2Test.php | 9 +++-- 8 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 tests/_data/template/js/test.js diff --git a/common/framework/Template.php b/common/framework/Template.php index 10574c1e5..9d743d069 100644 --- a/common/framework/Template.php +++ b/common/framework/Template.php @@ -492,10 +492,20 @@ class Template $path = preg_replace('#/\./#', '/', $basepath . $path); } - // Remove extra slashes and parent directory references. - $path = preg_replace('#\\\\#', '/', $path); - $path = preg_replace('#//#', '/', $path); - while (($tmp = preg_replace('#/[^/]+/\.\./#', '/', $path)) !== $path) + // Normalize and return the path. + return $this->normalizePath($path); + } + + /** + * Normalize a path by removing extra slashes and parent directory references. + * + * @param string $path + * @return string + */ + public function normalizePath(string $path): string + { + $path = preg_replace('#[\\\\/]+#', '/', $path); + while (($tmp = preg_replace('#/[^/]+/\.\.(/|$)#', '$1', $path)) !== $path) { $path = $tmp; } @@ -527,13 +537,6 @@ class Template $path = $isConditional ? $args[2] : $args[1]; $vars = $isConditional ? ($args[3] ?? null) : ($args[2] ?? null); - // Handle paths relative to the Rhymix installation directory. - if (preg_match('#^\^/?(\w.+)$#s', $path, $match)) - { - $basedir = str_contains($match[1], '/') ? dirname($match[1]) : \RX_BASEDIR; - $path = basename($match[1]); - } - // If the conditions are not met, return. if ($isConditional && $directive === 'includeWhen' && !$cond) { @@ -544,6 +547,20 @@ class Template return ''; } + // Handle paths relative to the Rhymix installation directory. + if (preg_match('#^\^/?(\w.+)$#s', $path, $match)) + { + $basedir = str_contains($match[1], '/') ? dirname($match[1]) : \RX_BASEDIR; + $path = basename($match[1]); + } + + // Convert relative paths embedded in the filename. + if (preg_match('#^(.+)/([^/]+)$#', $path, $match)) + { + $basedir = $this->normalizePath($basedir . $match[1] . '/'); + $path = $match[2]; + } + // Create a new instance of TemplateHandler. $template = new self($basedir, $path, $extension); diff --git a/common/framework/parsers/template/TemplateParser_v2.php b/common/framework/parsers/template/TemplateParser_v2.php index 52499ed01..e2d87b6ea 100644 --- a/common/framework/parsers/template/TemplateParser_v2.php +++ b/common/framework/parsers/template/TemplateParser_v2.php @@ -399,6 +399,11 @@ class TemplateParser_v2 $dir = '"' . (str_contains($m[1], '/') ? dirname($m[1]) : '') . '"'; $path = basename($m[1]); } + if (preg_match('#^(.+)/([^/]+)$#', $path, $match)) + { + $dir = '$this->normalizePath(' . $dir . ' . "' . $match[1] . '")'; + $path = $match[2]; + } // Generate the code to create a new Template object and compile it. $tpl = 'template->extension ?: 'auto') . '"); '; diff --git a/tests/_data/template/incl/scopetest1.html b/tests/_data/template/incl/scopetest1.html index d6bf7e92d..10ba9859c 100644 --- a/tests/_data/template/incl/scopetest1.html +++ b/tests/_data/template/incl/scopetest1.html @@ -1,3 +1,6 @@
{{ $foobar }}
{{ $globalonly }}
+@once + @load ('../js/test.js') +@endonce diff --git a/tests/_data/template/incl/scopetest2.html b/tests/_data/template/incl/scopetest2.html index 972382225..1c99d4d92 100644 --- a/tests/_data/template/incl/scopetest2.html +++ b/tests/_data/template/incl/scopetest2.html @@ -1,5 +1,5 @@
{{ $foobar }}
- +
diff --git a/tests/_data/template/js/test.js b/tests/_data/template/js/test.js new file mode 100644 index 000000000..3f7900cc9 --- /dev/null +++ b/tests/_data/template/js/test.js @@ -0,0 +1,3 @@ +(function($) { + // TEST +})(jQuery); diff --git a/tests/_data/template/v2varscope.html b/tests/_data/template/v2varscope.html index 2845985fc..0988f1c03 100644 --- a/tests/_data/template/v2varscope.html +++ b/tests/_data/template/v2varscope.html @@ -1,9 +1,9 @@ @version(2) -@php +
@include ('incl/scopetest1.html') @@ -14,5 +14,5 @@
- @include ('incl/scopetest2.html', ['foobar' => 'Included #2']) +
diff --git a/tests/unit/framework/TemplateTest.php b/tests/unit/framework/TemplateTest.php index 5f97fee67..1814cb6f8 100644 --- a/tests/unit/framework/TemplateTest.php +++ b/tests/unit/framework/TemplateTest.php @@ -51,4 +51,17 @@ class TemplateTest extends \Codeception\Test\Unit $target = '/rhymix/foo/bar.gif'; $this->assertEquals($target, $tmpl->convertPath($source)); } + + public function testNormalizePath() + { + $tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html'); + + $source = '/rhymix/foo/bar//../hello/world\\..'; + $target = '/rhymix/foo/hello'; + $this->assertEquals($target, $tmpl->normalizePath($source)); + + $source = '../foo\\bar/../baz/'; + $target = '../foo/baz/'; + $this->assertEquals($target, $tmpl->normalizePath($source)); + } } diff --git a/tests/unit/framework/parsers/TemplateParserV2Test.php b/tests/unit/framework/parsers/TemplateParserV2Test.php index ecb555958..987465352 100644 --- a/tests/unit/framework/parsers/TemplateParserV2Test.php +++ b/tests/unit/framework/parsers/TemplateParserV2Test.php @@ -55,17 +55,17 @@ class TemplateParserV2Test extends \Codeception\Test\Unit // Legacy 'target' attribute $source = ''; - $target = 'relative_dirname, "subdir/foobar", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; + $target = 'normalizePath($this->relative_dirname . "subdir"), "foobar", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Conditional include $source = ''; - $target = 'relative_dirname, "../up/foobar", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; + $target = 'normalizePath($this->relative_dirname . "../up"), "foobar", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Conditional include with legacy 'cond' attribute $source = ''; - $target = 'relative_dirname, "legacy/cond.statement.html", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; + $target = 'normalizePath($this->relative_dirname . "legacy"), "cond.statement.html", "html"); if ($this->vars): $__tpl->setVars($this->vars); endif; echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Path relative to Rhymix installation directory @@ -1183,6 +1183,9 @@ class TemplateParserV2Test extends \Codeception\Test\Unit $this->_normalizeWhitespace($expected), $this->_normalizeWhitespace($executed_output) ); + + $list = \Context::getJsFile(); + $this->assertStringContainsString('/rhymix/tests/_data/template/js/test.js', array_last($list)['file']); } /**