Add WebDAV protocol engine and XML handling with safety validation#4
Conversation
## Summary Introduce `aster_forge_webdav`, a transport-neutral WebDAV protocol crate that handles RFC 4918 compliance, path canonicalization, header parsing, and protocol preconditions. This crate defines backend ports for resource operations, property storage, lock management, and optional versioning, allowing product repositories to own authentication, authorization, workspace scope, and persistence semantics. ## Features - **Path handling**: Canonical `DavPath` with percent-decoding, dot-segment normalization, and mount-escape rejection - **Request parsing**: Transport-neutral `DavRequestHead` with method-specific `Depth`, `Overwrite`, `Destination`, and `If` header validation - **Precondition evaluation**: HTTP ETag and modification-date rules following RFC 7232 precedence - **Backend ports**: `DavResourceBackend`, `DavPropertyBackend`, `DavLockBackend`, and optional `DavVersionBackend` traits for product adapters - **Event observation**: `DavEventSink` for non-authoritative audit, metrics, and notifications after synchronous operations complete - **Actix transport**: Optional feature-gated adapter converting Actix types to/from protocol-neutral `http` 1.x structures - **Error boundaries**: Stable `DavProtocolError` and `DavBackendErrorKind` classifications isolating protocol from product concerns ## Implementation details - `path.rs`: Percent-encoding, dot-segment cleaning, collection trailing-slash semantics, and parent/child path helpers - `protocol.rs`: `Depth`, `Destination`, `If` header grammar, same-origin validation, and HTTP conditional request evaluation - `request.rs`: `DavMethod` and `DavRequestHead` parsing with mount-relative target extraction - `response.rs`: Transport-neutral `DavResponse` with empty, byte, or stream body variants - `backend.rs`: Trait definitions for resource CRUD, dead properties, locks, and optional DeltaV version control - `event.rs`: Observable operation events excluding credentials, bodies, and tokens - `actix.rs`: Actix-web integration converting between framework types and protocol models - Comprehensive protocol tests covering path escapes, header grammar, same-origin constraints, and precondition rules ## Documentation - `docs/crates/aster_forge_webdav.md`: Chinese design doc explaining protocol ownership, backend contracts, error boundaries, and testing requirements - Integration guidance in `docs/guide/new-project-integration.md` - Updated root README and docs index ## Dependencies - `bytes`, `percent-encoding`, `urlencoding`: Path and content handling - `async-trait`, `futures`, `http`, `thiserror`: Core protocol infrastructure - Optional `actix-web` behind `actix` feature for transport adaptation - Internal `aster_forge_utils` for HTTP validator helpers
… validation Implement XML parsing and serialization boundary for PROPFIND, PROPPATCH, LOCK, and REPORT request bodies with QName-aware grammar enforcement and safety validation. Changes: - Add `DavXmlElement` and `DavXmlNode` as transport-independent XML representation boundary - Implement `parse_propfind_request` with allprop/propname/prop selector and include support - Implement `parse_proppatch_request` with ordered set/remove operations and xml:lang inheritance - Implement `parse_lock_request` for exclusive/shared scope and owner element preservation - Implement `parse_report_root` for QName-aware REPORT method dispatch - Add DTD/entity and nesting depth validation via `aster_forge_utils::xml::validate_xml_input` - Update `DavProperty`, `DavLockRequest`, and `DavLockInfo` to use `DavXmlElement` instead of raw bytes - Add comprehensive XML grammar and safety test matrix covering namespace collisions, unknown extensions, escaping, UTF-8, and depth limits - Add dependency: xmltree 0.12 as concrete XML parser (private to xml module) - Update documentation: WebDAV XML responsibility and testing requirements
Add product-neutral XML response generation for WebDAV protocol: - **Core response structures:** - `DavPropStat`: property status groups with HTTP status and property list - `DavMultiStatusItem`: response entries supporting both property-based and resource-wide status patterns - `DavErrorCondition`: RFC 4918 preconditions and postconditions (no-external-entities, lock-token-submitted, lock-token-matches-request-uri, propfind-finite-depth) - `DavLockXml`: protocol-visible lock values for lockdiscovery rendering - `DavVersionXml`: DeltaV version-tree entry representation - **Response builders:** - `dav_multistatus_element`: complete multistatus documents with response/propstat hierarchy - `dav_error_element`: error condition documents with proper namespace declarations - `dav_status_element`: HTTP status-line formatting with canonical reason phrases - `dav_propstat_element`, `dav_response_element`: composable response fragments - **Property builders:** - `dav_property_name_element`, `dav_property_text_element`, `dav_property_child_element`: QName-preserving property construction with namespace handling - `dav_dead_property_element`: dead property reconstruction from persisted XML with attribute preservation and malformed-value escaping fallback - **Lock and versioning:** - `dav_supported_lock_element`: RFC 4918 supportedlock with exclusive/shared write entries - `dav_lock_discovery_element`: activelock rendering with owner, timeout, token, depth, and root - `dav_lock_response_element`: LOCK response prop documents - `dav_version_multistatus_element`: DeltaV version-tree multistatus with protocol property order - **Test coverage:** - Response/propstat/property ordering preservation - Namespace declaration and QName handling for DAV:, custom, and no-namespace properties - Error condition document generation and href escaping - Lock field coverage (scope, depth, timeout variants, owner, token percent-encoding) - Dead property validation with attribute preservation and legacy value escaping - DeltaV multistatus protocol property order and value escaping - All outputs validated for well-formed XML Forge now handles the complete outbound XML grammar for multistatus, error, lock response, and version-tree documents. Products call builders with semantic data structures and receive validated XML without coupling to quick-xml or raw serialization logic.
…nsport-neutral status - Add `parse_lock_timeout` with bounded server policy validation - Add `parse_lock_token_header` for angle-bracketed token extraction - Add `submitted_lock_tokens` helper accepting parsed `IfHeader` - Change `DavEventOutcome` status fields from `http::StatusCode` to `u16` for transport neutrality - Add `DavEventOutcome::from_status` constructor classifying HTTP boundaries - Add `DavEventOutcome::status` accessor for completed status code - Export new parsing functions in public API - Add comprehensive test coverage for timeout bounds, token format, and outcome classification - Update documentation noting transport-neutral status and expanded header support
- Add `http_range` module to `aster_forge_utils` with transport-neutral single byte-range parsing - `parse_single_byte_range` handles bounded, open-ended, and suffix ranges - `HttpByteRange` resolves and validates ranges against representation length - Clamps out-of-bounds end values and distinguishes unsatisfiable/malformed/multi-range errors - Generates `Content-Range` header values for 206/416 responses - Add body policy classification to `DavMethod` with `Empty`, `BoundedXml`, `Stream`, and `Unused` variants - Add Actix body adapters for enforcing empty-body and bounded-XML policies before protocol execution - Add `plan_download_response` to build complete GET/HEAD response shells with 200/206/304/416 status selection - Evaluates HTTP preconditions and byte ranges in protocol order - Returns `DavDownloadPlan` with response headers and storage read contract (`Full`, `Range`, or `Empty`) - Handles HEAD method, conditional requests, and range validation without touching storage - Add product-neutral response builders: `options_response`, `method_not_allowed_response`, `body_error_response`, `range_not_satisfiable_response` - Add comprehensive test coverage for range parsing edge cases, body policy enforcement, and download response planning - Update documentation with HTTP range semantics, body policy contracts, and response planning responsibilities
…resource mutation, property, DeltaV, and If enforcement - Add `DavIfResourceState` and `DavIfStateResolver` port to backend for `If` header resolution - Implement `enforce_if_header` with tagged-resource scoping, AND/OR/Not state-list evaluation, and scheme/host/mount normalization - Add `DavIfEvaluationError` separating protocol and backend failures - Add `lock` module: `plan_lock_request` selects acquire/refresh, validates timeout/token/body; success/conflict/limit/unlock response builders - Add `put` module: `plan_put_request` evaluates ETag preconditions and selects create/create_new flags; 201/204 response with `Content-Location` - Add `resource` module: `plan_copy_move_request` enforces depth, descendant, and overwrite rules; `validate_delete_target`/`validate_collection_create_target`; typed `DavMutationFailure` with locked DAV-error207 multistatus - Add `property` module: `build_propfind_item` handles allprop/include dedup, propname, prop with200/404 propstat grouping; `build_proppatch_item` groups outcomes by status; `plan_atomic_proppatch` assigns403/424 on protected properties; `format_creation_date` formats RFC 3339 UTC via `chrono` - Add `deltav` module: `validate_version_tree_report` selects DAV:version-tree grammar; `version_tree_response` composes 207 multistatus; `version_control_response` selects method-level status by resource kind - Add `DavPreparedBody` and `prepare_request_body` to actix adapter for method-owned body policy dispatch - Expose `origin` field on `DavRequestHead` after parse - Add `protocol_error_response` and `backend_error_response` to centralize error-to-status mapping - Add `xml_document_response`, `text_document_response`, `xml_request_error_response` internal helpers in `response` - Add comprehensive integration tests for all new modules
…ed filesystem port - Replace `DavResourceBackend`, `DavPropertyBackend`, `DavLockBackend`, and `DavVersionBackend` with a single `DavFileSystem` trait and a `DavLockSystem` trait, eliminating the `DavBackend` aggregate bound - Introduce `DavFile`, `DavMetaData`, and `DavDirEntry` as object-safe traits in place of concrete `DavReadOutcome`, `DavResourceMetadata`, and `DavDirectoryEntry` structs - Add `FsError` enum with exhaustive `From<FsError> for DavBackendError` conversion; add `FsResult`, `FsFuture`, `FsStream`, and `LsFuture` type aliases - Add `OpenOptions` value type with `read()` and `write()` constructors; add `ReadDirMeta` enum for directory listing metadata mode - Add `DavPropertyTarget` carrying resource kind and opaque product-side id for batched dead-property reads; replace `DavProperty`/`DavPropertyName`/`DavPropertyPatch`/`DavPropertyPatchOutcome` with flat `DavProp` and `(StatusCode, DavProp)` tuples - Extend `DavLockSystem` with `prepare_lock`, `check`, `discover_many`, `conflicting_locks`, and `delete`; add `DavLockPreflightError` and `DavLockError` error types; rename persistent lock state to `DavLock`, keep `DavLockInfo` as protocol response value - Add `get_props_many` and `get_props_many_for_targets` default methods on `DavFileSystem` with sequential fallback implementations - Update `lib.rs` re-exports to reflect the new public surface - Add `tests/backend.rs` covering exhaustive `FsError`→`DavBackendErrorKind` mapping and `OpenOptions`/`DavPropertyTarget` value construction - Update `docs/crates/aster_forge_webdav.md` to document the consolidated backend contract
…om xmltree to aster_forge_xml
Replace xmltree dependency with aster_forge_xml for bounded parsing, streaming writing, and safety enforcement.
**XML Boundary Migration:**
- Replace `xmltree::Element` with `aster_forge_xml::{BorrowedDocument, OwnedDocument, ElementRef, NodeRef}`
- Add `element_from_forge` to convert forge element refs to owned `DavXmlElement` trees
- Implement streaming `write_element` using `XmlStreamWriter` with namespace inheritance tracking
- Preserve whitespace trimming policy via `ParseOptions::trim_whitespace(true)`
**Safety and Error Mapping:**
- Map all `XmlSafetyError` variants (InputTooLarge, TooManyElements, etc.) to `DavXmlError`
- Add `map_forge_xml_error` to unify `ForgeXmlError` → `DavXmlError` conversion
- Enhance ENTITY detection in `aster_forge_xml` parser to reject `<!ENTITY` before DOCTYPE with improved error position reporting
**Namespace and Attribute Handling:**
- Track inherited namespaces through `write_element` recursion to minimize redundant declarations
- Handle default namespace undeclaration (`xmlns=""`) correctly for unqualified subtrees
- Validate namespace conflicts in writer (qualified name vs xmlns declarations)
- Preserve namespace shadowing and namespaced attributes through round-trips
**Test Coverage:**
- Add reader I/O failure and input size boundary tests (exact max, max+1)
- Add writer validation for unbound prefixes, conflicting namespaces, invalid content (NUL, `]]>`, `--`, `?>`)
- Add depth boundary tests (exact `DEFAULT_XML_MAX_DEPTH`, depth+1)
- Add namespace shadowing, undeclaration, and attribute prefix tests
- Enhance ENTITY rejection tests (in comments, bare `<!ENTITY`, CDATA safety)
**Documentation:**
- Add `crates/aster_forge_webdav/AGENTS.md` referencing upstream XML contract ownership
- Update `docs/crates/aster_forge_webdav.md` to reflect `aster_forge_xml` delegation and expanded test matrix
- Document ownership boundary: `aster_forge_xml` owns parsing/writing primitives, webdav owns DAV grammar
**Dependencies:**
- Remove `xmltree` from workspace and `aster_forge_webdav`
- Add `aster_forge_xml` local path dependency to `aster_forge_webdav`
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthrough新增 ChangesWebDAV 协议栈
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/index.md (1)
64-68: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win排序乱了,别自己打自己脸。
文档说好"按字母顺序排列",结果
aster_forge_webdav插到了aster_forge_storage_core后面、aster_forge_tasks前面——w 排在 t 前面?我数学没这么差。挪到aster_forge_validation和aster_forge_xml之间去。📖 位置修正
- [`aster_forge_storage_core`](./crates/aster_forge_storage_core.md) -- [`aster_forge_webdav`](./crates/aster_forge_webdav.md) - [`aster_forge_tasks`](./crates/aster_forge_tasks.md) - [`aster_forge_utils`](./crates/aster_forge_utils.md) - [`aster_forge_validation`](./crates/aster_forge_validation.md) +- [`aster_forge_webdav`](./crates/aster_forge_webdav.md) - [`aster_forge_xml`](./crates/aster_forge_xml.md)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/index.md` around lines 64 - 68, Correct the alphabetical ordering in the crate list in docs/index.md by moving aster_forge_webdav after aster_forge_validation and before aster_forge_xml, while preserving the ordering of all other entries.
🧹 Nitpick comments (17)
docs/guide/new-project-integration.md (1)
86-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win新依赖用了非默认 feature,边界表里却没它。
第 86 行给
aster_forge_webdav加了features = ["actix"],但第 102-117 行那张"默认 feature / 显式 feature"表里压根没提它。这份指南自己说"Feature 边界要保持显式",你倒是先把自己漏了。补一行。| `aster_forge_tasks` | 无 | `runtime`, `runtime-component`, `openapi` | retry、dedupe、steps、spec 默认可用;worker/scheduled runtime 和 component factory 分开启用。 | +| `aster_forge_webdav` | 无 | `actix` | transport-neutral 协议内核默认可用;Actix 请求/响应类型转换需要显式启用 `actix`。 |Also applies to: 102-117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/guide/new-project-integration.md` at line 86, 在文档中的“默认 feature / 显式 feature”边界表(102-117 行)补充 aster_forge_webdav 的显式 actix feature,并与依赖声明中的 features = ["actix"] 保持一致。docs/crates/aster_forge_webdav.md (1)
18-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPUT 都写完了,文档里却一个字没提。
这份清单细到 COPY/MOVE/DELETE、LOCK、GET/HEAD range 都单独列了条目,
put.rs里完整的If-Match/If-None-Match前置条件、create/create_new 选择、X-Expected-Entity-Length优先级、collection 目标 405 拒绝、201/204 选择——一条都没写进"Forge 负责"。接入方看文档会以为这块得自己来,白干一遍。补上。- COPY/MOVE/DELETE 的资源路径关系、typed partial failure、207 与 201/204 响应选择。 +- PUT 的 `If-Match`/`If-None-Match` 前置条件、create/create_new 选择、`X-Expected-Entity-Length` 与 `Content-Length` 优先级,以及 collection 目标拒绝与 201/204 响应选择。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/crates/aster_forge_webdav.md` around lines 18 - 41, 在“Forge 负责”清单中补充 PUT 请求处理职责,覆盖 put.rs 中的 If-Match/If-None-Match 前置条件、create/create_new 选择、X-Expected-Entity-Length 优先级、collection 目标的 405 拒绝,以及成功时 201/204 响应选择,明确这些逻辑由 Forge 负责。crates/aster_forge_webdav/src/response.rs (1)
172-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win"空响应 + Cache-Control: no-store"这套逻辑抄了四遍。
三个文件里各自维护同一套"建空响应、插
no-store"的代码,没有共享。改成一个pub(crate)函数放 response.rs 里,其余模块直接调用,省得以后改策略时漏改导致行为不一致。
crates/aster_forge_webdav/src/response.rs#L172-L178:把这里的no_store_empty_response改成pub(crate) fn,作为唯一实现。crates/aster_forge_webdav/src/lock.rs#L197-L203:删掉本地重复定义,改为调用crate::response::no_store_empty_response。crates/aster_forge_webdav/src/resource.rs#L139-L152:mutation_plan_error_response里手写的 empty+no-store 逻辑改为调用同一个共享函数。crates/aster_forge_webdav/src/resource.rs#L191-L202:mutation_success_response同样改为调用共享函数。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/response.rs` around lines 172 - 178, 共有的 no-store 空响应逻辑应只保留一份。将 response.rs 中的 no_store_empty_response 暴露为 pub(crate) 唯一实现;在 crates/aster_forge_webdav/src/lock.rs 的对应位置删除本地定义并调用 crate::response::no_store_empty_response,在 crates/aster_forge_webdav/src/resource.rs 的 mutation_plan_error_response 和 mutation_success_response 中也改为调用该共享函数,分别涉及 139-152 和 191-202 行范围。crates/aster_forge_webdav/tests/protocol.rs (1)
629-651: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win前置条件的"抑制"分支没被测到。
evaluate_http_download_preconditions里has_if_match/has_if_none_match两个抑制条件是整个函数最容易写反的地方,现在一条用例都没覆盖。建议各补一个:If-Match命中时If-Unmodified-Since(一个本会失败的日期)必须被忽略;If-None-Match不命中时If-Modified-Since必须被忽略、返回Proceed。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/tests/protocol.rs` around lines 629 - 651, Extend etag_and_date_preconditions_keep_http_precedence with two evaluate_http_download_preconditions cases: verify a matching If-Match suppresses a failing If-Unmodified-Since check, and verify a nonmatching If-None-Match suppresses If-Modified-Since and returns DavPrecondition::Proceed. Use dates and entity tags that clearly exercise each suppression branch.crates/aster_forge_webdav/src/request.rs (1)
139-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win挂载前缀剥离逻辑与
protocol.rs里的strip_mount_prefix逐字重复。
crates/aster_forge_webdav/src/protocol.rs第 548-557 行已有同样规则的私有助手。两份拷贝日后只要有一处调整,就会出现"请求目标放行、Destination拒绝"这种自相矛盾的边界。把那个助手升成pub(crate)复用即可。♻️ 建议复用同一助手
protocol.rs:-fn strip_mount_prefix<'a>(path: &'a str, prefix: &str) -> Option<&'a str> { +pub(crate) fn strip_mount_prefix<'a>(path: &'a str, prefix: &str) -> Option<&'a str> {
request.rs:- let relative = uri - .path() - .strip_prefix(mount_path) - .filter(|_| { - mount_path == "/" - || uri.path() == mount_path - || uri - .path() - .as_bytes() - .get(mount_path.len()) - .is_some_and(|byte| *byte == b'/') - }) - .ok_or_else(|| { + let relative = crate::protocol::strip_mount_prefix(uri.path(), mount_path) + .ok_or_else(|| { DavProtocolError::bad_request("Request target must stay under WebDAV prefix") })?;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/request.rs` around lines 139 - 153, Reuse the existing `strip_mount_prefix` helper from `protocol.rs` in the request handling code by changing its visibility to `pub(crate)` and importing/calling it from `request.rs`. Remove the duplicated inline `strip_prefix` and boundary-filter logic while preserving the existing bad-request behavior when the request target is outside the WebDAV mount.Cargo.toml (1)
64-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value两套百分号编解码依赖并存,没必要。
percent-encoding已经能做解码(percent_decode_str(..).decode_utf8()),urlencoding纯属重复。crates/aster_forge_webdav/src/path.rs第 121 行和crates/aster_forge_webdav/src/protocol.rs第 763-764 行都只用了urlencoding::decode,收敛到一个库能少一份供应链面,也避免两套语义(比如+处理、非 UTF-8 行为)在不同模块里悄悄分叉。♻️ 建议的收敛方向
-urlencoding = "2"
path.rs/protocol.rs内改用:percent_encoding::percent_decode_str(raw) .decode_utf8() .map_err(|_| DavPathError::InvalidEncoding)?Also applies to: 90-90
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Cargo.toml` at line 64, Remove the redundant urlencoding dependency from Cargo.toml and replace all urlencoding::decode usage in the path handling and protocol parsing code with percent_encoding::percent_decode_str(...).decode_utf8(), mapping UTF-8 failures to the existing DavPathError::InvalidEncoding behavior. Ensure both decoding sites use the shared percent-encoding semantics and update dependency declarations accordingly.crates/aster_forge_webdav/src/path.rs (2)
22-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
decoded字段是raw的完全冗余副本,字段名还和语义相反。
DavPath::new里decoded = raw.as_bytes().to_vec(),即每个路径都多存一份同样的字节;而叫raw的字段存的其实是解码后的规范路径,叫decoded的存的是它的字节。读代码的人第一反应必然是反的。♻️ 建议合并为单一字段
pub struct DavPath { - raw: String, - decoded: Vec<u8>, + canonical: String, }pub fn as_bytes(&self) -> &[u8] { - &self.decoded + self.canonical.as_bytes() }
new/root/as_str/is_collection同步改用canonical即可,行为不变。Also applies to: 138-148
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/path.rs` around lines 22 - 25, 将 DavPath 合并为单一的 canonical 字段,删除冗余且语义相反的 decoded 字段,并将现有 raw 字段重命名为 canonical。同步更新 DavPath::new、root、as_str 和 is_collection 中的字段访问,保持规范路径内容及现有行为不变。
28-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value两处等价 API 冗余。
decode_relative_path返回的String就是path.as_str().to_string(),调用方拿两份同样的东西;relative()也只是as_str()的别名。留着不痛,但公共 API 面越小越省事:要么让decode_relative_path只返回DavPath,要么把relative()标为#[deprecated]或直接删掉。Also applies to: 156-160
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/path.rs` around lines 28 - 32, 简化 decode_relative_path 的公共 API,改为只返回 DavPath,并同步更新所有调用方以通过 DavPath::as_str() 获取字符串;同时移除或标记等价的 relative() API 为弃用,避免继续暴露重复接口。crates/aster_forge_webdav/src/backend.rs (3)
236-262: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win批量默认实现是串行 N 次调用,注释里得说清楚这是 fallback。
get_props_many逐个await self.get_props(...),get_props_many_for_targets更是直接丢掉DavPropertyTarget退回按路径查。PROPFINDDepth: 1打一个上千条目的集合时,没覆盖这两个方法的产品就是上千次往返 —— 而这个方法存在的唯一理由就是"批量"。默认实现本身没错,但请在文档注释里明确"默认实现为串行回退,生产适配器应覆盖",否则产品会以为默认就是批量的。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/backend.rs` around lines 236 - 262, 为 get_props_many 和 get_props_many_for_targets 添加文档注释,明确说明它们是逐项调用的串行回退实现,并非真正的批量查询;同时指出生产环境适配器应覆盖这些方法以实现批量处理。保持现有实现逻辑不变。
273-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
DavLock和DavLockInfo是同一个东西的两副面孔。字段几乎逐一对应(
owner/owner_xml、path: Box<DavPath>/path: DavPath、owner: Option<Box<DavXmlElement>>/Option<DavXmlElement>),只是装箱与命名不同。两处都要维护,任何字段新增都得改两遍,还得写一个手工转换。如果
Box只是为了压DavLockError::Conflict(DavLock)的体积,那把Conflict(Box<DavLock>)装箱、让DavLock本身保持平铺,然后删掉DavLockInfo更干净。Also applies to: 349-359
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/backend.rs` around lines 273 - 284, 合并重复的 DavLockInfo/DavLock 数据模型:删除 DavLockInfo,保留扁平的 DavLock 字段结构,并将 DavLockError::Conflict 改为持有 Box<DavLock> 以缩小错误体积。更新相关构造、转换和模式匹配代码,统一使用 DavLock,确保现有字段语义与协议行为不变。
94-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
i64的id谈不上 "Opaque"。文档说这是"不透明的产品侧标识",但类型直接把产品钉在整数主键上。用 UUID、字符串路径 ID 或复合键的产品没法表达,只能自己造映射表。既然是端口契约,要么改成泛型/
Cow<'_, str>/新类型包装,要么把注释改成实话("产品侧数值主键")。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/backend.rs` around lines 94 - 99, 将 DavPropertyTarget 上方的文档注释改为准确描述其约束,明确说明 id 是产品侧数值主键,而不是不透明的产品侧标识;保持现有 kind、i64 id 字段及其端口契约不变。crates/aster_forge_webdav/tests/actix.rs (1)
31-46: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win上限测试只走了单块路径,跨块累加才是真正会写错的地方。
Bytes::from_static(b"12345")一次到位,测不到"每块都不超限、合计超限"这种典型漏判。建议加一个多块 payload(TestRequest可以喂多段,或直接构造分块流)验证累计计数。同理可顺手补
limit = 0的边界。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/tests/actix.rs` around lines 31 - 46, 扩展 bounded_xml_body_accepts_the_exact_limit_and_rejects_one_byte_over 测试,使用多块 payload 验证每块均未超限但累计字节数超过限制时 collect_bounded_xml_body 返回 DavBodyError::XmlTooLarge;同时补充 limit = 0 的边界用例,覆盖空体与非空体的预期行为。crates/aster_forge_webdav/src/xml.rs (3)
386-392: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value回退到无命名空间的
lang属性属于自作多情。
element_from_forge用attribute.qualified_name()存键,XML 的语言属性永远是xml:lang。这个or_else(get("lang"))唯一效果是:客户端某个私有属性恰好叫lang,就被当成语言继承源写进 PROPPATCH 的持久化属性里。♻️ 建议去掉回退
fn xml_lang_value(element: &DavXmlElement) -> Option<&str> { - element - .attributes - .get("xml:lang") - .or_else(|| element.attributes.get("lang")) - .map(String::as_str) + element.attributes.get("xml:lang").map(String::as_str) }如果这是为兼容某个历史存量格式而故意留的,请补一行注释说明来源。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/xml.rs` around lines 386 - 392, Update xml_lang_value to read only the namespaced "xml:lang" attribute and remove the fallback to the unqualified "lang" key. Preserve the existing Option<&str> conversion and do not treat arbitrary client attributes as language inheritance sources.
367-372: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win每份请求体被完整扫两遍。
validate_xml_input内部跑一次scan_xml全量遍历,紧接着BorrowedDocument::parse_with_options又做一次安全校验加建树。webdav_parse_options()只设了trim_whitespace,safety 用的是ParseOptions::new()默认值 —— 而crates/aster_forge_webdav/tests/xml.rs:322-328拿XmlSafetyPolicy::untrusted().max_input_bytes去断言 reader 路径的边界并通过,说明默认就是untrusted()。也就是说这两次扫描的安全策略是同一套,纯粹重复。PROPFIND 请求体上限不小,热路径上白付一倍解析开销。理想做法是让
aster_forge_xml的解析入口自己保证单根完整性,把这里的前置扫描去掉;短期至少显式写上.safety_policy(XmlSafetyPolicy::untrusted()),别让两处策略靠默认值暗中对齐。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/xml.rs` around lines 367 - 372, 更新 parse_element 使用的 webdav_parse_options,显式配置 XmlSafetyPolicy::untrusted(),确保解析器安全边界与 validate_xml_input 一致而不依赖默认值;若 aster_forge_xml 的解析入口已保证单根完整性,则同时移除 parse_element 中重复的 validate_xml_input 前置扫描。
33-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value六种不同的安全违规全塌成
Malformed,排查时无从下手。
InputTooLarge、TooManyElements、TooManyAttributes、TextTooLarge、TooManyEvents、InvalidEncoding挤在同一个变体里,而crates/aster_forge_webdav/src/response.rs:126-144会把它们统一渲染成 400 "Invalid XML body"。客户端发了一个超限但格式完全正确的文档,被告知"XML 格式错误" —— 这是在说谎,InputTooLarge语义上更接近 413。
ExternalEntity/TooDeep单列了,说明分类粒度本来是有意设计的;建议至少把体积类(InputTooLarge/TextTooLarge)拆出一个TooLarge变体。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/src/xml.rs` around lines 33 - 49, 更新 XmlSafetyError 到 DavXmlError 的转换,新增独立的 TooLarge 变体并将 InputTooLarge 与 TextTooLarge 映射到该变体,而不是归入 Malformed;同步调整 DavXmlError 及 response.rs 中的渲染逻辑,使 TooLarge 返回 413,其他违规类型保持现有分类和响应行为。crates/aster_forge_webdav/tests/backend.rs (1)
6-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win名字里写着 "exhaustively",实际是七个硬编码元素的数组。
FsError以后加一个变体,这个测试照样绿灯 —— 而From<FsError>里的match才是唯一的守门人。想让名字兑现,就让编译器参与穷举:💚 让编译器强制穷举
fn filesystem_errors_map_exhaustively_to_protocol_backend_categories() { - let cases = [ + fn expected(error: FsError) -> DavBackendErrorKind { + // 新增 FsError 变体时此处不穷举会编译失败。 + match error { + FsError::NotFound => DavBackendErrorKind::NotFound, + FsError::Forbidden => DavBackendErrorKind::Forbidden, + FsError::GeneralFailure => DavBackendErrorKind::Internal, + FsError::Exists => DavBackendErrorKind::AlreadyExists, + FsError::InsufficientStorage => DavBackendErrorKind::InsufficientStorage, + FsError::TooLarge => DavBackendErrorKind::PayloadTooLarge, + FsError::BadRequest => DavBackendErrorKind::InvalidInput, + } + } + + let cases = [ (FsError::NotFound, DavBackendErrorKind::NotFound),配合把断言改成同时比对
expected(error)与表格值。更彻底的做法是给FsError加一个ALL: &[FsError]常量并在match处留下守卫注释。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_webdav/tests/backend.rs` around lines 6 - 22, Update filesystem_errors_map_exhaustively_to_protocol_backend_categories so the test derives coverage from every FsError variant rather than only the seven hardcoded cases. Add or reuse an exhaustive FsError::ALL collection, ensure the From<FsError> match remains compiler-checked for new variants, and compare each mapped result with the table’s expected DavBackendErrorKind.crates/aster_forge_xml/src/parser.rs (1)
243-258: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win把
ExternalEntity的分类依赖标清楚。当前逻辑依赖
quick-xml把<!ENTITY归为SyntaxError::InvalidBangMarkup,且error_position()指向报错标记首字节;如果库升级改动这些契约,这里会静默退成Malformed,让 WebDAV 从 403no-external-entities变成 400。现在的b"<!ENTITY x 'boom'><a/>"测试已经在兜底,但别让这份隐式依赖只剩在错误分支里。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aster_forge_xml/src/parser.rs` around lines 243 - 258, 在解析错误映射逻辑中明确封装并说明 ExternalEntity 对 quick-xml 错误类型和 error_position() 语义的依赖,避免该契约只隐含在当前分支中;更新相关测试以覆盖该分类,并确保库升级导致错误类型或位置变化时不会静默退回 Malformed。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aster_forge_utils/src/http_range.rs`:
- Around line 82-87: Update the range-unit handling in the parser around the
`raw.strip_prefix("bytes=")` logic to trim leading whitespace and match the
`bytes` prefix case-insensitively, while preserving the existing single-range
and `MultipleRangesUnsupported` checks. Ensure unsupported units and multiple
ranges are represented so the `response.rs` handling can fall back to a 200 full
response rather than mapping them to 416.
In `@crates/aster_forge_webdav/src/backend.rs`:
- Around line 317-323: 将后端 trait 中 unlock、refresh 和 delete 的 Result 错误类型从 ()
统一替换为与 lock 相同的 DavLockError,并同步更新所有实现及调用方的错误处理,使协议层能够区分 Conflict、LimitExceeded
和 Backend 等错误并据此选择状态码;保持成功返回值和现有业务语义不变。
In `@crates/aster_forge_webdav/src/path.rs`:
- Around line 59-72: 更新 child_relative_path,禁止使用 String::from_utf8_lossy:对非
UTF-8 的 name 显式返回失败,并在拼接路径前将 name 中的路径分隔符进行百分号编码,避免其被解释为层级。保留现有 parent 拼接逻辑及
is_collection 追加结尾斜杠的行为,同时同步调整调用方以处理新的失败结果。
In `@crates/aster_forge_webdav/src/protocol.rs`:
- Around line 228-244: Update the same-origin validation in the URI authority
matching branch to compare normalized destination and request hosts: remove
userinfo and treat http port 80 and https port 443 as equivalent to an omitted
port. Preserve the existing case-insensitive scheme/host checks, invalid-header
handling, and BadRequest response; leave the proposed 502 error-category
expansion out of this change.
- Around line 382-389: Update the token collection logic in the surrounding
condition traversal to collect only non-negated token conditions, excluding
tokens represented by `Not <token>` from the submitted lock-token list.
Synchronize the affected assertions in `tests/protocol.rs` so they reflect that
negated tokens are not treated as submitted.
- Around line 284-302: The If-header evaluation must use OR semantics across
groups per RFC 4918: succeed when any group’s list evaluates true, and return
precondition_failed only when none do. Update the loop around if_header.groups
and adjust the related test
if_evaluator_requires_every_tagged_resource_group_to_match and its documentation
to reflect the corrected behavior.
- Around line 414-425: Update the timeout parsing branch in the protocol
function to clamp parsed Second-* values above maximum to maximum instead of
returning a bad-request error, matching the existing Infinite behavior. Preserve
normal values and invalid-header handling, and update the related protocol tests
so Second-604801 and Second-18446744073709551615 expect maximum while Extension
still returns 400.
In `@crates/aster_forge_webdav/src/response.rs`:
- Around line 237-301: 更新 plan_download_response 中的 Range 选择逻辑,结合请求头 If-Range
校验当前 etag 或 last_modified;仅在 If-Range 匹配时解析并使用 DavDownloadBody::Range。If-Range
缺失或校验失败时忽略 Range,继续返回完整资源的 200 响应,并保留现有无 If-Range 请求的行为。
In `@crates/aster_forge_webdav/src/xml_response.rs`:
- Around line 403-421: Update property_element so client-supplied reserved
prefixes "xml" and "xmlns" are not used when constructing the QName or namespace
declaration. Fall back to default_property_prefix for those prefixes, while
preserving valid client prefixes and the existing DAV namespace handling.
- Around line 332-378: Reorder child construction in active_lock_element so the
activelock elements follow RFC 4918 order: lockscope, locktype, depth, optional
owner, optional timeout, locktoken, then lockroot. Move the existing depth
element before owner and preserve the current values and optional-field
behavior. Add an XML response test asserting these element positions in order,
rather than only checking contains.
In `@crates/aster_forge_webdav/src/xml.rs`:
- Around line 367-372: 统一 XML 安全校验边界:在
crates/aster_forge_webdav/src/xml.rs:367-372 的 parse_element 中移除重复的
validate_xml_input,并在 webdav_parse_options() 中显式配置
XmlSafetyPolicy::untrusted();在 crates/aster_forge_webdav/src/xml.rs:115-121 的
parse_reader 复用同一路径以执行单根完整性校验,并补充 parse_reader(Cursor::new(b"<a/><b/>")) 的拒绝断言,与
crates/aster_forge_webdav/tests/xml.rs:113 对齐。
---
Outside diff comments:
In `@docs/index.md`:
- Around line 64-68: Correct the alphabetical ordering in the crate list in
docs/index.md by moving aster_forge_webdav after aster_forge_validation and
before aster_forge_xml, while preserving the ordering of all other entries.
---
Nitpick comments:
In `@Cargo.toml`:
- Line 64: Remove the redundant urlencoding dependency from Cargo.toml and
replace all urlencoding::decode usage in the path handling and protocol parsing
code with percent_encoding::percent_decode_str(...).decode_utf8(), mapping UTF-8
failures to the existing DavPathError::InvalidEncoding behavior. Ensure both
decoding sites use the shared percent-encoding semantics and update dependency
declarations accordingly.
In `@crates/aster_forge_webdav/src/backend.rs`:
- Around line 236-262: 为 get_props_many 和 get_props_many_for_targets
添加文档注释,明确说明它们是逐项调用的串行回退实现,并非真正的批量查询;同时指出生产环境适配器应覆盖这些方法以实现批量处理。保持现有实现逻辑不变。
- Around line 273-284: 合并重复的 DavLockInfo/DavLock 数据模型:删除 DavLockInfo,保留扁平的
DavLock 字段结构,并将 DavLockError::Conflict 改为持有 Box<DavLock>
以缩小错误体积。更新相关构造、转换和模式匹配代码,统一使用 DavLock,确保现有字段语义与协议行为不变。
- Around line 94-99: 将 DavPropertyTarget 上方的文档注释改为准确描述其约束,明确说明 id
是产品侧数值主键,而不是不透明的产品侧标识;保持现有 kind、i64 id 字段及其端口契约不变。
In `@crates/aster_forge_webdav/src/path.rs`:
- Around line 22-25: 将 DavPath 合并为单一的 canonical 字段,删除冗余且语义相反的 decoded 字段,并将现有
raw 字段重命名为 canonical。同步更新 DavPath::new、root、as_str 和 is_collection
中的字段访问,保持规范路径内容及现有行为不变。
- Around line 28-32: 简化 decode_relative_path 的公共 API,改为只返回 DavPath,并同步更新所有调用方以通过
DavPath::as_str() 获取字符串;同时移除或标记等价的 relative() API 为弃用,避免继续暴露重复接口。
In `@crates/aster_forge_webdav/src/request.rs`:
- Around line 139-153: Reuse the existing `strip_mount_prefix` helper from
`protocol.rs` in the request handling code by changing its visibility to
`pub(crate)` and importing/calling it from `request.rs`. Remove the duplicated
inline `strip_prefix` and boundary-filter logic while preserving the existing
bad-request behavior when the request target is outside the WebDAV mount.
In `@crates/aster_forge_webdav/src/response.rs`:
- Around line 172-178: 共有的 no-store 空响应逻辑应只保留一份。将 response.rs 中的
no_store_empty_response 暴露为 pub(crate) 唯一实现;在
crates/aster_forge_webdav/src/lock.rs 的对应位置删除本地定义并调用
crate::response::no_store_empty_response,在
crates/aster_forge_webdav/src/resource.rs 的 mutation_plan_error_response 和
mutation_success_response 中也改为调用该共享函数,分别涉及 139-152 和 191-202 行范围。
In `@crates/aster_forge_webdav/src/xml.rs`:
- Around line 386-392: Update xml_lang_value to read only the namespaced
"xml:lang" attribute and remove the fallback to the unqualified "lang" key.
Preserve the existing Option<&str> conversion and do not treat arbitrary client
attributes as language inheritance sources.
- Around line 367-372: 更新 parse_element 使用的 webdav_parse_options,显式配置
XmlSafetyPolicy::untrusted(),确保解析器安全边界与 validate_xml_input 一致而不依赖默认值;若
aster_forge_xml 的解析入口已保证单根完整性,则同时移除 parse_element 中重复的 validate_xml_input 前置扫描。
- Around line 33-49: 更新 XmlSafetyError 到 DavXmlError 的转换,新增独立的 TooLarge 变体并将
InputTooLarge 与 TextTooLarge 映射到该变体,而不是归入 Malformed;同步调整 DavXmlError 及
response.rs 中的渲染逻辑,使 TooLarge 返回 413,其他违规类型保持现有分类和响应行为。
In `@crates/aster_forge_webdav/tests/actix.rs`:
- Around line 31-46: 扩展
bounded_xml_body_accepts_the_exact_limit_and_rejects_one_byte_over 测试,使用多块
payload 验证每块均未超限但累计字节数超过限制时 collect_bounded_xml_body 返回
DavBodyError::XmlTooLarge;同时补充 limit = 0 的边界用例,覆盖空体与非空体的预期行为。
In `@crates/aster_forge_webdav/tests/backend.rs`:
- Around line 6-22: Update
filesystem_errors_map_exhaustively_to_protocol_backend_categories so the test
derives coverage from every FsError variant rather than only the seven hardcoded
cases. Add or reuse an exhaustive FsError::ALL collection, ensure the
From<FsError> match remains compiler-checked for new variants, and compare each
mapped result with the table’s expected DavBackendErrorKind.
In `@crates/aster_forge_webdav/tests/protocol.rs`:
- Around line 629-651: Extend etag_and_date_preconditions_keep_http_precedence
with two evaluate_http_download_preconditions cases: verify a matching If-Match
suppresses a failing If-Unmodified-Since check, and verify a nonmatching
If-None-Match suppresses If-Modified-Since and returns DavPrecondition::Proceed.
Use dates and entity tags that clearly exercise each suppression branch.
In `@crates/aster_forge_xml/src/parser.rs`:
- Around line 243-258: 在解析错误映射逻辑中明确封装并说明 ExternalEntity 对 quick-xml 错误类型和
error_position() 语义的依赖,避免该契约只隐含在当前分支中;更新相关测试以覆盖该分类,并确保库升级导致错误类型或位置变化时不会静默退回
Malformed。
In `@docs/crates/aster_forge_webdav.md`:
- Around line 18-41: 在“Forge 负责”清单中补充 PUT 请求处理职责,覆盖 put.rs 中的
If-Match/If-None-Match 前置条件、create/create_new 选择、X-Expected-Entity-Length
优先级、collection 目标的 405 拒绝,以及成功时 201/204 响应选择,明确这些逻辑由 Forge 负责。
In `@docs/guide/new-project-integration.md`:
- Line 86: 在文档中的“默认 feature / 显式 feature”边界表(102-117 行)补充 aster_forge_webdav 的显式
actix feature,并与依赖声明中的 features = ["actix"] 保持一致。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 61be86f2-2d46-4f23-967f-f5c1a04b3e33
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (40)
Cargo.tomlREADME.mdREADME.zh.mdcrates/aster_forge_utils/src/http_range.rscrates/aster_forge_utils/src/lib.rscrates/aster_forge_webdav/AGENTS.mdcrates/aster_forge_webdav/Cargo.tomlcrates/aster_forge_webdav/src/actix.rscrates/aster_forge_webdav/src/backend.rscrates/aster_forge_webdav/src/deltav.rscrates/aster_forge_webdav/src/event.rscrates/aster_forge_webdav/src/lib.rscrates/aster_forge_webdav/src/lock.rscrates/aster_forge_webdav/src/path.rscrates/aster_forge_webdav/src/property.rscrates/aster_forge_webdav/src/protocol.rscrates/aster_forge_webdav/src/put.rscrates/aster_forge_webdav/src/request.rscrates/aster_forge_webdav/src/resource.rscrates/aster_forge_webdav/src/response.rscrates/aster_forge_webdav/src/xml.rscrates/aster_forge_webdav/src/xml_response.rscrates/aster_forge_webdav/tests/actix.rscrates/aster_forge_webdav/tests/backend.rscrates/aster_forge_webdav/tests/deltav.rscrates/aster_forge_webdav/tests/event.rscrates/aster_forge_webdav/tests/lock.rscrates/aster_forge_webdav/tests/property.rscrates/aster_forge_webdav/tests/protocol.rscrates/aster_forge_webdav/tests/put.rscrates/aster_forge_webdav/tests/resource.rscrates/aster_forge_webdav/tests/response.rscrates/aster_forge_webdav/tests/xml.rscrates/aster_forge_webdav/tests/xml_response.rscrates/aster_forge_xml/src/parser.rscrates/aster_forge_xml/tests/xml.rsdocs/crates/aster_forge_utils.mddocs/crates/aster_forge_webdav.mddocs/guide/new-project-integration.mddocs/index.md
## Summary Consolidate If-header evaluation, lock enforcement, and parent validation into unified ports with Actix transport adapters. Parse known request grammars directly from source-backed XML arena without materializing intermediate DOM. ## Changes ### WebDAV Protocol Layer - Add `enforce_if_header_with_backends` to resolve ETag/lock state from canonical filesystem and lock system ports - Add `enforce_unlocked`, `enforce_parent_unlocked` to reject operations when conflicting lock tokens were not submitted for lock root - Add `unsubmitted_lock_conflicts` to filter locks whose tokens were not provided - Add `enforce_parent_collection` to require mutation target parent exists as collection - Add `ensure_lock_target_exists` to create empty lock-null file when allowed - Add `replace_relative_prefix` for recursive mutation path rebasing - Add `lock_discovery_element` to build DAV:lockdiscovery property from backend locks - Move `DavLockInfo` fields into `DavLock` backend type (path, owner, timeouts) - Add `DavEvent::completed` to build transport-neutral event from request head, deliberately excluding sensitive If tokens, credentials, and request bodies ### Actix Transport Adapters - Add `protocol_error_response`, `converted_headers` for unified error mapping - Add `enforce_if_header_with_backends`, `enforce_unlocked`, `enforce_parent_unlocked` Actix wrappers that map protocol/backend errors to HttpResponse - Add `evaluate_http_etag_preconditions` adapter for ETag precondition evaluation ### XML Parser Optimization - Parse PROPFIND, PROPPATCH, LOCK request grammars directly from `aster_forge_xml` source-backed arena without validation pass or full DOM copy - Materialize `DavXmlElement` only for owner/property subtrees crossing backend boundary - Consolidate ENTITY declaration detection in `map_quick_xml_error_at` across document parser, streaming reader, and validator entry points - Extract common `utf8` and error mapping to `syntax` module ### Tests - Add 603-line guard.rs with fake backends covering ETag + lock token resolution, tagged lock root matching, parent lock enforcement, parent collection validation, lock-null file creation, and metadata/open/flush error propagation - Add actix.rs adapter tests for header conversion and precondition evaluation - Add event.rs test confirming completed event excludes sensitive lock tokens - Add xml.rs test ensuring consistent ENTITY classification across all entry points - Add resource.rs test for recursive path rebasing ### Documentation - Update aster_forge_webdav.md to reflect unified guard ports, direct XML arena traversal, Actix adapter coverage, and event sanitization contract
…f-Range - Remove urlencoding dependency; use percent-encoding directly for paths - Fix If header evaluation: use OR between tagged resource groups - Add If-Range validator support for conditional range requests - Fix Range header: accept case-insensitive unit, trim leading whitespace - Fix origin comparison: validate authority with port defaults and reject userinfo - Fix lock timeout: clamp oversized values instead of rejecting - Change DavLockError::Conflict to Box to reduce enum size - Add DavLockError::TokenMismatch variant - Change unlock/refresh/delete signatures to return DavLockError - Fix child_relative_path: reject separator characters, return Result - Add InvalidChildName and map TooLarge to PAYLOAD_TOO_LARGE responses - Fix activelock XML element order: depth before owner/timeout - Filter reserved xml/xmlns prefixes from custom property namespaces - Add XmlSafetyPolicy::untrusted() for request parsing - Improve test coverage: chunked payload, If-Range validators, port matching - Update docs: clarify DavPropertyTarget as numeric primary key
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aster_forge_webdav/src/backend.rs`:
- Line 94: Remove the product-specific resource kind and numeric primary key
from the DavFileSystem backend contract, including DavPropertyTarget and the
get_props_many_for_targets signature. Expose only DavPath at the trait boundary,
and move batch-key construction plus path-to-primary-key mapping into each
adapter’s implementation while preserving existing batched property-read
behavior.
In `@crates/aster_forge_webdav/src/lock.rs`:
- Around line 82-87: 更新 lock 冲突过滤流程及 submitted_lock_tokens,只将 negated 为 false 的
If 状态令牌视为已提交的解锁令牌,排除 Not 条件中的令牌。保留正常匹配令牌的现有行为,并补充回归测试验证 If: (Not <匹配令牌>) 时仍返回
423 Locked。
In `@crates/aster_forge_webdav/src/path.rs`:
- Around line 125-134: 在 DavPath::new 中调用 percent_decode_str
之前检查原始编码路径,拒绝不区分大小写的 %2F 和 %5C 序列,并返回现有的路径错误类型;仅在检查通过后继续 UTF-8 解码和
clean_decoded_path,保持其他路径处理行为不变。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 271a093c-f934-43bf-a983-16e95237fed6
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
Cargo.tomlcrates/aster_forge_metrics/src/prometheus.rscrates/aster_forge_utils/src/http_range.rscrates/aster_forge_webdav/Cargo.tomlcrates/aster_forge_webdav/src/actix.rscrates/aster_forge_webdav/src/backend.rscrates/aster_forge_webdav/src/deltav.rscrates/aster_forge_webdav/src/event.rscrates/aster_forge_webdav/src/lib.rscrates/aster_forge_webdav/src/lock.rscrates/aster_forge_webdav/src/path.rscrates/aster_forge_webdav/src/protocol.rscrates/aster_forge_webdav/src/request.rscrates/aster_forge_webdav/src/resource.rscrates/aster_forge_webdav/src/response.rscrates/aster_forge_webdav/src/xml.rscrates/aster_forge_webdav/src/xml_response.rscrates/aster_forge_webdav/tests/actix.rscrates/aster_forge_webdav/tests/deltav.rscrates/aster_forge_webdav/tests/event.rscrates/aster_forge_webdav/tests/guard.rscrates/aster_forge_webdav/tests/lock.rscrates/aster_forge_webdav/tests/property.rscrates/aster_forge_webdav/tests/protocol.rscrates/aster_forge_webdav/tests/resource.rscrates/aster_forge_webdav/tests/response.rscrates/aster_forge_webdav/tests/xml.rscrates/aster_forge_webdav/tests/xml_response.rscrates/aster_forge_xml/src/document.rscrates/aster_forge_xml/src/parser.rscrates/aster_forge_xml/src/stream.rscrates/aster_forge_xml/src/syntax.rscrates/aster_forge_xml/tests/xml.rsdocs/crates/aster_forge_webdav.mddocs/guide/new-project-integration.mddocs/index.md
💤 Files with no reviewable changes (2)
- crates/aster_forge_webdav/Cargo.toml
- Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (14)
- docs/crates/aster_forge_webdav.md
- docs/guide/new-project-integration.md
- crates/aster_forge_webdav/tests/deltav.rs
- crates/aster_forge_webdav/src/deltav.rs
- crates/aster_forge_webdav/tests/resource.rs
- crates/aster_forge_webdav/tests/lock.rs
- crates/aster_forge_webdav/src/request.rs
- crates/aster_forge_utils/src/http_range.rs
- crates/aster_forge_webdav/src/lib.rs
- crates/aster_forge_webdav/src/xml_response.rs
- crates/aster_forge_webdav/src/response.rs
- crates/aster_forge_webdav/tests/protocol.rs
- crates/aster_forge_webdav/src/protocol.rs
- crates/aster_forge_webdav/tests/xml.rs
| conflicts.retain(|lock| { | ||
| let lock_href = href_for_dav_path(prefix, &lock.path); | ||
| let submitted_tokens = if_header.map_or_else(Vec::new, |if_header| { | ||
| submitted_lock_tokens(if_header, &lock_href, request_scheme, request_host) | ||
| }); | ||
| !submitted_tokens.iter().any(|token| token == &lock.token) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
不要把否定的 If 状态令牌当作已提交的解锁令牌。
submitted_lock_tokens 会收集所有 Token 条件,包括 Not <token>;因此该过滤器会把否定条件中的匹配令牌视为已提交,并移除本应返回 423 的冲突锁。提取时应排除 negated == true,并补充 If: (Not <匹配令牌>) 仍返回 423 Locked 的回归测试。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/aster_forge_webdav/src/lock.rs` around lines 82 - 87, 更新 lock 冲突过滤流程及
submitted_lock_tokens,只将 negated 为 false 的 If 状态令牌视为已提交的解锁令牌,排除 Not
条件中的令牌。保留正常匹配令牌的现有行为,并补充回归测试验证 If: (Not <匹配令牌>) 时仍返回 423 Locked。
…eparator validation Remove the batching abstraction DavPropertyTarget and related methods from the backend trait: - Remove DavPropertyTarget struct (kind + numeric ID pair) - Remove DavMetaData::property_target() method - Remove DavFileSystem::get_props_many_for_targets() method - Update public exports to remove DavPropertyTarget Add path security validation: - Reject percent-encoded path separators (%2F, %2f, %5C, %5c) before decoding - Prevent path traversal via encoded separators - Add contains_encoded_path_separator() helper - Add test coverage for encoded separator rejection and double-encoding behavior Update documentation to reflect that dead-property batching now uses DavPath only, with product adapters responsible for parsing database identity.
This pull request introduces the new
aster_forge_webdavcrate, adds HTTP byte range parsing utilities, and updates workspace configuration and documentation to reflect these changes. The main themes are the addition of WebDAV protocol support, improved HTTP handling utilities, and the necessary project configuration and documentation updates.New crate: WebDAV protocol support
aster_forge_webdavcrate, including itsCargo.toml, agent documentation (AGENTS.md), and the initial Actix transport adapter insrc/actix.rs. This crate provides a modular, transport-neutral WebDAV and DeltaV protocol engine, with an optional Actix integration layer. [1] [2] [3]HTTP utilities
http_range.rsinaster_forge_utils, providing transport-neutral parsing and validation for HTTP byte range requests, with comprehensive error handling and tests.aster_forge_utilsto include the new HTTP range module and improved crate-level documentation to mention HTTP range/validator handling. [1] [2]Workspace and dependency configuration
aster_forge_webdavas a workspace member in the rootCargo.tomland added required dependencies (bytes,percent-encoding,urlencoding). [1] [2] [3] [4]Documentation updates
README.mdandREADME.zh.mdto includeaster_forge_webdavin the Web and API section, ensuring visibility in both English and Chinese documentation. [1] [2]References:
Summary by CodeRabbit
DAV:version-tree)支持:文件访问、PUT、COPY/MOVE/DELETE、PROPFIND/PROPPATCH、LOCK/UNLOCK、多状态 XML 响应。bytes=Range 解析与表示能力,并将 WebDAV 协议头解析/条件判断/下载范围规划整合到传输无关层。