From c9e27a895a56a504b48a12d5d96dba5c7107b0ee Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 9 Oct 2023 21:56:49 +0200 Subject: [PATCH] Fix removing directories with symlinks inside --- src/main/php/io/Folder.class.php | 31 +++++----- src/test/php/io/unittest/FolderTest.class.php | 58 ++++++++++++++++++- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/main/php/io/Folder.class.php b/src/main/php/io/Folder.class.php index 95f764d042..c808e3ea7b 100755 --- a/src/main/php/io/Folder.class.php +++ b/src/main/php/io/Folder.class.php @@ -144,27 +144,26 @@ public function create($permissions= 0700) { * @throws io.IOException in case one of the entries could'nt be deleted */ public function unlink($uri= null) { - if (null === $uri) $uri= $this->uri; // We also use this recursively - - if (false === ($d= dir($uri))) { + $uri= null === $uri ? $this->uri : rtrim($uri, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + if (false === ($d= opendir($uri))) { throw new IOException('Directory '.$uri.' does not exist'); } - - while (false !== ($e= $d->read())) { - if ('.' == $e || '..' == $e) continue; - - $fn= $d->path.$e; - if (!is_dir($fn)) { - $ret= unlink($fn); - } else { - $ret= $this->unlink($fn.DIRECTORY_SEPARATOR); + + while (false !== ($e= readdir($d))) { + if ('.' === $e || '..' === $e) continue; + + // Recurse into subdirectories (but not if they are symlinks!) + $fn= $uri.$e; + if (0x4000 === (lstat($fn)['mode'] & 0xf000)) { + $this->unlink($fn); + } else if (false === unlink($fn)) { + closedir($d); + throw new IOException("Deleting '{$fn}' failed"); } - if (false === $ret) throw new IOException(sprintf('unlink of "%s" failed', $fn)); } - $d->close(); + closedir($d); - if (false === rmdir($uri)) throw new IOException(sprintf('unlink of "%s" failed', $uri)); - + if (false === rmdir($uri)) throw new IOException("Deleting '{$uri}' failed"); return true; } diff --git a/src/test/php/io/unittest/FolderTest.class.php b/src/test/php/io/unittest/FolderTest.class.php index e71281107c..fa5265d533 100755 --- a/src/test/php/io/unittest/FolderTest.class.php +++ b/src/test/php/io/unittest/FolderTest.class.php @@ -1,7 +1,8 @@ exists()); } + #[Test, Expect(IOException::class)] + public function unlink_nonexistant() { + $f= new Folder($this->tempFolder()); + $f->unlink(); + } + + #[Test] + public function unlink_with_subdir() { + $f= new Folder($this->tempFolder()); + $f->create(); + + $s= new Folder($f, 'sub'); + $s->create(); + + $f->unlink(); + Assert::false($f->exists()); + } + + #[Test] + public function unlink_with_file() { + $f= new Folder($this->tempFolder()); + $f->create(); + + Files::write(new File($f, 'file'), 'Test'); + + $f->unlink(); + Assert::false($f->exists()); + } + + #[Test, Runtime(os: 'Linux')] + public function unlink_with_file_symlink() { + $f= new Folder($this->tempFolder()); + $f->create(); + + $target= new File($f, '.default.ini'); + Files::write($target, 'key=value'); + symlink('.default.ini', $f->getURI().'config.ini'); + + $f->unlink(); + Assert::false($f->exists()); + } + + #[Test, Runtime(os: 'Linux')] + public function unlink_with_folder_symlink() { + $f= new Folder($this->tempFolder()); + $f->create(); + + $target= new Folder($f, '.default'); + $target->create(); + symlink('.default', $f->getURI().'config'); + + $f->unlink(); + Assert::false($f->exists()); + } + #[Test] public function uriOfNonExistantFolder() { $temp= $this->tempFolder();