mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 14:22:55 +08:00
✨ 新增路径验证功能,确保用户输入的路径安全并在项目根目录内
This commit is contained in:
parent
005cafb90d
commit
07ba035db6
@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
@ -10,7 +9,7 @@ from fastapi.responses import JSONResponse
|
|||||||
from zhenxun.utils._build_image import BuildImage
|
from zhenxun.utils._build_image import BuildImage
|
||||||
|
|
||||||
from ....base_model import Result, SystemFolderSize
|
from ....base_model import Result, SystemFolderSize
|
||||||
from ....utils import authentication, get_system_disk
|
from ....utils import authentication, get_system_disk, validate_path
|
||||||
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
|
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
|
||||||
|
|
||||||
router = APIRouter(prefix="/system")
|
router = APIRouter(prefix="/system")
|
||||||
@ -27,17 +26,11 @@ IMAGE_TYPE = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"]
|
|||||||
)
|
)
|
||||||
async def _(path: str | None = None) -> Result[list[DirFile]]:
|
async def _(path: str | None = None) -> Result[list[DirFile]]:
|
||||||
try:
|
try:
|
||||||
# 清理和验证路径
|
base_path, error = validate_path(path)
|
||||||
if path:
|
if error:
|
||||||
# 移除任何可能的路径遍历尝试
|
return Result.fail(error)
|
||||||
path = re.sub(r"[\\/]\.\.[\\/]", "", path)
|
if not base_path:
|
||||||
# 规范化路径
|
return Result.fail("无效的路径")
|
||||||
base_path = Path(path).resolve()
|
|
||||||
# 验证路径是否在项目根目录内
|
|
||||||
if not base_path.is_relative_to(Path().resolve()):
|
|
||||||
return Result.fail("访问路径超出允许范围")
|
|
||||||
else:
|
|
||||||
base_path = Path().resolve()
|
|
||||||
|
|
||||||
data_list = []
|
data_list = []
|
||||||
for file in os.listdir(base_path):
|
for file in os.listdir(base_path):
|
||||||
@ -77,8 +70,12 @@ async def _(full_path: str | None = None) -> Result[list[SystemFolderSize]]:
|
|||||||
description="删除文件",
|
description="删除文件",
|
||||||
)
|
)
|
||||||
async def _(param: DeleteFile) -> Result:
|
async def _(param: DeleteFile) -> Result:
|
||||||
path = Path(param.full_path)
|
path, error = validate_path(param.full_path)
|
||||||
if not path or not path.exists():
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
path.unlink()
|
path.unlink()
|
||||||
@ -95,8 +92,12 @@ async def _(param: DeleteFile) -> Result:
|
|||||||
description="删除文件夹",
|
description="删除文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: DeleteFile) -> Result:
|
async def _(param: DeleteFile) -> Result:
|
||||||
path = Path(param.full_path)
|
path, error = validate_path(param.full_path)
|
||||||
if not path or not path.exists() or path.is_file():
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
if not path.exists() or path.is_file():
|
||||||
return Result.warning_("文件夹不存在...")
|
return Result.warning_("文件夹不存在...")
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(path.absolute())
|
shutil.rmtree(path.absolute())
|
||||||
@ -113,10 +114,14 @@ async def _(param: DeleteFile) -> Result:
|
|||||||
description="重命名文件",
|
description="重命名文件",
|
||||||
)
|
)
|
||||||
async def _(param: RenameFile) -> Result:
|
async def _(param: RenameFile) -> Result:
|
||||||
path = (
|
parent_path, error = validate_path(param.parent)
|
||||||
(Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
|
if error:
|
||||||
)
|
return Result.fail(error)
|
||||||
if not path or not path.exists():
|
if not parent_path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
|
||||||
|
path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
|
||||||
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
path.rename(path.parent / param.name)
|
path.rename(path.parent / param.name)
|
||||||
@ -133,10 +138,14 @@ async def _(param: RenameFile) -> Result:
|
|||||||
description="重命名文件夹",
|
description="重命名文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: RenameFile) -> Result:
|
async def _(param: RenameFile) -> Result:
|
||||||
path = (
|
parent_path, error = validate_path(param.parent)
|
||||||
(Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
|
if error:
|
||||||
)
|
return Result.fail(error)
|
||||||
if not path or not path.exists() or path.is_file():
|
if not parent_path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
|
||||||
|
path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
|
||||||
|
if not path.exists() or path.is_file():
|
||||||
return Result.warning_("文件夹不存在...")
|
return Result.warning_("文件夹不存在...")
|
||||||
try:
|
try:
|
||||||
new_path = path.parent / param.name
|
new_path = path.parent / param.name
|
||||||
@ -154,7 +163,13 @@ async def _(param: RenameFile) -> Result:
|
|||||||
description="新建文件",
|
description="新建文件",
|
||||||
)
|
)
|
||||||
async def _(param: AddFile) -> Result:
|
async def _(param: AddFile) -> Result:
|
||||||
path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
|
parent_path, error = validate_path(param.parent)
|
||||||
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not parent_path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
|
||||||
|
path = (parent_path / param.name) if param.parent else Path(param.name)
|
||||||
if path.exists():
|
if path.exists():
|
||||||
return Result.warning_("文件已存在...")
|
return Result.warning_("文件已存在...")
|
||||||
try:
|
try:
|
||||||
@ -172,7 +187,13 @@ async def _(param: AddFile) -> Result:
|
|||||||
description="新建文件夹",
|
description="新建文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: AddFile) -> Result:
|
async def _(param: AddFile) -> Result:
|
||||||
path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
|
parent_path, error = validate_path(param.parent)
|
||||||
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not parent_path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
|
|
||||||
|
path = (parent_path / param.name) if param.parent else Path(param.name)
|
||||||
if path.exists():
|
if path.exists():
|
||||||
return Result.warning_("文件夹已存在...")
|
return Result.warning_("文件夹已存在...")
|
||||||
try:
|
try:
|
||||||
@ -190,7 +211,11 @@ async def _(param: AddFile) -> Result:
|
|||||||
description="读取文件",
|
description="读取文件",
|
||||||
)
|
)
|
||||||
async def _(full_path: str) -> Result:
|
async def _(full_path: str) -> Result:
|
||||||
path = Path(full_path)
|
path, error = validate_path(full_path)
|
||||||
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
@ -208,9 +233,13 @@ async def _(full_path: str) -> Result:
|
|||||||
description="读取文件",
|
description="读取文件",
|
||||||
)
|
)
|
||||||
async def _(param: SaveFile) -> Result[str]:
|
async def _(param: SaveFile) -> Result[str]:
|
||||||
path = Path(param.full_path)
|
path, error = validate_path(param.full_path)
|
||||||
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
try:
|
try:
|
||||||
async with aiofiles.open(path, "w", encoding="utf-8") as f:
|
async with aiofiles.open(str(path), "w", encoding="utf-8") as f:
|
||||||
await f.write(param.content)
|
await f.write(param.content)
|
||||||
return Result.ok("更新成功!")
|
return Result.ok("更新成功!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -225,7 +254,11 @@ async def _(param: SaveFile) -> Result[str]:
|
|||||||
description="读取图片base64",
|
description="读取图片base64",
|
||||||
)
|
)
|
||||||
async def _(full_path: str) -> Result[str]:
|
async def _(full_path: str) -> Result[str]:
|
||||||
path = Path(full_path)
|
path, error = validate_path(full_path)
|
||||||
|
if error:
|
||||||
|
return Result.fail(error)
|
||||||
|
if not path:
|
||||||
|
return Result.fail("无效的路径")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import contextlib
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
@ -28,6 +29,31 @@ if token_file.exists():
|
|||||||
token_data = json.load(open(token_file, encoding="utf8"))
|
token_data = json.load(open(token_file, encoding="utf8"))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_path(path_str: str | None) -> tuple[Path | None, str | None]:
|
||||||
|
"""验证路径是否安全
|
||||||
|
|
||||||
|
参数:
|
||||||
|
path_str: 用户输入的路径
|
||||||
|
|
||||||
|
返回:
|
||||||
|
tuple[Path | None, str | None]: (验证后的路径, 错误信息)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not path_str:
|
||||||
|
return Path().resolve(), None
|
||||||
|
|
||||||
|
# 移除任何可能的路径遍历尝试
|
||||||
|
path_str = re.sub(r"[\\/]\.\.[\\/]", "", path_str)
|
||||||
|
# 规范化路径
|
||||||
|
path = Path(path_str).resolve()
|
||||||
|
# 验证路径是否在项目根目录内
|
||||||
|
if not path.is_relative_to(Path().resolve()):
|
||||||
|
return None, "访问路径超出允许范围"
|
||||||
|
return path, None
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"路径验证失败: {e!s}"
|
||||||
|
|
||||||
|
|
||||||
def get_user(uname: str) -> User | None:
|
def get_user(uname: str) -> User | None:
|
||||||
"""获取账号密码
|
"""获取账号密码
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user