From 18f20170b3432f2cbc3c43084d868f160b8bb354 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Mon, 17 Aug 2026 20:38:48 +0200 Subject: [PATCH] Ask the file, not its folder, why the write was refused The 403 probe read `oc:permissions` off the parent collection and warned when `W` was missing. But Nextcloud reports `W` on files and `CK` on collections, so a directory legitimately lacks `W`: the warning fired on a healthy share and pointed at a mount that was fine. Probe the file itself. Its permissions answer the question that matters, and the status distinguishes the two cases the parent could not: a 404 means the sidecar does not exist and the refusal was about creating it, while a 200 without `W` means it exists and cannot be updated. Co-Authored-By: Claude Opus 5 --- core/dr-sync-nextcloud/src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/dr-sync-nextcloud/src/lib.rs b/core/dr-sync-nextcloud/src/lib.rs index cdbe0c2..8db855e 100644 --- a/core/dr-sync-nextcloud/src/lib.rs +++ b/core/dr-sync-nextcloud/src/lib.rs @@ -373,16 +373,18 @@ impl RemoteBackend for NextcloudBackend { // make anything worse and it is the difference between a // server-side fix and a client-side one. if status == reqwest::StatusCode::FORBIDDEN { - let parent = path - .as_str() - .rsplit_once('/') - .map(|(head, _)| head) - .unwrap_or(""); + // The *file*, not the folder. `W` is reported on files and + // `CK` on collections, so a directory legitimately lacks `W` + // and reading that as read-only would send someone to change a + // mount that is fine. If the file exists without `W` the + // refusal is an update it will not allow; a 404 here means it + // does not exist and the refusal was about creating it. + let target = path.as_str().to_string(); let probe = self .client .request( reqwest::Method::from_bytes(b"PROPFIND").expect("valid method"), - self.url_for(&RemotePath::new(parent)), + self.url_for(path), ) .basic_auth(&self.login, Some(&self.password)) .header("Depth", "0") @@ -394,6 +396,7 @@ impl RemoteBackend for NextcloudBackend { .await; match probe { Ok(r) => { + let status = r.status(); let text = r.text().await.unwrap_or_default(); let perms = text .split("") @@ -401,11 +404,11 @@ impl RemoteBackend for NextcloudBackend { .and_then(|t| t.split('<').next()) .unwrap_or("(not reported)"); log::warn!( - " parent {parent} permissions: {perms} \ - (W = write, C = create; absent means read-only)" + " target {target} -> {status} permissions: {perms} \ + (on a file W = update; a 404 means it does not exist yet)" ); } - Err(e) => log::warn!(" could not read parent permissions: {e}"), + Err(e) => log::warn!(" could not read {target} permissions: {e}"), } }