import type { DavPatAuth } from "@/worker/auth/basic"; import { davError, emptyResponse, methodNotAllowed } from "@/worker/dav/http"; import type { SubjectRow } from "@/worker/db/d1/schema"; import type { AppContext } from "@/worker/types"; export interface DavRequestContext { c: AppContext; subject: SubjectRow; auth: DavPatAuth; pathname: string; } export interface DavArea { name: "files" | "caldav" | "carddav" | "discovery"; allow: string; optionsHeaders: HeadersInit; matches(pathname: string): boolean; canUseMethod(auth: DavPatAuth, method: string): boolean; handle(context: DavRequestContext): Promise; } function allowedMethods(allow: string): Set { return new Set(allow.split(",").map((method) => method.trim())); } export function findDavArea(pathname: string, areas: readonly DavArea[]): DavArea | null { return areas.find((area) => area.matches(pathname)) ?? null; } export function isMethodAllowed(area: DavArea, method: string): boolean { return allowedMethods(area.allow).has(method); } export function isExpensiveDavRequest(request: Request): boolean { if (request.method === "REPORT") return true; return request.method === "PROPFIND" && request.headers.get("depth")?.toLowerCase() === "infinity"; } export function expensiveDavRequestArea(pathname: string): string { return pathname === "/files" || pathname.startsWith("/files/") ? "files" : (pathname.split("/")[1] ?? "unknown"); } export async function dispatchDavArea(area: DavArea, context: DavRequestContext): Promise { const method = context.c.req.method; if (!area.canUseMethod(context.auth, method)) return davError(403, "PAT scope does not allow this method"); if (method === "OPTIONS") return emptyResponse(204, area.optionsHeaders); if (!isMethodAllowed(area, method)) return methodNotAllowed(area.allow); return await area.handle(context); }