import { davError, davResponse, emptyResponse } from "@/worker/dav/http"; import { maxXmlBodyBytes } from "@/worker/dav/limits"; import type { SubjectRow } from "@/worker/db/d1/schema"; import type { AppContext } from "@/worker/types"; import { parseLockInfoXml } from "@/worker/xml/dav-request"; import { lockXml, parseTimeout } from "@/worker/webdav/lock-xml"; import { fileDavObject, resultError, type NormalizedFilesRequestPath } from "@/worker/webdav/object"; import { readFileDavWriteRequest } from "@/worker/webdav/request"; export async function handleLock(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) { const writeRequest = readFileDavWriteRequest(c.req.raw); if (writeRequest instanceof Response) return writeRequest; const body = await c.req.text(); const refresh = body.trim() === ""; let lockInfo: { scope: "exclusive" | "shared"; ownerXml: string } | null = null; if (!refresh) { try { lockInfo = parseLockInfoXml(body, maxXmlBodyBytes(c.env)); } catch (cause) { return davError(400, cause instanceof Error ? cause.message : "Invalid LOCK body"); } } else if (writeRequest.lockTokens.length === 0) { return davError(400, "LOCK refresh requires an If header lock token"); } let depth: "0" | "infinity"; if (refresh) { // RFC 4918 9.10.2: LOCK refresh ignores Depth; honor whatever the existing lock recorded. depth = "infinity"; } else { const depthHeader = c.req.header("depth"); const normalizedDepth = depthHeader?.trim().toLowerCase(); const parsedDepth = normalizedDepth === undefined ? "infinity" : normalizedDepth === "0" || normalizedDepth === "infinity" ? normalizedDepth : null; if (parsedDepth === null) return davError(400, "LOCK Depth must be 0 or infinity"); depth = parsedDepth; } const result = await fileDavObject(c.env, subject.storageId).lock({ subjectId: subject.id, storageId: subject.storageId, path: path.path, ownerXml: lockInfo?.ownerXml ?? null, scope: lockInfo?.scope ?? "exclusive", depth, timeoutSeconds: parseTimeout(c.req.header("timeout") ?? null), ifHeader: writeRequest.ifHeader, lockTokens: writeRequest.lockTokens, nowMs: writeRequest.nowMs, refresh, }); if (!result.ok) return resultError(result); const bodyXml = `${lockXml( result.lock, )}`; // RFC 4918 9.10.1: Lock-Token response header is only for new lock creation, not refresh. const headers: Record = {}; if (!refresh) headers["Lock-Token"] = `<${result.lock.token}>`; return davResponse(bodyXml, result.created ? 201 : 200, headers); } export async function handleUnlock(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) { const token = c.req.header("lock-token"); if (!token) return davError(400, "Missing Lock-Token header"); const result = await fileDavObject(c.env, subject.storageId).unlock({ subjectId: subject.id, storageId: subject.storageId, path: path.path, lockToken: token, nowMs: Date.now(), }); if (!result.ok) return resultError(result); return emptyResponse(204); }