Skip to content

[BUG] WebDAV 模块 ResolvePath 导致非根 BasePath 下路径双重拼接,引发 MKCOL 409 Conflict #9613

Description

@fightnvrgp

Please make sure of the following things

  • I have read the documentation.
    我已经阅读了文档

  • I'm sure there are no duplicate issues or discussions.
    我确定没有重复的issue或讨论。

  • I'm sure it's due to AList and not something else(such as Network ,Dependencies or Operational).
    我确定是AList的问题,而不是其他原因(例如网络依赖操作)。

  • I'm sure this issue is not fixed in the latest version.
    我确定这个问题在最新版本中没有被修复。

AList Version / AList 版本

v3.63.0

Driver used / 使用的存储驱动

本机存储

Describe the bug / 问题描述

当 WebDAV 用户或角色的“基本路径(Base Path)”设置为非根目录(例如设置为 /webdav,且存储根目录下已存在 /webdav 目录)时:

客户端连接 https://domain/dav/ 并尝试在根目录下创建子文件夹或上传文件(例如发送 MKCOL /dav/cc-switch-sync/)时,AList 会报错返回 409 Conflict (请确认上级目录存在)

经排查,该报错是因为服务端代码在处理请求时把 BasePath 拼接了两次,导致 AList 试图去底层查找物理上不存在的 /webdav/webdav/cc-switch-sync 路径,引发父目录判定不存在错误。

Reproduction / 复现链接

【复现步骤】:

  1. 在 AList 存储根目录下确保存在一个名为 /webdav 的文件夹。
  2. 创建一个 WebDAV 用户(或为其绑定的角色),设置“基本路径”为 /webdav
  3. 使用 WebDAV 客户端(如 CC-Switch / Rclone / Joplin)连接 https://domain/dav/
  4. 客户端在根目录下尝试创建子目录(例如 cc-switch-sync)。
  5. 客户端收到报错:409 Conflict (WebDAV MKCOL failed: 409 Conflict: Please ensure the parent directory exists)

对比验证:如果把用户的基本路径改回 /,客户端连接 URL 设为 https://domain/dav/webdav/,则一切正常。


【源码级 Bug 根源分析】:

问题发生在 server/webdav/path.go 中的 ResolvePath 函数与 internal/model/user.go 中的 user.JoinPath() 的叠加作用:

  1. server/webdav/path.go 中:
func ResolvePath(user *model.User, raw string) (string, error) {
	cleaned := utils.FixAndCleanPath(raw)
	basePath := utils.FixAndCleanPath(user.BasePath)

	if cleaned != "/" && basePath != "/" && !utils.IsSubPath(basePath, cleaned) {
		cleaned = path.Join(basePath, strings.TrimPrefix(cleaned, "/"))
	}

	return user.JoinPath(cleaned)
}

当客户端请求 MKCOL /dav/cc-switch-syncraw = "/cc-switch-sync")时:
ResolvePath 判断 !utils.IsSubPath("/webdav", "/cc-switch-sync") 为 true,执行 path.Join("/webdav", "cc-switch-sync"),得到 cleaned = "/webdav/cc-switch-sync"

  1. 随后 ResolvePath"/webdav/cc-switch-sync" 传递给 user.JoinPath(cleaned)
func (u *User) JoinPath(reqPath string) (string, error) {
	if reqPath == "/" {
		return utils.FixAndCleanPath(u.BasePath), nil
	}
	path, err := utils.JoinBasePath(u.BasePath, reqPath) // <--- 此处又重复拼接了一次 u.BasePath !
	...
}

user.JoinPath 内部调用 utils.JoinBasePath("/webdav", "/webdav/cc-switch-sync"),再次无条件拼接 u.BasePath,最终算出的目标路径为:
"/webdav/webdav/cc-switch-sync"

由于存储根目录下只有 /webdav,并不存在 /webdav/webdav,因此底层 Stat 判定父目录不存在,向上抛出 409 Conflict


【修复建议】:
建议修补 server/webdav/path.go 中的逻辑,避免在传递给 user.JoinPath 前重复拼接 basePath

 func ResolvePath(user *model.User, raw string) (string, error) {
 	cleaned := utils.FixAndCleanPath(raw)
 	basePath := utils.FixAndCleanPath(user.BasePath)

-	if cleaned != "/" && basePath != "/" && !utils.IsSubPath(basePath, cleaned) {
-		cleaned = path.Join(basePath, strings.TrimPrefix(cleaned, "/"))
-	}
+	if basePath != "/" && utils.IsSubPath(basePath, cleaned) {
+		cleaned = utils.GetSubPath(basePath, cleaned)
+	}

 	return user.JoinPath(cleaned)
 }

Config / 配置

与配置无关,需说明的配置在上面均已说明

Logs / 日志

代码层面已查明,暂且懒得提供日志

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions