service = $this->app->make(LanguagePackService::class); $this->tempDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('lp_security_', true); File::ensureDirectoryExists($this->tempDir); } protected function tearDown(): void { File::deleteDirectory($this->tempDir); // _pending 루트는 보존하고 (Git 추적 .gitignore/.gitkeep 유지) 서브디렉토리만 정리 $pendingRoot = base_path('lang-packs/_pending'); if (File::isDirectory($pendingRoot)) { foreach (File::directories($pendingRoot) as $sub) { File::deleteDirectory($sub); } } parent::tearDown(); } /** * 주어진 콘텐츠로 ZIP 파일을 만듭니다. * * @param array $files 파일명 ⇒ 내용 * @return string ZIP 파일 절대 경로 */ private function makeZip(array $files): string { $zipPath = $this->tempDir.DIRECTORY_SEPARATOR.uniqid('pack_', true).'.zip'; $zip = new ZipArchive(); $zip->open($zipPath, ZipArchive::CREATE); foreach ($files as $name => $content) { $zip->addFromString($name, $content); } $zip->close(); return $zipPath; } /** * 유효한 manifest JSON 문자열을 반환합니다. * * @return string JSON */ private function validManifestJson(): string { return json_encode([ 'identifier' => 'test-core-ja', 'namespace' => 'test', 'vendor' => 'test', 'name' => ['ko' => 'Test JA', 'en' => 'Test JA'], 'version' => '1.0.0', 'scope' => 'core', 'target_identifier' => null, 'locale' => 'ja', 'locale_native_name' => '日本語', 'g7_version' => '>=7.0.0-beta.4', ]); } public function test_php_outside_backend_directory_rejected(): void { $zip = $this->makeZip([ 'language-pack.json' => $this->validManifestJson(), 'evil.php' => "expectException(\RuntimeException::class); $this->expectExceptionMessageMatches('/backend/i'); $this->service->installFromFile($file, false, null); } public function test_unsafe_php_pattern_in_backend_rejected(): void { $zip = $this->makeZip([ 'language-pack.json' => $this->validManifestJson(), 'backend/messages.php' => "expectException(\RuntimeException::class); $this->expectExceptionMessageMatches('/허용되지 않/u'); $this->service->installFromFile($file, false, null); } public function test_safe_backend_php_accepted(): void { $zip = $this->makeZip([ 'language-pack.json' => $this->validManifestJson(), 'backend/messages.php' => " 'こんにちは'];\n", ]); $file = new UploadedFile($zip, 'pack.zip', 'application/zip', null, true); $pack = $this->service->installFromFile($file, false, null); $this->assertSame('test-core-ja', $pack->identifier); // 활성 디렉토리 정리 File::deleteDirectory(base_path('lang-packs/test-core-ja')); } }