fix(drive): answer a runtime unsupported PROPPATCH the way an absent one is

A backend with setProps that rejects unsupported for this path got 501 while a
backend without setProps got 403, for the same inability. 403 is the answer
kept: WebDAV allows it for a property the server will not store, where 501 says
PROPPATCH itself is unimplemented, which is not what the server means.

Refs #179

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 21:10:54 +00:00
co-authored by Claude
parent 3519c1c63e
commit d934f0fee1
+20 -6
View File
@@ -1,5 +1,6 @@
import type { Context } from "@webnet/http/server"
import type { AsyncVFS } from "@webnet/vfs"
import { isUnsupported } from "@webnet/vfs/fallback"
import { vfsPathToHref } from "../../common/utils.js"
import { parse, parseProppatchBody, buildMultistatusXml, makePropKey } from "../xml.js"
import { el, text } from "@webnet/xml"
@@ -9,17 +10,25 @@ import type { DAVServerOptions } from "../types.js"
const d = (localName: string) => el(DAV_NS, localName)
const dt = (localName: string, value: string) => text(DAV_NS, localName, value)
/**
* WebDAV lets a server answer 403 for a property it will not store, and a filesystem with no
* property storage is that case for every property. 501 would say PROPPATCH itself is
* unimplemented, which is not what the server means, and clients read it as a broken server rather
* than a limited one. A backend that has `setProps` but rejects `unsupported` for this path is the
* same answer rather than a different one, so both arrive here.
*/
function cannotStoreProps(ctx: Context): void {
ctx.res.setStatus(403, "Forbidden")
ctx.res.body = null
}
export async function handleProppatch(
ctx: Context,
vfs: AsyncVFS,
vfsPath: string,
opts: DAVServerOptions,
): Promise<void> {
if (!vfs.setProps) {
ctx.res.setStatus(403, "Forbidden")
ctx.res.body = null
return
}
if (!vfs.setProps) return cannotStoreProps(ctx)
const bodyText = await ctx.req.text()
const xml = parse(bodyText)
@@ -35,7 +44,12 @@ export async function handleProppatch(
delete props[makePropKey(ns, localName)]
}
await vfs.setProps(vfsPath, props)
try {
await vfs.setProps(vfsPath, props)
} catch (e) {
if (!isUnsupported(e)) throw e
return cannotStoreProps(ctx)
}
const href = vfsPathToHref(vfsPath, opts.prefix, ctx.req.url)
const allPropEls = [