]> BookStack Code Mirror - system-cli/commitdiff
Added support for key restore files/folders being symlinks
authorDan Brown <redacted>
Sat, 6 May 2023 13:52:43 +0000 (14:52 +0100)
committerDan Brown <redacted>
Sat, 6 May 2023 13:52:43 +0000 (14:52 +0100)
Fixes #4
Added test to cover.

src/Commands/RestoreCommand.php
src/Services/Paths.php
tests/Commands/RestoreCommandTest.php

index f46d571d23cb06aa9a50c43efa66dd02354d7fbe..04ea1bb9ebc655ae675835b0739dfa87552b9ecb 100644 (file)
@@ -8,6 +8,7 @@ use Cli\Services\BackupZip;
 use Cli\Services\EnvironmentLoader;
 use Cli\Services\InteractiveConsole;
 use Cli\Services\MySqlRunner;
+use Cli\Services\Paths;
 use Cli\Services\ProgramRunner;
 use Cli\Services\RequirementsValidator;
 use Exception;
@@ -74,7 +75,7 @@ class RestoreCommand extends Command
         }
 
         $output->writeln("<info>Extracting ZIP into temporary directory...</info>");
-        $extractDir = $appDir . DIRECTORY_SEPARATOR . 'restore-temp-' . time();
+        $extractDir = Paths::join($appDir, 'restore-temp-' . time());
         if (!mkdir($extractDir)) {
             throw new CommandError("Could not create temporary extraction directory at [{$extractDir}].");
         }
@@ -130,8 +131,8 @@ class RestoreCommand extends Command
     {
         $oldEnv = EnvironmentLoader::load($extractDir);
         $currentEnv = EnvironmentLoader::load($appDir);
-        $envContents = file_get_contents($extractDir . DIRECTORY_SEPARATOR . '.env');
-        $appEnvPath = $appDir . DIRECTORY_SEPARATOR . '.env';
+        $envContents = file_get_contents(Paths::join($extractDir, '.env'));
+        $appEnvPath = Paths::real(Paths::join($appDir, '.env'));
 
         $mysqlCurrent = MySqlRunner::fromEnvOptions($currentEnv);
         $mysqlOld = MySqlRunner::fromEnvOptions($oldEnv);
@@ -171,16 +172,16 @@ class RestoreCommand extends Command
             $returnData['new_url'] = $changedUrl;
         }
 
-        file_put_contents($appDir . DIRECTORY_SEPARATOR . '.env', $envContents);
+        file_put_contents($appEnvPath, $envContents);
 
         return $returnData;
     }
 
     protected function restoreFolder(string $folderSubPath, string $appDir, string $extractDir): void
     {
-        $fullAppFolderPath = $appDir . DIRECTORY_SEPARATOR . $folderSubPath;
+        $fullAppFolderPath = Paths::real(Paths::join($appDir, $folderSubPath));
         $this->deleteDirectoryAndContents($fullAppFolderPath);
-        rename($extractDir . DIRECTORY_SEPARATOR . $folderSubPath, $fullAppFolderPath);
+        rename(Paths::join($extractDir, $folderSubPath), $fullAppFolderPath);
     }
 
     protected function deleteDirectoryAndContents(string $dir): void
@@ -200,7 +201,7 @@ class RestoreCommand extends Command
 
     protected function restoreDatabase(string $appDir, string $extractDir): void
     {
-        $dbDump = $extractDir . DIRECTORY_SEPARATOR . 'db.sql';
+        $dbDump = Paths::join($extractDir, 'db.sql');
         $currentEnv = EnvironmentLoader::load($appDir);
         $mysql = MySqlRunner::fromEnvOptions($currentEnv);
 
index d0bb10fa26b8bce6576ff7c9191204b741bcc872..a258baa157341a72dd04618e5c221eb57631d68d 100644 (file)
@@ -5,6 +5,21 @@ namespace Cli\Services;
 class Paths
 {
 
+    /**
+     * Get the full real path, resolving symbolic links, to
+     * the existing file/directory of the given path.
+     * @throws \Exception
+     */
+    public static function real(string $path): string
+    {
+        $real = realpath($path);
+        if ($real === false) {
+            throw new \Exception("Path {$path} could not be resolved to a location on the filesystem");
+        }
+
+        return $real;
+    }
+
     /**
      * Join together the given path components.
      * Does no resolving or cleaning.
index a8ff2b45a78e142f2d3843b30b859189bd8fc710..df70d16ae1f14000ef0fbf70cfc333d5e21d3aab 100644 (file)
@@ -29,11 +29,8 @@ class RestoreCommandTest extends TestCase
 
         $result = $this->runCommand('restore', [
             'backup-zip' => $zipFile,
-        ], [
-            'yes', '1'
-        ]);
+        ], ['yes', '1']);
 
-        $result->dumpError();
         $result->assertSuccessfulExit();
         $result->assertStdoutContains('✔ .env Config File');
         $result->assertStdoutContains('✔ Themes Folder');
@@ -98,6 +95,44 @@ class RestoreCommandTest extends TestCase
         $mysql->query("DROP TABLE zz_testing;");
     }
 
+    public function test_restore_with_symlinked_content_folders()
+    {
+        $zipFile = $this->buildZip(function (\ZipArchive $zip) {
+            $zip->addFromString('.env', "APP_KEY=abc123\nAPP_URL=https://example.com");
+            $zip->addFromString('public/uploads/test.txt', 'hello-public-uploads');
+            $zip->addFromString('storage/uploads/test.txt', 'hello-storage-uploads');
+            $zip->addFromString('themes/test.txt', 'hello-themes');
+        });
+
+        exec('cp -r /var/www/bookstack /var/www/bookstack-symlink-restore');
+        chdir('/var/www/bookstack-symlink-restore');
+        mkdir('/symlinks');
+
+        $symlinkPaths = ['public/uploads', 'storage/uploads', '.env', 'themes'];
+        foreach ($symlinkPaths as $path) {
+            $targetFile = str_replace('/', '-', $path);
+            $code = 0;
+            $output = null;
+            exec("mv /var/www/bookstack-symlink-restore/{$path} /symlinks/{$targetFile}", $output, $code);
+            exec("ln -s /symlinks/{$targetFile} /var/www/bookstack-symlink-restore/{$path}", $output, $code);
+            if ($code !== 0) {
+                $this->fail("Error when setting up symlinks");
+            }
+        }
+
+        $result = $this->runCommand('restore', [
+            'backup-zip' => $zipFile,
+        ], ['yes', '1']);
+
+        $result->assertSuccessfulExit();
+
+        $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/public/uploads/test.txt', 'hello-public-uploads');
+        $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/storage/uploads/test.txt', 'hello-storage-uploads');
+        $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/themes/test.txt', 'hello-themes');
+
+        exec('rm -rf /var/www/bookstack-symlink-restore');
+    }
+
     protected function buildZip(callable $builder): string
     {
         $zipFile = tempnam(sys_get_temp_dir(), 'cli-test');