import { davError, emptyResponse } from "@/worker/dav/http"; import { maxFileBytes as maxConfiguredFileBytes } from "@/worker/dav/limits"; import type { SubjectRow } from "@/worker/db/d1/schema"; import { blobGcMessage } from "@/worker/queues/blob-gc"; import { BodyTooLargeError, putFileBlob } from "@/worker/r2/blobs"; import { enqueueBlobGc } from "@/worker/r2/gc"; import type { AppContext } from "@/worker/types"; import { fileDavObject, resultError, type NormalizedFilesRequestPath } from "@/worker/webdav/object"; import { parseContentLength, readFileDavWriteRequest } from "@/worker/webdav/request"; export async function handlePut(c: AppContext, subject: SubjectRow, path: NormalizedFilesRequestPath) { const writeRequest = readFileDavWriteRequest(c.req.raw); if (writeRequest instanceof Response) return writeRequest; const contentType = c.req.header("content-type") ?? "application/octet-stream"; const contentLength = parseContentLength(c.req.raw); const maxFileBytes = maxConfiguredFileBytes(c.env); const object = fileDavObject(c.env, subject.storageId); const begin = await object.beginWrite({ subjectId: subject.id, storageId: subject.storageId, path: path.path, contentLength, contentType, contentLanguage: c.req.header("content-language") ?? null, ...writeRequest, maxFileBytes, }); if (!begin.ok) return resultError(begin); let r2Result: { size: number; etag: string | null }; try { r2Result = await putFileBlob(c.env.FILE_BLOBS, { key: begin.blobKey, body: c.req.raw.body, contentType, contentLength, maxBytes: maxFileBytes, }); } catch (cause) { await object.abortWrite({ uploadId: begin.uploadId }); if (cause instanceof BodyTooLargeError) return davError(413, cause.message); throw cause; } const commit = await object.commitWrite({ subjectId: subject.id, storageId: subject.storageId, uploadId: begin.uploadId, size: r2Result.size, r2Etag: r2Result.etag, contentType, contentLanguage: c.req.header("content-language") ?? null, nowMs: Date.now(), }); if (!commit.ok) { await object.abortWrite({ uploadId: begin.uploadId }); await enqueueBlobGc(c.env, [ blobGcMessage({ subjectId: subject.id, storageId: subject.storageId, blobId: begin.blobId, blobKey: begin.blobKey, notBeforeMs: Date.now(), }), ]); return resultError(commit); } await enqueueBlobGc( c.env, commit.blobGc.map((blob) => blobGcMessage({ subjectId: subject.id, storageId: subject.storageId, blobId: blob.blobId, blobKey: blob.blobKey, notBeforeMs: Date.now(), }), ), ); return emptyResponse(commit.created ? 201 : 204, { ETag: commit.node.etag ?? "" }); }