Skip to content

Commit eeb70c7

Browse files
committed
fix: #349
1 parent 136ea58 commit eeb70c7

File tree

4 files changed

+50
-14
lines changed

4 files changed

+50
-14
lines changed

1

Whitespace-only changes.

apps/base/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99
from apps.base.dependencies import IPRateLimit
1010
from apps.base.models import FileCodes
1111
from core.settings import settings
12-
from core.utils import get_random_num, get_random_string, max_save_times_desc
12+
from core.utils import get_random_num, get_random_string, max_save_times_desc, sanitize_filename
1313

1414

1515
async def get_file_path_name(file: UploadFile) -> Tuple[str, str, str, str, str]:
1616
"""获取文件路径和文件名"""
1717
today = datetime.datetime.now()
1818
storage_path = settings.storage_path.strip("/") # 移除开头和结尾的斜杠
1919
file_uuid = uuid.uuid4().hex
20-
20+
filename = await sanitize_filename(file.filename)
2121
# 使用 UUID 作为子目录名
2222
base_path = f"share/data/{today.strftime('%Y/%m/%d')}/{file_uuid}"
2323

2424
# 如果设置了存储路径,将其添加到基础路径中
2525
path = f"{storage_path}/{base_path}" if storage_path else base_path
2626

27-
prefix, suffix = os.path.splitext(file.filename)
27+
prefix, suffix = os.path.splitext(filename)
2828
# 保持原始文件名
29-
save_path = f"{path}/{file.filename}"
30-
return path, suffix, prefix, file.filename, save_path
29+
save_path = f"{path}/{filename}"
30+
return path, suffix, prefix, filename, save_path
3131

3232

3333
async def get_chunk_file_path_name(file_name: str, upload_id: str) -> Tuple[str, str, str, str, str]:

core/storage.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from core.response import APIResponse
2323
from core.settings import data_root, settings
2424
from apps.base.models import FileCodes, UploadChunk
25-
from core.utils import get_file_url
25+
from core.utils import get_file_url, sanitize_filename
2626
from fastapi.responses import FileResponse
2727

2828

@@ -109,10 +109,16 @@ def _save(self, file, save_path):
109109
chunk = file.read(self.chunk_size)
110110

111111
async def save_file(self, file: UploadFile, save_path: str):
112-
save_path = self.root_path / save_path
113-
if not save_path.parent.exists():
114-
save_path.parent.mkdir(parents=True)
115-
await asyncio.to_thread(self._save, file.file, save_path)
112+
path_obj = Path(save_path)
113+
directory = str(path_obj.parent)
114+
# 提取原始文件名并进行清理
115+
filename = await sanitize_filename(path_obj.name)
116+
# 构建安全的完整保存路径
117+
safe_save_path = self.root_path / directory / filename
118+
# 确保目录存在
119+
if not safe_save_path.parent.exists():
120+
safe_save_path.parent.mkdir(parents=True)
121+
await asyncio.to_thread(self._save, file.file, safe_save_path)
116122

117123
async def delete_file(self, file_code: FileCodes):
118124
save_path = self.root_path / await file_code.get_file_path()
@@ -447,7 +453,7 @@ def _get_path_str(self, path):
447453

448454
def _save(self, file, save_path):
449455
content = file.file.read()
450-
name = file.filename
456+
name = save_path(file.filename)
451457
path = self._get_path_str(save_path)
452458
self.root_path.get_by_path(path).upload(name, content).execute_query()
453459

@@ -627,16 +633,20 @@ async def _delete_empty_dirs(self, file_path: str, session: aiohttp.ClientSessio
627633

628634
async def save_file(self, file: UploadFile, save_path: str):
629635
"""保存文件(自动创建目录)"""
630-
# 分离文件名和目录路径
631636
path_obj = Path(save_path)
632637
directory_path = str(path_obj.parent)
638+
# 提取原始文件名并进行清理
639+
filename = await sanitize_filename(path_obj.name)
640+
# 构建安全的保存路径
641+
safe_save_path = str(Path(directory_path) / filename)
642+
633643
try:
634644
# 先创建目录结构
635645
await self._mkdir_p(directory_path)
636646
# 上传文件
637-
url = self._build_url(save_path)
647+
url = self._build_url(safe_save_path)
638648
async with aiohttp.ClientSession(auth=self.auth) as session:
639-
content = await file.read() # 注意:大文件需要分块读取
649+
content = await file.read()
640650
async with session.put(
641651
url, data=content, headers={
642652
"Content-Type": file.content_type}

core/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
# @Software: PyCharm
55
import datetime
66
import hashlib
7+
import os
78
import random
9+
import re
810
import string
911
import time
1012
from core.settings import settings
@@ -92,3 +94,27 @@ def gen_desc_en(value: int, desc: str):
9294
desc_zh += gen_desc_zh(max_timedelta.seconds % 60, "秒")
9395
desc_en += gen_desc_en(max_timedelta.seconds % 60, "second")
9496
return desc_zh, desc_en
97+
98+
99+
async def sanitize_filename(filename: str) -> str:
100+
"""
101+
安全处理文件名:
102+
1. 剥离路径只保留文件名
103+
2. 替换非法字符
104+
3. 处理空文件名情况
105+
"""
106+
filename = os.path.basename(filename)
107+
illegal_chars = r'[\\/*?:"<>|\x00-\x1F]' # 包含控制字符
108+
# 替换非法字符为下划线
109+
cleaned = re.sub(illegal_chars, '_', filename)
110+
# 处理空格(可选替换为_)
111+
cleaned = cleaned.replace(' ', '_')
112+
# 处理连续下划线
113+
cleaned = re.sub(r'_+', '_', cleaned)
114+
# 处理首尾特殊字符
115+
cleaned = cleaned.strip('._')
116+
# 处理空文件名情况
117+
if not cleaned:
118+
cleaned = 'unnamed_file'
119+
# 长度限制(按需调整)
120+
return cleaned[:255]

0 commit comments

Comments
 (0)