From 4e4ce282dbece5381cfda430aeb2a1299cb8cb9b Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 20 Oct 2025 13:41:25 +0800 Subject: [PATCH] Fix zip extract not strip dir bug --- src/SPC/store/FileSystem.php | 59 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 7a574925..9cbd68b7 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -584,7 +584,7 @@ class FileSystem 'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"), 'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"), 'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"), - 'zip' => f_passthru("unzip {$filename} -d {$target}"), + 'zip' => self::unzipWithStrip($filename, $target), default => throw new FileSystemException('unknown archive format: ' . $filename), }; } elseif (PHP_OS_FAMILY === 'Windows') { @@ -599,7 +599,7 @@ class FileSystem match (self::extname($filename)) { 'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"), 'xz', 'txz', 'gz', 'tgz', 'bz2' => cmd()->execWithResult("\"{$_7z}\" x -so {$filename} | tar -f - -x -C \"{$target}\" --strip-components 1"), - 'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"), + 'zip' => self::unzipWithStrip($filename, $target), default => throw new FileSystemException("unknown archive format: {$filename}"), }; } @@ -644,4 +644,59 @@ class FileSystem SPC_SOURCE_LOCAL => symlink(self::convertPath($filename), $extract_path), }; } + + /** + * Unzip file with stripping top-level directory + */ + private static function unzipWithStrip(string $zip_file, string $extract_path): void + { + $temp_dir = self::convertPath(sys_get_temp_dir() . '/spc_unzip_' . bin2hex(random_bytes(16))); + $zip_file = self::convertPath($zip_file); + $extract_path = self::convertPath($extract_path); + + // extract to temp dir + self::createDir($temp_dir); + + if (PHP_OS_FAMILY === 'Windows') { + $mute = defined('DEBUG_MODE') ? '' : ' > NUL'; + // use php-sdk-binary-tools/bin/7za.exe + $_7z = self::convertPath(getenv('PHP_SDK_PATH') . '/bin/7za.exe'); + f_passthru("\"{$_7z}\" x {$zip_file} -o{$temp_dir} -y{$mute}"); + } else { + $mute = defined('DEBUG_MODE') ? '' : ' > /dev/null'; + f_passthru("unzip \"{$zip_file}\" -d \"{$temp_dir}\"{$mute}"); + } + // scan first level dirs (relative, not recursive, include dirs) + $contents = self::scanDirFiles($temp_dir, false, true, true); + if ($contents === false) { + throw new FileSystemException('Cannot scan unzip temp dir: ' . $temp_dir); + } + // if extract path already exists, remove it + if (is_dir($extract_path)) { + self::removeDir($extract_path); + } + // if only one dir, move its contents to extract_path using rename + $subdir = self::convertPath("{$temp_dir}/{$contents[0]}"); + if (count($contents) === 1 && is_dir($subdir)) { + rename($subdir, $extract_path); + } else { + // else, move all contents to extract_path + self::createDir($extract_path); + foreach ($contents as $item) { + $subdir = self::convertPath("{$temp_dir}/{$item}"); + if (is_dir($subdir)) { + // move all dir contents to extract_path (strip top-level) + $sub_contents = self::scanDirFiles($subdir, false, true, true); + if ($sub_contents === false) { + throw new FileSystemException('Cannot scan unzip temp sub-dir: ' . $subdir); + } + foreach ($sub_contents as $sub_item) { + rename(self::convertPath("{$subdir}/{$sub_item}"), self::convertPath("{$extract_path}/{$sub_item}")); + } + } else { + rename(self::convertPath("{$temp_dir}/{$item}"), self::convertPath("{$extract_path}/{$item}")); + } + } + } + } }