当前位置:论坛首页 > Linux面板 > 建议

【已记录】Bug: WebDAV 存储插件自动备份上传必现 403(Depth:inf

发表在 Linux面板前天 01:34 [复制链接] 1 60

环境[td]
项目
版本/值
面板宝塔 / aaPanel 11.4.0
插件WebDav存储 (webdav) v1.3,2024-06-05
插件文件/www/server/panel/plugin/webdav/webdav_main.py
Python3.7(面板自带 pyenv)
webdav40.9.8
httpx0.24.1
系统Oracle Linux Server 8.8, aarch64
WebDAV 服务端自建(域名经 Cloudflare 代理),Apache mod_dav

现象
计划任务里配置的"数据库全量备份 + WebDAV 云端同步",本地备份和压缩包生成都正常,但同步到 WebDAV 这一步必现失败:










httpx.HTTPStatusError: Client error '403 Forbidden' for url 'https://webdav.xxx.com/home'
...
webdav4.client.HTTPError: received 403 (Forbidden)
  File ".../webdav_main.py", line 133, in ensure_directories_exist
    if not self.client.exists(current_path):
  File ".../webdav_main.py", line 175, in upload_file_in_chunks
    if not self.ensure_directories_exist(remote_dir):
  File ".../class-projectModel-binlogModel.py", line 844, in execute_by_comandline
    if self._cloud.upload_file(zip_file, upload_path):






面板计划任务日志把这次备份仍标记为 ★ ... Successful,因为云端上传失败没有被上层捕获成"失败"状态,只是在 stderr 里打了一段未处理的 Python traceback——对用户来说观感是"备份成功了",但云端其实完全没同步上,属于静默失败。
根本原因
webdav4.client.Client.exists() 内部实现(0.9.8):







def exists(self, path: str) -> bool:
    try:
        self.propfind(path)   # 不带 headers,Depth 缺省 = "infinity"
    except ResourceNotFound:
        return False
    return True






propfind() 不传 headers 时不会设置 Depth,按 WebDAV 规范(RFC 4918)省略 Depth 等价于 Depth: infinity。
而本例 WebDAV 服务端(Apache mod_dav)对 /home 这一层显式禁止无限深度的 PROPFIND,直接返回:





HTTP/1.1 403 Forbidden
<title>403 Forbidden</title>
<h1>Forbidden</h1>
<p>PROPFIND requests with a Depth of "infinity" are not allowed for /home.</p>






插件里所有目录/文件存在性检查都是直接调用 self.client.exists(path):
  • create_directory()(连接时初始化远程目录)
  • ensure_directories_exist()(每次上传前逐级建目录)
  • upload_file_in_chunks() 分块上传循环里判断分块文件是否已存在
这三处全部会命中同一个 403,且是确定性复现,不是网络抖动——用 curl 手动测试时如果显式带 -H "Depth: 0" 反而永远成功,这也是最初排查时误判成"Cloudflare 瞬时故障"的原因:两种客户端发的请求本质不同(有没有 Depth 头),不是同一个请求的两次结果。
复现步骤
  • 配置任意一个真实拒绝 Depth: infinity PROPFIND 的 WebDAV 服务端做云端存储(Apache 默认较新版本、部分 NAS/群晖 WebDAV 实现都有此限制)。
  • 触发一次全量备份并勾选/启用云端同步。
  • ensure_directories_exist() 第一次调用 self.client.exists() 即抛出未捕获的 HTTPError,整条上传链路中断,日志里只看到裸 Python traceback,没有任何面向用户的错误提示。
影响
  • 云端备份完全没有落地,且面板任务日志仍显示整体"Successful",容易被误认为一切正常,只有等真正需要恢复时才会发现云端是空的。
  • 影响所有三个存在性检查点(连接初始化建目录 / 每次上传前建目录 / 分块存在性判断),不是偶发个例。
  • 任何 WebDAV 服务端只要对无限深度 PROPFIND 有安全限制(这是比较常见的加固配置),都会 100% 触发本问题,不限于本环境这一家。
建议修复
不要依赖 webdav4.Client.exists(),改为显式指定 Depth: 0 的浅层 PROPFIND:









from webdav4.client import ResourceNotFound

def _path_exists(self, path):
    try:
        self.client.propfind(path, headers={'Depth': '0'})
    except ResourceNotFound:
        return False
    return True






并把 webdav_main.py 里所有 self.client.exists(...) 的调用替换为 self._path_exists(...)(create_directory、ensure_directories_exist、upload_file_in_chunks 分块循环,共 4 处)。
同时建议:
  • ensure_directories_exist() / 分块上传对单次请求失败增加有限重试(真实网络抖动仍然存在,跟本 bug 是两回事,但顺手能一起做),而不是遇错直接把异常整个抛出中断整条上传链路。
  • execute_by_comandline()(binlogModel.py)在云端上传失败时不应仍然整体标记为 Successful,至少要在日志里给出一条用户能看懂的失败提示,而不是裸露 Python traceback。
本地临时修复(已自测验证,仅个人环境临时用,非官方 patch)
已在本机 /www/server/panel/plugin/webdav/webdav_main.py 按上述方案打了 patch(原文件备份于 /root/plugin-backups/webdav_main.py.bak.20260922_172106),验证方式:
  • 用面板同款 pyenv 解释器加载补丁后的插件,ensure_directories_exist() 连续 3 次全部返回 True(补丁前 100% 复现 403)。
  • 跑了一次完整的分块上传(写测试文件 → upload_file_in_chunks() → 确认远端存在 → 删除清理),全程走插件自身代码路径。
⚠️ 该 patch 是直接改的面板系统文件,下次插件更新会被覆盖,仅作为临时缓解,最终还是需要官方修复。

使用道具 举报 只看该作者 回复
发表于 前天 09:50 | 显示全部楼层
您好,非常感谢反馈和支持!您的建议已收到并记录,后续会有相关人员进行排障修复
如果您还有更多问题和疑虑,您可以通过客服咨询宝塔面板相关问题:https://docs.bt.cn/wechat-service
操作方式:复制链接在微信打开,即可在线咨询客服。
使用道具 举报 回复 支持 反对
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

紧急运维服务

响应时间:3分钟

问题处理方式:宝塔专家1对1服务

工作时间:工作日:9:00 - 18:30

宝塔专业团队为您解决服务器疑难问题

点击联系技术分析

工作时间:09:00至18:30

快速回复 返回顶部 返回列表