Skip to content
File

Blob: src/worker/webdav/get-head.ts

typescript78 lines
1import { davError } from "@/worker/dav/http";
2import { maxDavDepthInfinityNodes } from "@/worker/dav/limits";
3import { parentHref } from "@/worker/dav/paths";
4import { davHtmlListingResponse, type DavListingEntry } from "@/worker/dav/html-listing";
5import type { SubjectRow } from "@/worker/db/d1/schema";
6import type { FileDavNodeView } from "@/worker/objects/file-dav-object";
7import { getFileBlob } from "@/worker/r2/blobs";
8import type { AppContext } from "@/worker/types";
9import { fileDavObject, resultError, type NormalizedFilesRequestPath } from "@/worker/webdav/object";
10import { httpDate } from "@/worker/webdav/props";
11 
12function fileListingName(node: { name: string }, href: string): string {
13 if (href === "/files/") return "Files";
14 return node.name || href;
15}
16 
17function fileListingEntries(nodes: FileDavNodeView[]) {
18 return nodes.slice(1).map((entry): DavListingEntry => {
19 const isCollection = entry.node.kind === "collection";
20 return {
21 href: entry.href,
22 name: fileListingName(entry.node, entry.href),
23 kind: isCollection ? "collection" : "file",
24 size: isCollection ? null : entry.node.size,
25 modifiedAtMs: entry.node.modifiedAtMs,
26 };
27 });
28}
29 
30async function handleCollectionGetHead(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) {
31 const result = await fileDavObject(c.env, subject.storageId).propfind({
32 subjectId: subject.id,
33 storageId: subject.storageId,
34 path: path.path,
35 depth: "1",
36 maxNodes: maxDavDepthInfinityNodes(c.env),
37 nowMs: Date.now(),
38 });
39 if (!result.ok) return resultError(result);
40 const target = result.nodes[0];
41 if (!target || target.node.kind !== "collection") return davError(405, "Resource is not a collection");
42 return await davHtmlListingResponse(
43 c,
44 {
45 title: fileListingName(target.node, target.href),
46 href: target.href,
47 parentHref: path.path.segments.length === 0 ? "/" : parentHref(path.path),
48 entries: fileListingEntries(result.nodes),
49 },
50 c.req.method,
51 );
52}
53 
54export async function handleGetHead(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) {
55 const result = await fileDavObject(c.env, subject.storageId).resolveFile({
56 subjectId: subject.id,
57 storageId: subject.storageId,
58 path: path.path,
59 ifMatch: c.req.header("if-match") ?? null,
60 ifNoneMatch: c.req.header("if-none-match") ?? null,
61 nowMs: Date.now(),
62 });
63 if (!result.ok && result.status === 405 && result.code === "method_not_allowed") {
64 return await handleCollectionGetHead(c, subject, path);
65 }
66 if (!result.ok) return resultError(result);
67 const object = result.blob ? await getFileBlob(c.env.FILE_BLOBS, result.blob.blobKey) : null;
68 if (result.blob && !object) return davError(404, "Blob not found");
69 const headers = new Headers({
70 "Content-Length": String(result.node.size),
71 "Content-Type": result.node.contentType ?? object?.httpMetadata?.contentType ?? "application/octet-stream",
72 "Last-Modified": httpDate(result.node.modifiedAtMs),
73 });
74 if (result.node.etag) headers.set("ETag", result.node.etag);
75 if (c.req.method === "HEAD") return new Response(null, { status: 200, headers });
76 return new Response(object?.body ?? null, { status: 200, headers });
77}