import { davError } from "@/worker/dav/http"; import { maxDavDepthInfinityNodes } from "@/worker/dav/limits"; import { parentHref } from "@/worker/dav/paths"; import { davHtmlListingResponse, type DavListingEntry } from "@/worker/dav/html-listing"; import type { SubjectRow } from "@/worker/db/d1/schema"; import type { FileDavNodeView } from "@/worker/objects/file-dav-object"; import { getFileBlob } from "@/worker/r2/blobs"; import type { AppContext } from "@/worker/types"; import { fileDavObject, resultError, type NormalizedFilesRequestPath } from "@/worker/webdav/object"; import { httpDate } from "@/worker/webdav/props"; function fileListingName(node: { name: string }, href: string): string { if (href === "/files/") return "Files"; return node.name || href; } function fileListingEntries(nodes: FileDavNodeView[]) { return nodes.slice(1).map((entry): DavListingEntry => { const isCollection = entry.node.kind === "collection"; return { href: entry.href, name: fileListingName(entry.node, entry.href), kind: isCollection ? "collection" : "file", size: isCollection ? null : entry.node.size, modifiedAtMs: entry.node.modifiedAtMs, }; }); } async function handleCollectionGetHead(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) { const result = await fileDavObject(c.env, subject.storageId).propfind({ subjectId: subject.id, storageId: subject.storageId, path: path.path, depth: "1", maxNodes: maxDavDepthInfinityNodes(c.env), nowMs: Date.now(), }); if (!result.ok) return resultError(result); const target = result.nodes[0]; if (!target || target.node.kind !== "collection") return davError(405, "Resource is not a collection"); return await davHtmlListingResponse( c, { title: fileListingName(target.node, target.href), href: target.href, parentHref: path.path.segments.length === 0 ? "/" : parentHref(path.path), entries: fileListingEntries(result.nodes), }, c.req.method, ); } export async function handleGetHead(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) { const result = await fileDavObject(c.env, subject.storageId).resolveFile({ subjectId: subject.id, storageId: subject.storageId, path: path.path, ifMatch: c.req.header("if-match") ?? null, ifNoneMatch: c.req.header("if-none-match") ?? null, nowMs: Date.now(), }); if (!result.ok && result.status === 405 && result.code === "method_not_allowed") { return await handleCollectionGetHead(c, subject, path); } if (!result.ok) return resultError(result); const object = result.blob ? await getFileBlob(c.env.FILE_BLOBS, result.blob.blobKey) : null; if (result.blob && !object) return davError(404, "Blob not found"); const headers = new Headers({ "Content-Length": String(result.node.size), "Content-Type": result.node.contentType ?? object?.httpMetadata?.contentType ?? "application/octet-stream", "Last-Modified": httpDate(result.node.modifiedAtMs), }); if (result.node.etag) headers.set("ETag", result.node.etag); if (c.req.method === "HEAD") return new Response(null, { status: 200, headers }); return new Response(object?.body ?? null, { status: 200, headers }); }