File
Blob: src/worker/webdav/request.ts
| 1 | import { davError } from "@/worker/dav/http"; |
| 2 | import { parseIfHeader, positiveLockTokens, type ParsedIfHeader } from "@/worker/dav/if-header"; |
| 3 | |
| 4 | export interface FileDavWriteRequest { |
| 5 | ifMatch: string | null; |
| 6 | ifNoneMatch: string | null; |
| 7 | ifHeader: ParsedIfHeader; |
| 8 | lockTokens: string[]; |
| 9 | nowMs: number; |
| 10 | } |
| 11 | |
| 12 | export function parseContentLength(request: Request): number | null { |
| 13 | const value = request.headers.get("content-length"); |
| 14 | if (!value) return null; |
| 15 | const parsed = Number.parseInt(value, 10); |
| 16 | return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; |
| 17 | } |
| 18 | |
| 19 | export function requestIfHeader(request: Request): { ok: true; header: ParsedIfHeader } | Response { |
| 20 | const parsed = parseIfHeader(request.headers.get("if")); |
| 21 | if (!parsed.ok) return davError(400, parsed.message); |
| 22 | return parsed; |
| 23 | } |
| 24 | |
| 25 | export function readFileDavWriteRequest(request: Request, nowMs = Date.now()): FileDavWriteRequest | Response { |
| 26 | const parsedIf = requestIfHeader(request); |
| 27 | if (parsedIf instanceof Response) return parsedIf; |
| 28 | return { |
| 29 | ifMatch: request.headers.get("if-match"), |
| 30 | ifNoneMatch: request.headers.get("if-none-match"), |
| 31 | ifHeader: parsedIf.header, |
| 32 | lockTokens: positiveLockTokens(parsedIf.header), |
| 33 | nowMs, |
| 34 | }; |
| 35 | } |
| 36 | |
| 37 | export function validateDeleteDepth(value: string | null): Response | null { |
| 38 | // RFC 4918 9.6.1: DELETE on a collection is Depth: infinity; other values are invalid. |
| 39 | const normalized = value?.trim().toLowerCase() ?? null; |
| 40 | return normalized !== null && normalized !== "infinity" ? davError(400, "DELETE Depth must be infinity") : null; |
| 41 | } |
| 42 | |
| 43 | export function readCopyDepth(value: string | null): "0" | "infinity" | Response { |
| 44 | // RFC 4918 9.8.3: COPY Depth must be absent, "0", or "infinity". |
| 45 | const normalized = value?.trim().toLowerCase() ?? null; |
| 46 | if (normalized !== null && normalized !== "0" && normalized !== "infinity") { |
| 47 | return davError(400, "COPY Depth must be 0 or infinity"); |
| 48 | } |
| 49 | return normalized === "0" ? "0" : "infinity"; |
| 50 | } |
| 51 | |
| 52 | export function validateMoveDepth(value: string | null): Response | null { |
| 53 | // RFC 4918 9.9.3: MOVE on a collection is Depth: infinity; Depth: 0 is not permitted. |
| 54 | const normalized = value?.trim().toLowerCase() ?? null; |
| 55 | return normalized !== null && normalized !== "infinity" ? davError(400, "MOVE Depth must be infinity") : null; |
| 56 | } |