add webdav support
This commit is contained in:
parent
b277c9bbff
commit
49034bc571
@ -4,20 +4,43 @@ WebDAV 文件管理路由
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, HTTPException, Response
|
from fastapi import APIRouter, Request, HTTPException, Response, Depends
|
||||||
from fastapi.responses import FileResponse, Response as FastAPIResponse
|
from fastapi.responses import FileResponse, Response as FastAPIResponse
|
||||||
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from utils.settings import WEBDAV_USERNAME, WEBDAV_PASSWORD
|
||||||
|
|
||||||
logger = logging.getLogger('app')
|
logger = logging.getLogger('app')
|
||||||
|
|
||||||
|
security = HTTPBasic(realm="WebDAV")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)):
|
||||||
|
"""验证 WebDAV Basic Auth 账号密码"""
|
||||||
|
username_correct = secrets.compare_digest(credentials.username, WEBDAV_USERNAME)
|
||||||
|
password_correct = secrets.compare_digest(credentials.password, WEBDAV_PASSWORD)
|
||||||
|
if not (username_correct and password_correct):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="认证失败",
|
||||||
|
headers={"WWW-Authenticate": 'Basic realm="WebDAV"'},
|
||||||
|
)
|
||||||
|
return credentials
|
||||||
|
|
||||||
|
# 不在 router 级别加认证,OPTIONS 需要免认证(macOS Finder 等客户端预检不带凭据)
|
||||||
router = APIRouter(prefix="/webdav", tags=["webdav"])
|
router = APIRouter(prefix="/webdav", tags=["webdav"])
|
||||||
|
|
||||||
|
# 需要认证的依赖,用于非 OPTIONS 的端点
|
||||||
|
auth_dep = [Depends(verify_credentials)]
|
||||||
|
|
||||||
# WebDAV XML 命名空间
|
# WebDAV XML 命名空间
|
||||||
DAV_NS = "DAV:"
|
DAV_NS = "DAV:"
|
||||||
DAV_PREFIX = "{DAV:}"
|
DAV_PREFIX = "{DAV:}"
|
||||||
@ -107,8 +130,8 @@ def build_multistatus(responses: list[ET.Element]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
# ===== PROPFIND =====
|
# ===== PROPFIND =====
|
||||||
@router.api_route("/{bot_id}/{path:path}", methods=["PROPFIND"])
|
@router.api_route("/{bot_id}/{path:path}", methods=["PROPFIND"], dependencies=auth_dep)
|
||||||
@router.api_route("/{bot_id}", methods=["PROPFIND"])
|
@router.api_route("/{bot_id}", methods=["PROPFIND"], dependencies=auth_dep)
|
||||||
async def propfind(bot_id: str, request: Request, path: str = ""):
|
async def propfind(bot_id: str, request: Request, path: str = ""):
|
||||||
"""PROPFIND - 获取资源属性(列出目录)"""
|
"""PROPFIND - 获取资源属性(列出目录)"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -166,7 +189,7 @@ async def options(bot_id: str, path: str = ""):
|
|||||||
|
|
||||||
|
|
||||||
# ===== GET =====
|
# ===== GET =====
|
||||||
@router.get("/{bot_id}/{path:path}")
|
@router.get("/{bot_id}/{path:path}", dependencies=auth_dep)
|
||||||
async def get_file(bot_id: str, path: str):
|
async def get_file(bot_id: str, path: str):
|
||||||
"""GET - 下载文件"""
|
"""GET - 下载文件"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -192,7 +215,7 @@ async def get_file(bot_id: str, path: str):
|
|||||||
|
|
||||||
|
|
||||||
# ===== HEAD =====
|
# ===== HEAD =====
|
||||||
@router.head("/{bot_id}/{path:path}")
|
@router.head("/{bot_id}/{path:path}", dependencies=auth_dep)
|
||||||
async def head_file(bot_id: str, path: str):
|
async def head_file(bot_id: str, path: str):
|
||||||
"""HEAD - 获取文件元数据"""
|
"""HEAD - 获取文件元数据"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -214,7 +237,7 @@ async def head_file(bot_id: str, path: str):
|
|||||||
|
|
||||||
|
|
||||||
# ===== PUT =====
|
# ===== PUT =====
|
||||||
@router.put("/{bot_id}/{path:path}")
|
@router.put("/{bot_id}/{path:path}", dependencies=auth_dep)
|
||||||
async def put_file(bot_id: str, path: str, request: Request):
|
async def put_file(bot_id: str, path: str, request: Request):
|
||||||
"""PUT - 上传/覆盖文件"""
|
"""PUT - 上传/覆盖文件"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -234,7 +257,7 @@ async def put_file(bot_id: str, path: str, request: Request):
|
|||||||
|
|
||||||
|
|
||||||
# ===== DELETE =====
|
# ===== DELETE =====
|
||||||
@router.delete("/{bot_id}/{path:path}")
|
@router.delete("/{bot_id}/{path:path}", dependencies=auth_dep)
|
||||||
async def delete_resource(bot_id: str, path: str):
|
async def delete_resource(bot_id: str, path: str):
|
||||||
"""DELETE - 删除文件或目录"""
|
"""DELETE - 删除文件或目录"""
|
||||||
if not path.strip("/"):
|
if not path.strip("/"):
|
||||||
@ -256,7 +279,7 @@ async def delete_resource(bot_id: str, path: str):
|
|||||||
|
|
||||||
|
|
||||||
# ===== MKCOL =====
|
# ===== MKCOL =====
|
||||||
@router.api_route("/{bot_id}/{path:path}", methods=["MKCOL"])
|
@router.api_route("/{bot_id}/{path:path}", methods=["MKCOL"], dependencies=auth_dep)
|
||||||
async def mkcol(bot_id: str, path: str):
|
async def mkcol(bot_id: str, path: str):
|
||||||
"""MKCOL - 创建目录"""
|
"""MKCOL - 创建目录"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -274,7 +297,7 @@ async def mkcol(bot_id: str, path: str):
|
|||||||
|
|
||||||
|
|
||||||
# ===== COPY =====
|
# ===== COPY =====
|
||||||
@router.api_route("/{bot_id}/{path:path}", methods=["COPY"])
|
@router.api_route("/{bot_id}/{path:path}", methods=["COPY"], dependencies=auth_dep)
|
||||||
async def copy_resource(bot_id: str, path: str, request: Request):
|
async def copy_resource(bot_id: str, path: str, request: Request):
|
||||||
"""COPY - 复制文件或目录"""
|
"""COPY - 复制文件或目录"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
@ -315,7 +338,7 @@ async def copy_resource(bot_id: str, path: str, request: Request):
|
|||||||
|
|
||||||
|
|
||||||
# ===== MOVE =====
|
# ===== MOVE =====
|
||||||
@router.api_route("/{bot_id}/{path:path}", methods=["MOVE"])
|
@router.api_route("/{bot_id}/{path:path}", methods=["MOVE"], dependencies=auth_dep)
|
||||||
async def move_resource(bot_id: str, path: str, request: Request):
|
async def move_resource(bot_id: str, path: str, request: Request):
|
||||||
"""MOVE - 移动/重命名文件或目录"""
|
"""MOVE - 移动/重命名文件或目录"""
|
||||||
bot_root = get_bot_root(bot_id)
|
bot_root = get_bot_root(bot_id)
|
||||||
|
|||||||
@ -39,6 +39,10 @@ TOOL_OUTPUT_TRUNCATION_STRATEGY = os.getenv("TOOL_OUTPUT_TRUNCATION_STRATEGY", "
|
|||||||
DEFAULT_THINKING_ENABLE = os.getenv("DEFAULT_THINKING_ENABLE", "true") == "true"
|
DEFAULT_THINKING_ENABLE = os.getenv("DEFAULT_THINKING_ENABLE", "true") == "true"
|
||||||
|
|
||||||
|
|
||||||
|
# WebDAV Authentication
|
||||||
|
WEBDAV_USERNAME = os.getenv("WEBDAV_USERNAME", "admin")
|
||||||
|
WEBDAV_PASSWORD = os.getenv("WEBDAV_PASSWORD", "MmL85TjjxZC97hk9rsYfhQ")
|
||||||
|
|
||||||
# MCP Tool Timeout Settings
|
# MCP Tool Timeout Settings
|
||||||
MCP_HTTP_TIMEOUT = int(os.getenv("MCP_HTTP_TIMEOUT", 60)) # HTTP 请求超时(秒)
|
MCP_HTTP_TIMEOUT = int(os.getenv("MCP_HTTP_TIMEOUT", 60)) # HTTP 请求超时(秒)
|
||||||
MCP_SSE_READ_TIMEOUT = int(os.getenv("MCP_SSE_READ_TIMEOUT", 300)) # SSE 读取超时(秒)
|
MCP_SSE_READ_TIMEOUT = int(os.getenv("MCP_SSE_READ_TIMEOUT", 300)) # SSE 读取超时(秒)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user