diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php index 28c568b708..9392c33bd7 100644 --- a/framework/console/controllers/AssetController.php +++ b/framework/console/controllers/AssetController.php @@ -512,9 +512,10 @@ EOD; public function combineCssFiles($inputFiles, $outputFile) { $content = ''; + $outputFilePath = dirname($this->findRealPath($outputFile)); foreach ($inputFiles as $file) { $content .= "/*** BEGIN FILE: $file ***/\n" - . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile)) + . $this->adjustCssUrl(file_get_contents($file), dirname($this->findRealPath($file)), $outputFilePath) . "/*** END FILE: $file ***/\n"; } if (!file_put_contents($outputFile, $content)) { @@ -658,4 +659,26 @@ EOD; echo "Configuration file template created at '{$configFile}'.\n\n"; } } + + /** + * Returns canonicalized absolute pathname. + * Unlike regular `realpath()` this method does not expand symlinks and does not check path existence. + * @param string $path raw path + * @return string canonicalized absolute pathname + */ + private function findRealPath($path) + { + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + $pathParts = explode(DIRECTORY_SEPARATOR, $path); + + $realPathParts = []; + foreach ($pathParts as $pathPart) { + if ($pathPart === '..') { + array_pop($realPathParts); + } else { + array_push($realPathParts, $pathPart); + } + } + return implode(DIRECTORY_SEPARATOR, $realPathParts); + } } diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index 0b1ae94e66..534e7f07cd 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -409,4 +409,51 @@ EOL; $this->assertEquals($expectedCssContent, $adjustedCssContent, 'Unable to adjust CSS correctly!'); } + + /** + * Data provider for [[testFindRealPath()]] + * @return array test data + */ + public function findRealPathDataProvider() + { + return [ + [ + '/linux/absolute/path', + '/linux/absolute/path', + ], + [ + '/linux/up/../path', + '/linux/path', + ], + [ + '/linux/twice/up/../../path', + '/linux/path', + ], + [ + '/linux/../mix/up/../path', + '/mix/path', + ], + [ + 'C:\\windows\\absolute\\path', + 'C:\\windows\\absolute\\path', + ], + [ + 'C:\\windows\\up\\..\\path', + 'C:\\windows\\path', + ], + ]; + } + + /** + * @dataProvider findRealPathDataProvider + * + * @param string $sourcePath + * @param string $expectedRealPath + */ + public function testFindRealPath($sourcePath, $expectedRealPath) + { + $expectedRealPath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $expectedRealPath); + $realPath = $this->invokeAssetControllerMethod('findRealPath', [$sourcePath]); + $this->assertEquals($expectedRealPath, $realPath); + } }