mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-11 10:40:18 +08:00
fix: prevent cloud storage download path traversal
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Optional, List, Dict, Tuple, Callable, Union
|
||||
|
||||
from tqdm import tqdm
|
||||
@@ -105,6 +105,38 @@ class StorageBase(metaclass=ABCMeta):
|
||||
self.storagehelper.reset_storage(self.schema.value)
|
||||
self.init_storage()
|
||||
|
||||
@staticmethod
|
||||
def _safe_download_name(name: Optional[str]) -> Optional[str]:
|
||||
"""
|
||||
提取可安全落盘的文件名。
|
||||
"""
|
||||
if not name:
|
||||
return None
|
||||
|
||||
safe_name = PurePosixPath(str(name).replace("\\", "/")).name
|
||||
if safe_name in ("", ".", ".."):
|
||||
return None
|
||||
return safe_name
|
||||
|
||||
def _build_download_path(
|
||||
self, fileitem: schemas.FileItem, path: Path
|
||||
) -> Optional[Path]:
|
||||
"""
|
||||
构造本地下载路径,避免远端文件名携带目录片段时越过目标目录。
|
||||
"""
|
||||
safe_name = self._safe_download_name(fileitem.name)
|
||||
if not safe_name:
|
||||
logger.error(f"【存储】下载文件名无效:{fileitem.name}")
|
||||
return None
|
||||
|
||||
local_path = path / safe_name
|
||||
try:
|
||||
local_path.resolve().relative_to(path.resolve())
|
||||
except ValueError:
|
||||
logger.error(f"【存储】下载路径越界:{fileitem.name} -> {local_path}")
|
||||
return None
|
||||
return local_path
|
||||
|
||||
@abstractmethod
|
||||
def check(self) -> bool:
|
||||
"""
|
||||
|
||||
@@ -741,7 +741,9 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
|
||||
logger.error(f"【阿里云盘】下载链接为空: {fileitem.name}")
|
||||
return None
|
||||
|
||||
local_path = (path or settings.TEMP_PATH) / fileitem.name
|
||||
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
|
||||
if not local_path:
|
||||
return None
|
||||
|
||||
# 获取文件大小
|
||||
file_size = fileitem.size
|
||||
|
||||
@@ -340,7 +340,9 @@ class Rclone(StorageBase):
|
||||
"""
|
||||
带实时进度显示的下载
|
||||
"""
|
||||
local_path = (path or settings.TEMP_PATH) / fileitem.name
|
||||
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
|
||||
if not local_path:
|
||||
return None
|
||||
|
||||
# 初始化进度条
|
||||
logger.info(f"【rclone】开始下载: {fileitem.name} -> {local_path}")
|
||||
|
||||
@@ -511,7 +511,9 @@ class SMB(StorageBase, metaclass=WeakSingleton):
|
||||
"""
|
||||
带实时进度显示的下载
|
||||
"""
|
||||
local_path = (path or settings.TEMP_PATH) / fileitem.name
|
||||
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
|
||||
if not local_path:
|
||||
return None
|
||||
smb_path = self._normalize_path(fileitem.path)
|
||||
try:
|
||||
self._check_connection()
|
||||
|
||||
@@ -830,7 +830,9 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
logger.error(f"【115】下载链接为空: {fileitem.name}")
|
||||
return None
|
||||
|
||||
local_path = (path or settings.TEMP_PATH) / fileitem.name
|
||||
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
|
||||
if not local_path:
|
||||
return None
|
||||
|
||||
# 获取文件大小
|
||||
file_size = detail.size
|
||||
|
||||
Reference in New Issue
Block a user