a4fc92e803
Co-authored-by: Soulter <905617992@qq.com>
103 lines
2.8 KiB
Python
103 lines
2.8 KiB
Python
"""Dashboard 路由工具集。
|
||
|
||
这里放一些 dashboard routes 可复用的小工具函数。
|
||
|
||
目前主要用于「配置文件上传(file 类型配置项)」功能:
|
||
- 清洗/规范化用户可控的文件名与相对路径
|
||
- 将配置 key 映射到配置项独立子目录
|
||
"""
|
||
|
||
import os
|
||
|
||
|
||
def get_schema_item(schema: dict | None, key_path: str) -> dict | None:
|
||
"""按 dot-path 获取 schema 的节点。
|
||
|
||
同时支持:
|
||
- 扁平 schema(直接 key 命中)
|
||
- 嵌套 object schema({type: "object", items: {...}})
|
||
"""
|
||
|
||
if not isinstance(schema, dict) or not key_path:
|
||
return None
|
||
if key_path in schema:
|
||
return schema.get(key_path)
|
||
|
||
current = schema
|
||
parts = key_path.split(".")
|
||
for idx, part in enumerate(parts):
|
||
if part not in current:
|
||
return None
|
||
meta = current.get(part)
|
||
if idx == len(parts) - 1:
|
||
return meta
|
||
if not isinstance(meta, dict) or meta.get("type") != "object":
|
||
return None
|
||
current = meta.get("items", {})
|
||
return None
|
||
|
||
|
||
def sanitize_filename(name: str) -> str:
|
||
"""清洗上传文件名,避免路径穿越与非法名称。
|
||
|
||
- 丢弃目录部分,仅保留 basename
|
||
- 将路径分隔符替换为下划线
|
||
- 拒绝空字符串 / "." / ".."
|
||
"""
|
||
|
||
cleaned = os.path.basename(name).strip()
|
||
if not cleaned or cleaned in {".", ".."}:
|
||
return ""
|
||
for sep in (os.sep, os.altsep):
|
||
if sep:
|
||
cleaned = cleaned.replace(sep, "_")
|
||
return cleaned
|
||
|
||
|
||
def sanitize_path_segment(segment: str) -> str:
|
||
"""清洗目录片段(URL/path 安全,避免穿越)。
|
||
|
||
仅保留 [A-Za-z0-9_-],其余替换为 "_"
|
||
"""
|
||
|
||
cleaned = []
|
||
for ch in segment:
|
||
if (
|
||
("a" <= ch <= "z")
|
||
or ("A" <= ch <= "Z")
|
||
or ch.isdigit()
|
||
or ch
|
||
in {
|
||
"-",
|
||
"_",
|
||
}
|
||
):
|
||
cleaned.append(ch)
|
||
else:
|
||
cleaned.append("_")
|
||
result = "".join(cleaned).strip("_")
|
||
return result or "_"
|
||
|
||
|
||
def config_key_to_folder(key_path: str) -> str:
|
||
"""将 dot-path 的配置 key 转成稳定的文件夹路径。"""
|
||
|
||
parts = [sanitize_path_segment(p) for p in key_path.split(".") if p]
|
||
return "/".join(parts) if parts else "_"
|
||
|
||
|
||
def normalize_rel_path(rel_path: str | None) -> str | None:
|
||
"""规范化用户传入的相对路径,并阻止路径穿越。"""
|
||
|
||
if not isinstance(rel_path, str):
|
||
return None
|
||
rel = rel_path.replace("\\", "/").lstrip("/")
|
||
if not rel:
|
||
return None
|
||
parts = [p for p in rel.split("/") if p]
|
||
if any(part in {".", ".."} for part in parts):
|
||
return None
|
||
if rel.startswith("../") or "/../" in rel:
|
||
return None
|
||
return "/".join(parts)
|