Files
markbase/docs/WEBDAV_LOCK_INTERCEPT.md
2026-05-18 17:02:30 +08:00

6.0 KiB
Raw Permalink Blame History

WebDAV LOCK拦截机制详解

核心拦截点

dav-server框架在HTTP请求处理流程中内置锁检查机制无需手动拦截。

1. PUT写文件拦截点

文件位置: ~/.cargo/registry/src/*/dav-server-0.11.0/src/handle_put.rs:131-139

// if locked check if we hold that lock.
if let Some(ref locksystem) = self.ls {
    let principal = self.principal.as_deref();
    if let Err(_l) = locksystem
        .check(&path, principal, false, false, &tokens)
        .await
    {
        return Err(DavError::StatusClose(SC::LOCKED));  // 423 LOCKED
    }
}

拦截流程:

客户端 PUT /webdav/file.txt
    ↓
DavHandler.handle_put()
    ↓
if_match_get_tokens() - 解析If头中的lock token
    ↓
locksystem.check() - 检查锁冲突
    ↓ (失败)
返回 423 LOCKED (客户端无权限)
    ↓ (成功)
LocalFs.write() - 执行实际写入

2. DELETE删除拦截点

文件位置: ~/.cargo/registry/src/*/dav-server-0.11.0/src/handle_delete.rs:123-132

// check locks. since we cancel the entire operation if there is
// a conflicting lock, we do not return a 207 multistatus, but
// just a simple status.
if let Some(ref locksystem) = self.ls {
    let principal = self.principal.as_deref();
    if let Err(_l) = locksystem
        .check(&path, principal, false, true, &tokens)  // deep=true
        .await
    {
        return Err(DavError::Status(StatusCode::LOCKED));
    }
}

关键参数: deep=true 表示检查整个路径树(包括子目录)

拦截流程:

客户端 DELETE /webdav/folder/
    ↓
DavHandler.handle_delete()
    ↓
locksystem.check(&path, principal, false, true, &tokens)
    ↓ (冲突)
返回 423 LOCKED
    ↓ (成功)
locksystem.delete(&path) - 删除所有锁记录
    ↓
LocalFs.remove_dir() - 执行删除

3. LOCK加锁拦截点

文件位置: ~/.cargo/registry/src/*/dav-server-0.11.0/src/handle_lock.rs

// 创建新锁
let lock = locksystem.lock(
    &path,
    principal,
    owner,
    timeout,
    shared,
    deep,
).await;

// 刷新锁
let lock = locksystem.refresh(&path, &tokens[0], timeout).await;

拦截流程:

客户端 LOCK /webdav/file.txt
    ↓
DavHandler.handle_lock()
    ↓
检查If头中的token
    ↓ (有token)
locksystem.refresh() - 刷新现有锁
    ↓ (无token)
locksystem.lock() - 创建新锁
    ↓
返回 200 OK + lock token

4. UNLOCK解锁拦截点

文件位置: ~/.cargo/registry/src/*/dav-server-0.11.0/src/handle_lock.rs

locksystem.unlock(&path, &lock_token).await

拦截流程:

客户端 UNLOCK /webdav/file.txt
    Header: Lock-Token: <urn:uuid:xxx>
    ↓
DavHandler.handle_unlock()
    ↓
解析Lock-Token头
    ↓
locksystem.unlock(&path, &token)
    ↓ (成功)
返回 204 No Content
    ↓ (失败)
返回 403 Forbidden (token无效)

DavLockSystem.check() 参数详解

fn check(
    path: &DavPath,           // 文件路径
    principal: Option<&str>,  // 用户身份来自Authorization头
    ignore_principal: bool,   // true=忽略用户身份检查
    deep: bool,               // true=检查子路径锁
    submitted_tokens: &[String], // If头中的lock tokens
) -> LsFuture<'_, Result<(), DavLock>>

返回值:

  • Ok(()) - 有权限(锁匹配或无锁)
  • Err(DavLock) - 冲突锁返回423 LOCKED

if_match_get_tokens() 作用

文件位置: ~/.cargo/registry/src/*/dav-server-0.11.0/src/conditional.rs

解析HTTP请求中的lock tokens

  • If: <urn:uuid:xxx> - 单个token
  • If: (<urn:uuid:xxx>) - 标准格式
  • If-Match: * - 需要任意锁
  • If-None-Match: * - 需要无锁

MarkBase实现位置

LockManager: src/webdav/lock_manager.rs

impl DavLockSystem for LockManager {
    fn check(
        &'_ self,
        path: &DavPath,
        principal: Option<&str>,
        ignore_principal: bool,
        deep: bool,
        submitted_tokens: &[String],
    ) -> LsFuture<'_, Result<(), DavLock>> {
        // 1. 查询SQLite数据库中的锁
        // 2. 清理过期锁cleanup_expired_locks
        // 3. 比对submitted_tokens匹配则允许
        // 4. 比对principal同用户则允许
        // 5. 检查deep锁子路径冲突
        // 6. 返回冲突锁Err或允许Ok
    }
}

HTTP状态码对照

状态码 含义 触发条件
200 OK LOCK成功 lock()返回Ok
204 No Content UNLOCK/PUT成功 unlock()或write()成功
403 Forbidden UNLOCK失败 unlock()返回Errtoken无效
423 Locked 操作被锁阻止 check()返回Err
409 Conflict 目标不存在 文件不存在且无法创建
412 Precondition Failed If条件不满足 If-Match/If-None-Match失败

macOS Finder行为

典型请求序列:

1. PROPFIND /webdav/ - 获取文件列表
2. LOCK /webdav/file.txt - 加锁( exclusive
   Header: If: (<urn:uuid:xxx>)
3. PUT /webdav/file.txt - 写入带If头
   Header: If: (<urn:uuid:xxx>)
4. UNLOCK /webdav/file.txt - 解锁
   Header: Lock-Token: <urn:uuid:xxx>

锁有效期: macOS Finder默认60秒超时需定期refresh

测试方法

# 手动测试锁机制
curl -X LOCK http://localhost:4919/webdav/test.txt \
  -H "Content-Type: application/xml" \
  -d '<D:lockinfo><D:locktype><D:write/></D:locktype><D:lockscope><D:exclusive/></D:lockscope></D:lockinfo>'

# 查看锁token
curl -X PROPFIND http://localhost:4919/webdav/test.txt \
  -H "Depth: 0" \
  -H "Content-Type: application/xml" \
  -d '<D:propfind><D:prop><D:lockdiscovery/></D:prop></D:propfind>'

# 尝试写入无锁token
curl -X PUT http://localhost:4919/webdav/test.txt \
  -d "test content"
# 预期423 Locked

# 写入带正确token
curl -X PUT http://localhost:4919/webdav/test.txt \
  -H "If: (<urn:uuid:YOUR_TOKEN>)" \
  -d "test content"
# 预期204 No Content

创建时间: 2026-05-17 03:30 版本: 1.0(拦截点详解版)