diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 4fe3d3e8d..ca395736f 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -95,8 +95,8 @@ class Filesystem */ public function removeDirectory($directory) { - if (file_exists($directory) && is_link($directory)) { - return $this->unlink($directory); + if ($this->isSymlinkedDirectory($directory)) { + return $this->unlinkSymlinkedDirectory($directory); } if (!file_exists($directory) || !is_dir($directory)) { @@ -507,4 +507,49 @@ class Filesystem return unlink($path); } + + private function isSymlinkedDirectory($directory) + { + if (!is_dir($directory)) { + return false; + } + + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return is_link($resolved); + } + + /** + * @param string $directory + * + * @return bool + */ + private function unlinkSymlinkedDirectory($directory) + { + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return $this->unlink($resolved); + } + + /** + * resolve pathname to symbolic link of a directory + * + * @param string $pathname directory path to resolve + * + * @return string resolved path to symbolic link or original pathname (unresolved) + */ + private function resolveSymlinkedDirectorySymlink($pathname) + { + if (!is_dir($pathname)) { + return $pathname; + } + + $resolved = rtrim($pathname, '/'); + + if (!strlen($resolved)) { + return $pathname; + } + + return $resolved; + } } diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index bffea7434..a4d1255f9 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -203,4 +203,38 @@ class FilesystemTest extends TestCase $this->assertTrue($result); $this->assertFalse(file_exists($symlinked)); } + + /** + * @link https://github.com/composer/composer/issues/3144 + */ + public function testRemoveSymlinkedDirectoryWithTrailingSlash() + { + $tmp = sys_get_temp_dir(); + $basepath = $tmp . "/composer_testdir"; + @mkdir($basepath . "/real", 0777, true); + touch($basepath . "/real/FILE"); + $symlinked = $basepath . "/linked"; + $symlinkedTrailingSlash = $symlinked . "/"; + + $result = @symlink($basepath . "/real", $symlinked); + + if (!$result) { + $this->markTestSkipped('Symbolic links for directories not supported on this platform'); + } + + if (!is_dir($symlinked)) { + $this->fail('Precondition assertion failed (is_dir is false on symbolic link to directory).'); + } + + if (!is_dir($symlinkedTrailingSlash)) { + $this->fail('Precondition assertion failed (is_dir false w trailing slash).'); + } + + $fs = new Filesystem(); + + $result = $fs->removeDirectory($symlinkedTrailingSlash); + $this->assertTrue($result); + $this->assertFalse(file_exists($symlinkedTrailingSlash)); + $this->assertFalse(file_exists($symlinked)); + } }