diff --git a/app/adapters/system/plugin/package.py b/app/adapters/system/plugin/package.py index 90d4730aa..3565b3581 100644 --- a/app/adapters/system/plugin/package.py +++ b/app/adapters/system/plugin/package.py @@ -1232,10 +1232,10 @@ class PluginPackageManager: elif res.status_code != 200: return False, f"下载文件 {item.get('path')} 失败:{res.status_code}" - # 创建插件文件夹并写入文件 + # GitHub 文件列表同时承载 Python 源码和 wheel 等二进制制品,统一按原始字节写入 file_path.parent.mkdir(parents=True, exist_ok=True) - with open(file_path, "w", encoding="utf-8") as f: - f.write(res.text) + with open(file_path, "wb") as f: + f.write(res.content) logger.debug(f"文件 {item.get('path')} 下载成功,保存路径:{file_path}") else: # 如果是子目录,则将子目录内容加入栈中继续处理 @@ -1674,11 +1674,11 @@ class PluginPackageManager: elif res.status_code != 200: return False, f"下载文件 {item.get('path')} 失败:{res.status_code}" - # 创建插件文件夹并写入文件 + # GitHub 文件列表同时承载 Python 源码和 wheel 等二进制制品,统一按原始字节写入 file_path = AsyncPath(resolved_path) await file_path.parent.mkdir(parents=True, exist_ok=True) - async with aiofiles.open(file_path, "w", encoding="utf-8") as f: - await f.write(res.text) + async with aiofiles.open(file_path, "wb") as f: + await f.write(res.content) logger.debug(f"文件 {item.get('path')} 下载成功,保存路径:{file_path}") else: # 如果是子目录,则将子目录内容加入栈中继续处理 diff --git a/tests/test_plugin_package_manager.py b/tests/test_plugin_package_manager.py index 5efbf39ac..1cff91503 100644 --- a/tests/test_plugin_package_manager.py +++ b/tests/test_plugin_package_manager.py @@ -182,13 +182,14 @@ async def test_file_list_download_rejects_traversal_directory_names( @pytest.mark.asyncio -async def test_file_list_download_maps_valid_paths_into_injected_plugin_root( +async def test_file_list_download_preserves_binary_payload_in_injected_plugin_root( monkeypatch, tmp_path, ): - """同步与异步文件列表都只写入显式装配的插件根目录。""" + """同步与异步文件列表都应在受控根目录内保留原始文件字节。""" plugin_root = tmp_path / "plugins" - response = SimpleNamespace(status_code=200, text="payload") + payload = b"\x00\xffwheel-payload" + response = SimpleNamespace(status_code=200, content=payload, text="wrong-text") manager = PluginPackageManager(source=Mock(), plugin_root=plugin_root) monkeypatch.setattr( manager, @@ -211,9 +212,7 @@ async def test_file_list_download_maps_valid_paths_into_injected_plugin_root( assert await manager._PluginPackageManager__async_download_files( "DemoPlugin", [item], "owner/repo", "v2" ) == (True, "") - assert (plugin_root / "demoplugin" / "nested" / "file.py").read_text( - encoding="utf-8" - ) == "payload" + assert (plugin_root / "demoplugin" / "nested" / "file.py").read_bytes() == payload def test_checkpoint_does_not_scan_native_dependencies(monkeypatch, tmp_path):