From 24ea615d0f58b2919351301005ed715e11ad5795 Mon Sep 17 00:00:00 2001 From: michalsn Date: Wed, 24 Jun 2020 17:15:32 +0200 Subject: [PATCH 1/2] Update delete_files() helper function. Fixes #3015 --- system/Helpers/filesystem_helper.php | 41 +++++++++++-------- tests/system/Helpers/FilesystemHelperTest.php | 27 +++++++++++- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index 2556e86ade2a..8edde10b15e2 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -160,39 +160,48 @@ function write_file(string $path, string $data, string $mode = 'wb'): bool * @param string $path File path * @param boolean $del_dir Whether to delete any directories found in the path * @param boolean $htdocs Whether to skip deleting .htaccess and index page files - * @param integer $_level Current directory depth level (default: 0; internal use only) + * @param boolean $hidden Whether to include hidden files (files beginning with a period) * * @return boolean */ - function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, int $_level = 0): bool + function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, bool $hidden = false): bool { - // Trim the trailing slash - $path = rtrim($path, '/\\'); + $path = realpath($path) ?: $path; + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; try { - $current_dir = opendir($path); - - while (false !== ($filename = @readdir($current_dir))) + foreach (new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ) as $object) { - if ($filename !== '.' && $filename !== '..') + $filename = $object->getFilename(); + + if (! $hidden && $filename[0] === '.') + { + continue; + } + elseif (! $htdocs || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) { - if (is_dir($path . DIRECTORY_SEPARATOR . $filename) && $filename[0] !== '.') + $isDir = $object->isDir(); + + if ($isDir && $del_dir) { - delete_files($path . DIRECTORY_SEPARATOR . $filename, $del_dir, $htdocs, $_level + 1); + @rmdir($object->getPathname()); + continue; } - elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) + + if (! $isDir) { - @unlink($path . DIRECTORY_SEPARATOR . $filename); + @unlink($object->getPathname()); } } } - closedir($current_dir); - - return ($del_dir === true && $_level > 0) ? @rmdir($path) : true; + return true; } - catch (\Exception $fe) + catch (\Exception $e) { return false; } diff --git a/tests/system/Helpers/FilesystemHelperTest.php b/tests/system/Helpers/FilesystemHelperTest.php index a1cdba021c55..431fc123bb60 100644 --- a/tests/system/Helpers/FilesystemHelperTest.php +++ b/tests/system/Helpers/FilesystemHelperTest.php @@ -128,7 +128,7 @@ public function testDeleteFilesDefaultsToOneLevelDeep() delete_files(vfsStream::url('root')); $this->assertFalse($vfs->hasChild('simpleFile')); - $this->assertFalse($vfs->hasChild('.hidden')); + $this->assertTrue($vfs->hasChild('.hidden')); $this->assertTrue($vfs->hasChild('foo')); $this->assertTrue($vfs->hasChild('boo')); $this->assertTrue($vfs->hasChild('AnEmptyFolder')); @@ -143,7 +143,7 @@ public function testDeleteFilesHandlesRecursion() delete_files(vfsStream::url('root'), true); $this->assertFalse($vfs->hasChild('simpleFile')); - $this->assertFalse($vfs->hasChild('.hidden')); + $this->assertTrue($vfs->hasChild('.hidden')); $this->assertFalse($vfs->hasChild('foo')); $this->assertFalse($vfs->hasChild('boo')); $this->assertFalse($vfs->hasChild('AnEmptyFolder')); @@ -162,6 +162,29 @@ public function testDeleteFilesLeavesHTFiles() delete_files(vfsStream::url('root'), true, true); $this->assertFalse($vfs->hasChild('simpleFile')); + $this->assertTrue($vfs->hasChild('.hidden')); + $this->assertFalse($vfs->hasChild('foo')); + $this->assertFalse($vfs->hasChild('boo')); + $this->assertFalse($vfs->hasChild('AnEmptyFolder')); + $this->assertTrue($vfs->hasChild('.htaccess')); + $this->assertTrue($vfs->hasChild('index.html')); + $this->assertTrue($vfs->hasChild('index.php')); + } + + public function testDeleteFilesIncludingHidden() + { + $structure = array_merge($this->structure, [ + '.htaccess' => 'Deny All', + 'index.html' => 'foo', + 'index.php' => 'blah', + ]); + + $vfs = vfsStream::setup('root', null, $structure); + + delete_files(vfsStream::url('root'), true, true, true); + + $this->assertFalse($vfs->hasChild('simpleFile')); + $this->assertFalse($vfs->hasChild('.hidden')); $this->assertFalse($vfs->hasChild('foo')); $this->assertFalse($vfs->hasChild('boo')); $this->assertFalse($vfs->hasChild('AnEmptyFolder')); From 8c5d084e292aa75f156b453d978ec9efa2b512c5 Mon Sep 17 00:00:00 2001 From: michalsn Date: Wed, 24 Jun 2020 17:33:11 +0200 Subject: [PATCH 2/2] Use throwable --- system/Helpers/filesystem_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index 8edde10b15e2..0eb6851aa5a2 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -201,7 +201,7 @@ function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, return true; } - catch (\Exception $e) + catch (\Throwable $e) { return false; }