Legacy OCM shares carry no WebDAV URI — every remote access hits the sender root (folders still show as files) #474

Closed
opened 2026-09-10 10:19:11 +00:00 by Cordy · 1 comment
Owner

Follow-up to #464. Dogfood: after v0.6.154, end + re-accept of the "Summer Slam" folder (and a second test folder) still shows Download instead of Browse.

Root cause (systematic-debugging pass, evidence from internal/ocm/inbound.go + client.go on main):

  1. Nextcloud 34 sends the legacy dialect: protocol.options.sharedSecret only — no protocol.webdav block, no URI. Our shares handler fills uri only on the Protocol.Webdav != nil branch, so every NC ShareIn is stored with WebdavURI == "".
  2. IsRemoteDir / ListRemote / FetchRemote build scheme://server + WebdavURI — with the empty URI that is the Nextcloud homepage. The depth-0 PROPFIND answers non-207, IsRemoteDir returns an error…
  3. …and the accept path swallows it (derr == nil && isDir guard, no logging). So the v0.6.154 probe never had a chance, and there was zero evidence anywhere. Browse/Download against NC legacy shares are equally broken — they fetch HTML from the NC root.

Spec answer (OCM IETF draft, Share Access steps 4–5): when the share carries no webdav URI, the receiver derives the endpoint from the sender's discovery document (resourceTypes[].protocols.webdav/public.php/webdav/ on Nextcloud) and makes the PROPFIND to https://<sharedSecret>:@<sender-host><path> — i.e. Basic auth, shared secret as username (deprecated-but-kept legacy method). Our Discovery.WebdavRoot already parses that field; it was just never used for share access.

Fix (v0.6.156, TDD):

  • client.go: new remoteBase(ctx, sh) — explicit URI unchanged (Bearer); empty URI resolves the peer's cached Disco.WebdavRoot, falling back to live discovery; new authorize() sends Basic(secret, "") + X-Requested-With for legacy targets, Bearer otherwise. Wired into all three access paths, so existing stored rows heal without re-sharing.
  • IsRemoteDir: dirness decided primarily by a resourcetype collection element in the multistatus (servers that echo the request path verbatim break the trailing-slash heuristic); trailing slash stays as fallback.
  • Accept path: probe failures now slog.Warn("ocm folder probe failed", …) — a silently swallowed best-effort probe hid this bug for a release.
  • Handbook interop note.

Lesson recorded: best-effort is fine, silent is not — every best-effort remote call needs a log line on failure (same lesson as the #454 refusal logging, one layer further out).

Follow-up to #464. Dogfood: after v0.6.154, end + re-accept of the "Summer Slam" folder (and a second test folder) still shows Download instead of Browse. **Root cause** (systematic-debugging pass, evidence from `internal/ocm/inbound.go` + `client.go` on main): 1. Nextcloud 34 sends the **legacy dialect**: `protocol.options.sharedSecret` only — no `protocol.webdav` block, no URI. Our `shares` handler fills `uri` only on the `Protocol.Webdav != nil` branch, so every NC `ShareIn` is stored with `WebdavURI == ""`. 2. `IsRemoteDir` / `ListRemote` / `FetchRemote` build `scheme://server + WebdavURI` — with the empty URI that is the **Nextcloud homepage**. The depth-0 PROPFIND answers non-207, `IsRemoteDir` returns an error… 3. …and the accept path swallows it (`derr == nil && isDir` guard, no logging). So the v0.6.154 probe never had a chance, and there was zero evidence anywhere. Browse/Download against NC legacy shares are equally broken — they fetch HTML from the NC root. **Spec answer** (OCM IETF draft, Share Access steps 4–5): when the share carries no webdav URI, the receiver derives the endpoint from the sender's **discovery document** (`resourceTypes[].protocols.webdav` — `/public.php/webdav/` on Nextcloud) and makes the PROPFIND to `https://<sharedSecret>:@<sender-host><path>` — i.e. **Basic auth, shared secret as username** (deprecated-but-kept legacy method). Our `Discovery.WebdavRoot` already parses that field; it was just never used for share access. **Fix (v0.6.156, TDD):** - `client.go`: new `remoteBase(ctx, sh)` — explicit URI unchanged (Bearer); empty URI resolves the peer's cached `Disco.WebdavRoot`, falling back to live discovery; new `authorize()` sends Basic(secret, "") + `X-Requested-With` for legacy targets, Bearer otherwise. Wired into all three access paths, so existing stored rows heal without re-sharing. - `IsRemoteDir`: dirness decided primarily by a `resourcetype` `collection` element in the multistatus (servers that echo the request path verbatim break the trailing-slash heuristic); trailing slash stays as fallback. - Accept path: probe failures now `slog.Warn("ocm folder probe failed", …)` — a silently swallowed best-effort probe hid this bug for a release. - Handbook interop note. **Lesson recorded:** best-effort is fine, silent is not — every best-effort remote call needs a log line on failure (same lesson as the #454 refusal logging, one layer further out).
Cordy closed this issue 2026-09-10 10:31:35 +00:00
Author
Owner

Shipped in v0.6.156, live on both dogfoods (cairn_build_info{version="v0.6.156"} verified).

PR #475, TDD with red witnessed on the runner (probe 404 against the server root + the collection-element case) before the implementation splices. remoteBase resolves the legacy endpoint from the peer's cached discovery (live-discovery fallback), authorize sends Basic with the secret as username for legacy targets, IsRemoteDir trusts the resourcetype collection element, and probe failures now land in the server log as ocm folder probe failed.

Because the fix sits in the client target resolution, existing stored shares heal in place — Browse/Download work without re-sharing. The folder flag, though, is only probed at accept, so the already-accepted "Summer Slam" needs one final end + re-accept to flip from Download to Browse. Any probe trouble will now be visible in the cairn-openbao pod log.

Shipped in **v0.6.156**, live on both dogfoods (`cairn_build_info{version="v0.6.156"}` verified). PR #475, TDD with red witnessed on the runner (probe 404 against the server root + the collection-element case) before the implementation splices. `remoteBase` resolves the legacy endpoint from the peer's cached discovery (live-discovery fallback), `authorize` sends Basic with the secret as username for legacy targets, `IsRemoteDir` trusts the `resourcetype` collection element, and probe failures now land in the server log as `ocm folder probe failed`. Because the fix sits in the client target resolution, **existing stored shares heal in place** — Browse/Download work without re-sharing. The folder *flag*, though, is only probed at accept, so the already-accepted "Summer Slam" needs one final end + re-accept to flip from Download to Browse. Any probe trouble will now be visible in the cairn-openbao pod log.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#474
No description provided.