238 lines
6.0 KiB
Markdown
238 lines
6.0 KiB
Markdown
# WebDAV LOCK拦截机制详解
|
||
|
||
## 核心拦截点
|
||
|
||
dav-server框架在HTTP请求处理流程中内置锁检查机制,无需手动拦截。
|
||
|
||
### 1. PUT(写文件)拦截点
|
||
|
||
**文件位置**: `~/.cargo/registry/src/*/dav-server-0.11.0/src/handle_put.rs:131-139`
|
||
|
||
```rust
|
||
// 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`
|
||
|
||
```rust
|
||
// 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`
|
||
|
||
```rust
|
||
// 创建新锁
|
||
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`
|
||
|
||
```rust
|
||
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() 参数详解
|
||
|
||
```rust
|
||
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`
|
||
|
||
```rust
|
||
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()返回Err(token无效)|
|
||
| 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
|
||
|
||
## 测试方法
|
||
|
||
```bash
|
||
# 手动测试锁机制
|
||
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(拦截点详解版) |