33from pathlib import Path
44
55import uvicorn
6- from fastapi import Depends , FastAPI , HTTPException , Request
6+ from fastapi import FastAPI , HTTPException , Request
77from fastapi .responses import FileResponse , RedirectResponse
88from fastapi .staticfiles import StaticFiles
99from fastapi .templating import Jinja2Templates
10+
1011from module .api import lifespan , v1
1112from module .conf import VERSION , settings , setup_logger
12- from module .network import load_image
13- from module .security .api import get_current_user
1413
1514setup_logger (reset = True )
1615logger = logging .getLogger (__name__ )
@@ -37,62 +36,47 @@ def create_app() -> FastAPI:
3736
3837
3938app = create_app ()
40-
41-
42- @app .get ("/posters/{path:path}" , dependencies = [Depends (get_current_user )])
43- async def get_poster (path : str ):
44- """
45- 安全的poster图片访问端点
46- - 添加了用户鉴权
47- - 防止路径遍历攻击
48- - 限制只能访问posters目录下的文件
49- """
50- # 验证路径安全性 - 阻止路径遍历
51- if ".." in path or path .startswith ("/" ) or "\\ " in path :
52- logger .warning (f"[Poster] Blocked path traversal attempt: { path } " )
53- raise HTTPException (status_code = 400 , detail = "Invalid path" )
54-
55- # 构建安全的文件路径
56- poster_dir = Path ("data/posters" )
57- post_path = poster_dir / Path (path )
58-
59- # 确保解析后的路径仍在预期目录内
60- try :
61- post_path .resolve ().relative_to (poster_dir .resolve ())
62- except ValueError :
63- logger .warning (f"[Poster] Path outside allowed directory: { path } " )
64- raise HTTPException (status_code = 400 , detail = "Path outside allowed directory" )
65-
66- # 如果文件不存在,尝试下载
67- if not post_path .exists ():
68- try :
69- await load_image (path )
70- except Exception as e :
71- logger .warning (f"[Poster] Failed to load image { path } : { e } " )
72-
73- # 返回文件
74- if post_path .exists () and post_path .is_file ():
75- return FileResponse (
76- post_path ,
77- media_type = "image/jpeg" ,
78- headers = {"Cache-Control" : "public, max-age=86400" }, # 缓存1天
79- )
80- else :
81- logger .warning (f"[Poster] File not found: { post_path } " )
82- raise HTTPException (status_code = 404 , detail = "Poster not found" )
83-
84-
8539if VERSION != "DEV_VERSION" :
8640 app .mount ("/assets" , StaticFiles (directory = "dist/assets" ), name = "assets" )
8741 app .mount ("/images" , StaticFiles (directory = "dist/images" ), name = "images" )
8842 templates = Jinja2Templates (directory = "dist" )
8943
9044 @app .get ("/{path:path}" )
91- def html (request : Request , path : str ):
92- files = os .listdir ("dist" )
93- if path in files :
94- return FileResponse (f"dist/{ path } " )
45+ async def serve_spa (request : Request , path : str ):
46+ """
47+ 安全的SPA静态文件服务
48+ - 防止路径遍历攻击
49+ - 限制只能访问dist目录下的文件
50+ - 对未匹配路由返回SPA入口页面
51+ """
52+ # 空路径或根路径,返回SPA入口页面
53+ if not path or path == "/" :
54+ context = {"request" : request }
55+ return templates .TemplateResponse ("index.html" , context )
56+
57+ # 验证路径安全性 - 阻止路径遍历
58+ if ".." in path or path .startswith ("/" ) or "\\ " in path :
59+ logger .warning (f"[Static] Blocked path traversal attempt: { path } " )
60+ context = {"request" : request }
61+ return templates .TemplateResponse ("index.html" , context )
62+
63+ # 构建安全的文件路径
64+ dist_dir = Path ("dist" ).resolve ()
65+ file_path = (dist_dir / path ).resolve ()
66+
67+ # 确保解析后的路径仍在预期目录内
68+ try :
69+ file_path .relative_to (dist_dir )
70+ except ValueError :
71+ logger .warning (f"[Static] Path outside allowed directory: { path } " )
72+ context = {"request" : request }
73+ return templates .TemplateResponse ("index.html" , context )
74+
75+ # 如果文件存在且是文件,则返回
76+ if file_path .exists () and file_path .is_file ():
77+ return FileResponse (file_path )
9578 else :
79+ # 文件不存在,返回SPA入口页面(用于客户端路由)
9680 context = {"request" : request }
9781 return templates .TemplateResponse ("index.html" , context )
9882
0 commit comments