50 lines
1.3 KiB
Python
50 lines
1.3 KiB
Python
"""对象存储 key 生成。
|
|
|
|
布局:users/{user_id}/kbs/{kb_id}/original/{rand8}_{safe_filename}
|
|
users/{user_id}/kbs/{kb_id}/markdown/{rand8}.md
|
|
|
|
规则(需求 §27 / §23):
|
|
- 不信任用户文件名:取 basename,剔除路径穿越与控制字符,截断长度。
|
|
- 主体是内部 UUID,公共 URL 使用 token,绝不回显此 key。
|
|
"""
|
|
|
|
import re
|
|
import secrets
|
|
import uuid
|
|
|
|
_SAFE_NAME_RE = re.compile(r"[^A-Za-z0-9._\-]+")
|
|
_MAX_FILENAME_LEN = 120
|
|
|
|
|
|
def sanitize_filename(original: str) -> str:
|
|
name = original.replace("\\", "/").split("/")[-1]
|
|
name = _SAFE_NAME_RE.sub("_", name).strip("._")
|
|
if not name:
|
|
name = "file"
|
|
return name[:_MAX_FILENAME_LEN]
|
|
|
|
|
|
def original_object_key(
|
|
*,
|
|
user_id: str,
|
|
knowledge_base_id: str,
|
|
document_id: str,
|
|
original_filename: str,
|
|
) -> str:
|
|
rand = secrets.token_urlsafe(6) # ~8 字符
|
|
safe = sanitize_filename(original_filename)
|
|
return (
|
|
f"users/{user_id}/kbs/{knowledge_base_id}/docs/{document_id}/original/{rand}_{safe}"
|
|
)
|
|
|
|
|
|
def markdown_object_key(
|
|
*,
|
|
user_id: str,
|
|
knowledge_base_id: str,
|
|
document_id: str,
|
|
) -> str:
|
|
rand = secrets.token_urlsafe(6)
|
|
return (
|
|
f"users/{user_id}/kbs/{knowledge_base_id}/docs/{document_id}/markdown/{rand}.md"
|
|
) |