import type { QName } from "@/worker/xml/namespaces"; import { DAV_NS } from "@/worker/xml/namespaces"; export interface PropValue { qname: QName; valueXml: string; } export interface Propstat { status: number; props: PropValue[]; } export interface MultistatusResponse { href: string; status?: number; propstats: Propstat[]; } const REASON: Record = { 200: "OK", 201: "Created", 204: "No Content", 207: "Multi-Status", 400: "Bad Request", 403: "Forbidden", 404: "Not Found", 409: "Conflict", 412: "Precondition Failed", 423: "Locked", 424: "Failed Dependency", 507: "Insufficient Storage", }; export function escapeXml(value: string): string { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } export function statusLine(status: number): string { return `HTTP/1.1 ${status} ${REASON[status] ?? "Status"}`; } function prefixedName(qname: QName): string { return qname.nsUri === DAV_NS ? `D:${qname.localName}` : `x:${qname.localName}`; } function namespaceAttribute(qname: QName): string { return qname.nsUri === DAV_NS ? "" : ` xmlns:x="${escapeXml(qname.nsUri)}"`; } function propXml(prop: PropValue): string { if (prop.valueXml === "") { return `<${prefixedName(prop.qname)}${namespaceAttribute(prop.qname)}/>`; } if (prop.qname.nsUri === DAV_NS) return `${prop.valueXml}`; return prop.valueXml; } export function buildMultistatus(responses: MultistatusResponse[], options: { syncToken?: string } = {}): string { const body = responses .map((response) => { const status = response.status ? `${statusLine(response.status)}` : ""; const propstats = response.propstats .filter((propstat) => propstat.props.length > 0) .map( (propstat) => `${propstat.props.map(propXml).join("")}${statusLine( propstat.status, )}`, ) .join(""); return `${escapeXml(response.href)}${status}${propstats}`; }) .join(""); // RFC 6578 3.4: sync-token is emitted after all response elements inside multistatus. const syncToken = options.syncToken ? `${escapeXml(options.syncToken)}` : ""; return `${body}${syncToken}`; } export function emptyProp(qname: QName): PropValue { return { qname, valueXml: "" }; }