add webdav support
This commit is contained in:
parent
b277c9bbff
commit
49034bc571
@ -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)
|
||||
|
||||
@ -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 读取超时(秒)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user