diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 494376126..71c1caaed 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -202,8 +202,8 @@ class Filesystem throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } - $from = lcfirst(rtrim(strtr($from, '\\', '/'), '/')); - $to = lcfirst(rtrim(strtr($to, '\\', '/'), '/')); + $from = lcfirst($this->normalizePath($from)); + $to = lcfirst($this->normalizePath($to)); if ($directories) { $from .= '/dummy_file'; @@ -243,8 +243,8 @@ class Filesystem throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } - $from = lcfirst(strtr($from, '\\', '/')); - $to = lcfirst(strtr($to, '\\', '/')); + $from = lcfirst($this->normalizePath($from)); + $to = lcfirst($this->normalizePath($to)); if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; @@ -300,6 +300,35 @@ class Filesystem return filesize($path); } + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @param string $path Path to the file or directory + * @return string + */ + public function normalizePath($path) + { + $parts = array(); + $path = strtr($path, '\\', '/'); + $prefix = ''; + + if (preg_match('|^(([a-z]:)?/)|i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk) { + array_pop($parts); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + } + } + + return $prefix.implode('/', $parts); + } + protected function directorySize($directory) { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index 0694e24db..479fad56d 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -38,6 +38,7 @@ class FilesystemTest extends TestCase array('c:/bin/run', 'd:/vendor/acme/bin/run', false, "'d:/vendor/acme/bin/run'"), array('c:\\bin\\run', 'd:/vendor/acme/bin/run', false, "'d:/vendor/acme/bin/run'"), array('/foo/bar', '/foo/bar', true, "__DIR__"), + array('/foo/bar/', '/foo/bar', true, "__DIR__"), array('/foo/bar', '/foo/baz', true, "dirname(__DIR__).'/baz'"), array('/foo/bin/run', '/foo/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('/foo/bin/run', '/bar/bin/run', true, "'/bar/bin/run'"), @@ -52,6 +53,11 @@ class FilesystemTest extends TestCase array('/tmp/test', '/tmp', true, "dirname(__DIR__)"), array('/tmp', '/tmp/test', true, "__DIR__ . '/test'"), array('C:/Temp', 'c:\Temp\test', true, "__DIR__ . '/test'"), + array('/tmp/test/./', '/tmp/test/', true, '__DIR__'), + array('/tmp/test/../vendor', '/tmp/test', true, "dirname(__DIR__).'/test'"), + array('/tmp/test/.././vendor', '/tmp/test', true, "dirname(__DIR__).'/test'"), + array('C:/Temp', 'c:\Temp\..\..\test', true, "dirname(__DIR__).'/test'"), + array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'d:/test'"), ); } @@ -91,6 +97,12 @@ class FilesystemTest extends TestCase array('/tmp', '/tmp/test', "test"), array('C:/Temp', 'C:\Temp\test', "test"), array('C:/Temp', 'c:\Temp\test', "test"), + array('/tmp/test/./', '/tmp/test', './', true), + array('/tmp/test/../vendor', '/tmp/test', '../test', true), + array('/tmp/test/.././vendor', '/tmp/test', '../test', true), + array('C:/Temp', 'c:\Temp\..\..\test', "../test", true), + array('C:/Temp/../..', 'c:\Temp\..\..\test', "./test", true), + array('/tmp', '/tmp/../../test', '/test', true), ); }