A file's revision reads the same after a save as it does when listed
Agents / agent (pull_request) Successful in 4m32s

This commit is contained in:
temeddix
2026-09-26 02:24:53 +09:00
parent 5db8ed318a
commit ef6092481b
2 changed files with 27 additions and 5 deletions
+25 -3
View File
@@ -185,7 +185,7 @@ pub fn multistatus(body: &[u8]) -> Result<Vec<Found>, ErrorCode> {
b"getcontentlength" => {
entry.length = value.trim().parse().unwrap_or(0);
}
b"getetag" => entry.etag = Some(value.trim().to_owned()),
b"getetag" => entry.etag = Some(etag(&value)),
_ => {}
}
}
@@ -207,6 +207,21 @@ pub fn multistatus(body: &[u8]) -> Result<Vec<Found>, ErrorCode> {
Ok(found)
}
/// An entity tag in the quoted form an `ETag` header carries.
///
/// Some servers, WsgiDAV among them, list `getetag` bare while their `ETag`
/// header quotes the same tag, so a revision read from a listing and one
/// returned by a write would differ for the same bytes. Quoting a bare tag
/// gives every revision one spelling, and `If-Match` accepts that form.
pub fn etag(value: &str) -> String {
let value = value.trim();
if value.starts_with('"') || value.starts_with("W/") {
value.to_owned()
} else {
format!("\"{value}\"")
}
}
/// Strips any namespace prefix, so `d:href` and `D:href` read the same.
fn local(name: &[u8]) -> &[u8] {
match name.iter().position(|byte| *byte == b':') {
@@ -264,8 +279,8 @@ pub fn removable(found: &[Found]) -> Result<(), ErrorCode> {
#[cfg(test)]
mod tests {
use super::{
Body, Found, basic, children, multistatus, name_of, outcome, range_outcome,
removable, url,
Body, Found, basic, children, etag, multistatus, name_of, outcome,
range_outcome, removable, url,
};
use crate::sdk::ErrorCode;
@@ -414,4 +429,11 @@ mod tests {
assert_eq!(name_of(&found[1].href), "a b.md");
Ok(())
}
#[test]
fn bare_etags_are_quoted_like_headers() {
assert_eq!(etag("1996000-1790356720-264"), "\"1996000-1790356720-264\"");
assert_eq!(etag(" \"v1\" "), "\"v1\"");
assert_eq!(etag("W/\"v1\""), "W/\"v1\"");
}
}
+2 -2
View File
@@ -246,7 +246,7 @@ impl GuestWriter for DavWriter {
.headers
.iter()
.find(|header| header.name.eq_ignore_ascii_case("etag"))
.map(|header| header.value.clone());
.map(|header| dav::etag(&header.value));
Ok(Some(FileInfo {
kind: EntryKind::File,
size: self.written.get(),
@@ -369,7 +369,7 @@ impl Guest for Webdav {
.headers
.iter()
.find(|header| header.name.eq_ignore_ascii_case("etag"))
.map(|header| header.value.clone())
.map(|header| dav::etag(&header.value))
});
Ok(Reader::new(DavReader {
call,