import { davError, davResponse } from "@/worker/dav/http"; import { buildMultistatus, emptyProp, type MultistatusResponse, type Propstat } from "@/worker/dav/multistatus"; import { isProtectedLivePropForResource, type DavResourcePropKind } from "@/worker/dav/props"; import type { ProppatchInstruction } from "@/worker/xml/dav-request"; export interface DavResultError { ok: false; status: number; message: string; } export function davResultError(result: DavResultError): Response { return davError(result.status, result.message); } export function proppatchMultistatus(href: string, instructions: ProppatchInstruction[]): Response { return davResponse( buildMultistatus([ { href, propstats: [{ status: 200, props: instructions.map((item) => emptyProp(item.prop)) }], }, ]), 207, ); } export function protectedProppatchMultistatus( href: string, instructions: ProppatchInstruction[], resourceKind: DavResourcePropKind, ): Response | null { const protectedProp = instructions.find((instruction) => isProtectedLivePropForResource(instruction.prop, resourceKind), ); if (!protectedProp) return null; return davResponse( buildMultistatus([ { href, propstats: [ { status: 403, props: [emptyProp(protectedProp.prop)] }, { status: 424, props: instructions.filter((item) => item !== protectedProp).map((item) => emptyProp(item.prop)), }, ], }, ]), 207, ); } export function syncCollectionMultistatus( result: { resources: TResource[]; changes: { href: string; changeType: string }[]; syncToken: string; truncated: boolean; }, propstatsForResource: (resource: TResource) => Propstat[], ): Response { const responses: MultistatusResponse[] = [ ...result.resources.map((resource) => ({ href: resource.href, propstats: propstatsForResource(resource), })), ...result.changes .filter((change) => change.changeType === "deleted") .map((change) => ({ href: change.href, status: 404, propstats: [] })), ]; // RFC 6578 3.10: when a sync-collection response is truncated, include an // additional response with HTTP status 507 to signal the client to retry. if (result.truncated) responses.push({ href: "/", status: 507, propstats: [] }); return davResponse(buildMultistatus(responses, { syncToken: result.syncToken }), 207); } export function multigetMultistatus( requested: { href: string; resource: TResource | null }[], propstatsForResource: (resource: TResource) => Propstat[], ): Response { return davResponse( buildMultistatus( requested.map((entry) => entry.resource ? { href: entry.href, propstats: propstatsForResource(entry.resource), } : { href: entry.href, status: 404, propstats: [] }, ), ), 207, ); } export function davObjectResponse( method: string, object: { body: BodyInit; size: number; etag: string }, contentType: string, ): Response { const headers = new Headers({ "Content-Type": contentType, "Content-Length": String(object.size), ETag: object.etag, }); return new Response(method === "HEAD" ? null : object.body, { status: 200, headers }); }