diff --git a/composer.json b/composer.json index a98bd0c..a496090 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,11 @@ "bestyii/yii2-gii-rest": "*", "bestyii/yii2-openapi-reader": "dev-master", "fortawesome/font-awesome": "^6.5", - "ext-zip": "*" + "ext-zip": "*", + "ext-rar": "*", + "wapmorgan/unified-archive": "^1.2", + "symfony/console": "^6.1", + "gemorroj/archive7z": "^5.7" }, "require-dev": { "yiisoft/yii2-debug": "~2.1.0", diff --git a/controllers/HomeController.php b/controllers/HomeController.php index cb08c83..e36d44c 100644 --- a/controllers/HomeController.php +++ b/controllers/HomeController.php @@ -5,7 +5,11 @@ namespace app\controllers; use app\models\NewFolderForm; use app\models\RenameForm; use app\models\UploadForm; +use app\models\ZipForm; use app\utils\FileTypeDetector; +use wapmorgan\UnifiedArchive\Exceptions\FileAlreadyExistsException; +use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException; +use wapmorgan\UnifiedArchive\UnifiedArchive; use Yii; use yii\bootstrap5\ActiveForm; use yii\filters\VerbFilter; @@ -36,6 +40,7 @@ class HomeController extends Controller 'new-folder' => ['POST'], 'download-folder' => ['GET'], 'multi-ff-zip-dl' => ['POST'], + 'zip' => ['POST'], ], ], ] @@ -61,7 +66,7 @@ class HomeController extends Controller if ($directory === '.' || $directory == null) { $directory = null; $parentDirectory = null; - } elseif ($directory === '..' || str_contains($directory, '../')) { + } elseif (str_contains($directory, '..')) { throw new NotFoundHttpException('Invalid directory.'); } else { $parentDirectory = dirname($directory); @@ -127,7 +132,7 @@ class HomeController extends Controller $relativePath = rawurldecode($relativePath); // 检查相对路径是否只包含允许的字符 - if (!preg_match($this->pattern, $relativePath) || $relativePath === '.' || $relativePath === '..' || str_contains($relativePath, '../')) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { throw new NotFoundHttpException('Invalid file path.'); } @@ -163,7 +168,7 @@ class HomeController extends Controller $relativePath = rawurldecode($relativePath); // 检查相对路径是否只包含允许的字符 - if (!preg_match($this->pattern, $relativePath) || $relativePath === '.' || $relativePath === '..' || str_contains($relativePath, '../')) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { throw new NotFoundHttpException('Invalid file path.'); } @@ -207,7 +212,7 @@ class HomeController extends Controller { $relativePath = Yii::$app->request->post('relativePath'); $relativePath = rawurldecode($relativePath); - if (!preg_match($this->pattern, $relativePath) || $relativePath === '.' || $relativePath === '..' || str_contains($relativePath, '../')) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { throw new NotFoundHttpException('Invalid file path.'); } $absolutePath = Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $relativePath; @@ -282,7 +287,7 @@ class HomeController extends Controller foreach ($uploadedFiles as $uploadedFile) { $model->uploadFile = $uploadedFile; - if (!preg_match($this->pattern, $model->uploadFile->fullPath) || $model->uploadFile->fullPath === '.' || $model->uploadFile->fullPath === '..' || str_contains($model->uploadFile->fullPath, '../')) { + if (!preg_match($this->pattern, $model->uploadFile->fullPath) || str_contains($model->uploadFile->fullPath, '..')) { continue; } if ($model->upload()) { @@ -360,7 +365,7 @@ class HomeController extends Controller public function actionDownloadFolder($relativePath) { $relativePath = rawurldecode($relativePath); - if (!preg_match($this->pattern, $relativePath) || $relativePath === '.' || $relativePath === '..' || str_contains($relativePath, '../')) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { throw new NotFoundHttpException('Invalid path.'); } $absolutePath = Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $relativePath; @@ -416,7 +421,7 @@ class HomeController extends Controller foreach ($relativePaths as $relativePath) { // 检查相对路径是否只包含允许的字符 - if (!preg_match($this->pattern, $relativePath) || $relativePath === '.' || $relativePath === '..' || str_contains($relativePath, '../')) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { throw new NotFoundHttpException('Invalid file path.'); } @@ -451,15 +456,12 @@ class HomeController extends Controller $absolutePath = Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $relativePath; if (is_file($absolutePath)) { - // 如果是文件,直接添加到 zip 文件 $zip->addFile($absolutePath, $relativePath); } else if (is_dir($absolutePath)) { - // 如果是目录,获取目录中的所有文件并添加到 zip 文件 $files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($absolutePath), \RecursiveIteratorIterator::LEAVES_ONLY ); - foreach ($files as $name => $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); @@ -470,11 +472,43 @@ class HomeController extends Controller } } } - $zip->close(); } - - // 发送下载 return Yii::$app->response->sendFile($zipPath); } + + public function actionZip() + { + $model = new ZipForm(); + $relativePaths = json_decode(Yii::$app->request->post('relativePath'), true); + $targetDirectory = Yii::$app->request->post('targetDirectory')??'.'; + if ($model->load(Yii::$app->request->post()) && $model->validate()) { + if (str_contains($targetDirectory, '..')) { + throw new NotFoundHttpException('Invalid target directory.'); + } elseif (!is_dir(Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $targetDirectory)) { + throw new NotFoundHttpException('Directory not found.'); + } + $absolutePaths = []; + foreach ($relativePaths as $relativePath) { + if (!preg_match($this->pattern, $relativePath) || str_contains($relativePath, '..')) { + continue; + } + $absolutePath = Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $relativePath; + if (!file_exists($absolutePath)) { + throw new NotFoundHttpException('The requested file does not exist.'); + } + $absolutePaths[] = $absolutePath; + } + $zipPath = Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id . '/' . $targetDirectory . '/' . $model->zipFilename . '.' . $model->zipFormat; + try { + UnifiedArchive::create($absolutePaths, $zipPath); + Yii::$app->session->setFlash('success', '打包成功'); + } catch (FileAlreadyExistsException $e) { + Yii::$app->session->setFlash('error', '目标文件夹已存在同名压缩档案'); + } catch (UnsupportedOperationException $e) { + Yii::$app->session->setFlash('error', '不支持的操作'); + } + } + return $this->redirect(['index', 'directory' => $targetDirectory]); + } } diff --git a/models/NewFolderForm.php b/models/NewFolderForm.php index 1b38401..413106b 100644 --- a/models/NewFolderForm.php +++ b/models/NewFolderForm.php @@ -24,7 +24,7 @@ class NewFolderForm extends Model public function validateRelativePath($attribute, $params, $validator) { - if (str_contains($this->$attribute, '../') || str_contains($this->$attribute, '..')) { + if (str_contains($this->$attribute, '..')) { $this->addError($attribute, 'Invalid file path.'); } } diff --git a/models/ZipForm.php b/models/ZipForm.php new file mode 100644 index 0000000..fc4e1a0 --- /dev/null +++ b/models/ZipForm.php @@ -0,0 +1,30 @@ + 255], + ['zipFormat', 'in', 'range' => ['zip', '7z']], + ['zipFilename', 'match', 'pattern' => '/^[^\p{C}\/:*?"<>|\\\\]+$/u', 'message' => 'Invalid file name.'], + ]; + } + + public function attributeLabels() + { + return [ + 'zipFilename' => '压缩文件名', + 'zipFormat' => '压缩格式', + ]; + } +} \ No newline at end of file diff --git a/views/home/index.php b/views/home/index.php index 81f3c2a..d58544b 100644 --- a/views/home/index.php +++ b/views/home/index.php @@ -8,6 +8,7 @@ use app\models\NewFolderForm; use app\models\RenameForm; +use app\models\ZipForm; use yii\bootstrap5\ActiveForm; use yii\bootstrap5\Html; use app\assets\FontAwesomeAsset; @@ -211,6 +212,22 @@ echo Html::submitButton('提交', ['class' => 'btn btn-primary']); ActiveForm::end(); Modal::end(); +Modal::begin([ + 'title' => '