|
1 | | -from fastapi import APIRouter, Depends |
2 | | -from fastapi.responses import JSONResponse |
| 1 | +import logging |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | +from fastapi import APIRouter, Depends, HTTPException |
| 5 | +from fastapi.responses import FileResponse, JSONResponse |
3 | 6 | from sqlalchemy.util.concurrency import asyncio |
4 | 7 |
|
5 | 8 | from module.database import Database, engine |
6 | 9 | from module.manager import BangumiManager |
7 | 10 | from module.models import APIResponse, Bangumi, BangumiUpdate, ResponseModel |
| 11 | +from module.network import load_image |
8 | 12 | from module.security.api import get_current_user |
9 | 13 |
|
10 | 14 | from .response import u_response |
11 | 15 |
|
12 | 16 | router = APIRouter(prefix="/bangumi", tags=["bangumi"]) |
| 17 | +logger = logging.getLogger(__name__) |
13 | 18 |
|
14 | 19 |
|
15 | 20 | # def str_to_list(data: Bangumi): |
@@ -226,3 +231,48 @@ async def reset_all(): |
226 | 231 | "msg_zh": "重置所有规则成功。", |
227 | 232 | }, |
228 | 233 | ) |
| 234 | + |
| 235 | + |
| 236 | +@router.get("/poster/{path:path}", dependencies=[Depends(get_current_user)]) |
| 237 | +async def get_poster(path: str): |
| 238 | + """ |
| 239 | + 安全的poster图片访问端点 |
| 240 | + - 添加了用户鉴权 |
| 241 | + - 防止路径遍历攻击 |
| 242 | + - 限制只能访问posters目录下的文件 |
| 243 | + """ |
| 244 | + # 验证路径安全性 - 阻止路径遍历 |
| 245 | + if ".." in path or path.startswith("/") or "\\" in path: |
| 246 | + logger.warning(f"[Poster] Blocked path traversal attempt: {path}") |
| 247 | + raise HTTPException(status_code=400, detail="Invalid path") |
| 248 | + |
| 249 | + # 构建安全的文件路径 |
| 250 | + poster_dir = Path("data/posters") |
| 251 | + post_path = poster_dir / Path(path) |
| 252 | + |
| 253 | + # 确保解析后的路径仍在预期目录内 |
| 254 | + try: |
| 255 | + post_path.resolve().relative_to(poster_dir.resolve()) |
| 256 | + except ValueError: |
| 257 | + logger.warning(f"[Poster] Path outside allowed directory: {path}") |
| 258 | + raise HTTPException(status_code=400, detail="Path outside allowed directory") |
| 259 | + |
| 260 | + logger.debug(f"[Poster] Accessing poster: {post_path}") |
| 261 | + |
| 262 | + # 如果文件不存在,尝试下载 |
| 263 | + if not post_path.exists(): |
| 264 | + try: |
| 265 | + await load_image(path) |
| 266 | + except Exception as e: |
| 267 | + logger.warning(f"[Poster] Failed to load image {path}: {e}") |
| 268 | + |
| 269 | + # 返回文件 |
| 270 | + if post_path.exists() and post_path.is_file(): |
| 271 | + return FileResponse( |
| 272 | + post_path, |
| 273 | + media_type="image/jpeg", |
| 274 | + headers={"Cache-Control": "public, max-age=86400"}, # 缓存1天 |
| 275 | + ) |
| 276 | + else: |
| 277 | + logger.warning(f"[Poster] File not found: {post_path}") |
| 278 | + raise HTTPException(status_code=404, detail="Poster not found") |
0 commit comments