add webdav support

This commit is contained in:
朱潮 2026-03-09 12:31:07 +08:00
parent b277c9bbff
commit 49034bc571
2 changed files with 37 additions and 10 deletions

View File

@ -4,20 +4,43 @@ WebDAV 文件管理路由
"""
import os
import secrets
import shutil
import mimetypes
from pathlib import Path
from datetime import datetime, timezone
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.security import HTTPBasic, HTTPBasicCredentials
import logging
from utils.settings import WEBDAV_USERNAME, WEBDAV_PASSWORD
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"])
# 需要认证的依赖,用于非 OPTIONS 的端点
auth_dep = [Depends(verify_credentials)]
# WebDAV XML 命名空间
DAV_NS = "DAV:"
DAV_PREFIX = "{DAV:}"
@ -107,8 +130,8 @@ def build_multistatus(responses: list[ET.Element]) -> str:
# ===== PROPFIND =====
@router.api_route("/{bot_id}/{path:path}", methods=["PROPFIND"])
@router.api_route("/{bot_id}", methods=["PROPFIND"])
@router.api_route("/{bot_id}/{path:path}", methods=["PROPFIND"], dependencies=auth_dep)
@router.api_route("/{bot_id}", methods=["PROPFIND"], dependencies=auth_dep)
async def propfind(bot_id: str, request: Request, path: str = ""):
"""PROPFIND - 获取资源属性(列出目录)"""
bot_root = get_bot_root(bot_id)
@ -166,7 +189,7 @@ async def options(bot_id: str, path: str = ""):
# ===== 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):
"""GET - 下载文件"""
bot_root = get_bot_root(bot_id)
@ -192,7 +215,7 @@ async def get_file(bot_id: str, path: str):
# ===== 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):
"""HEAD - 获取文件元数据"""
bot_root = get_bot_root(bot_id)
@ -214,7 +237,7 @@ async def head_file(bot_id: str, path: str):
# ===== 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):
"""PUT - 上传/覆盖文件"""
bot_root = get_bot_root(bot_id)
@ -234,7 +257,7 @@ async def put_file(bot_id: str, path: str, request: Request):
# ===== 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):
"""DELETE - 删除文件或目录"""
if not path.strip("/"):
@ -256,7 +279,7 @@ async def delete_resource(bot_id: str, path: str):
# ===== 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):
"""MKCOL - 创建目录"""
bot_root = get_bot_root(bot_id)
@ -274,7 +297,7 @@ async def mkcol(bot_id: str, path: str):
# ===== 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):
"""COPY - 复制文件或目录"""
bot_root = get_bot_root(bot_id)
@ -315,7 +338,7 @@ async def copy_resource(bot_id: str, path: str, request: Request):
# ===== 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):
"""MOVE - 移动/重命名文件或目录"""
bot_root = get_bot_root(bot_id)

View File

@ -39,6 +39,10 @@ TOOL_OUTPUT_TRUNCATION_STRATEGY = os.getenv("TOOL_OUTPUT_TRUNCATION_STRATEGY", "
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_HTTP_TIMEOUT = int(os.getenv("MCP_HTTP_TIMEOUT", 60)) # HTTP 请求超时(秒)
MCP_SSE_READ_TIMEOUT = int(os.getenv("MCP_SSE_READ_TIMEOUT", 300)) # SSE 读取超时(秒)