import { validateImageUploadFile } from "@/lib/cdn-images"; import { ShareHttpError } from "@/lib/share/http-error"; import { MAX_SHARE_COMMENT_LENGTH, MAX_SHARE_DESCRIPTION_LENGTH, MAX_SHARE_TEXT_BYTES, } from "@/lib/share/limits"; export type ValidatedImage = { bytes: Buffer; name: string | null; type: string; size: number; }; export function validateShareText(content: unknown) { if (typeof content !== "string") { throw new ShareHttpError("Text is required", 400); } if (!content.trim()) { throw new ShareHttpError("Text cannot be blank", 400); } if (content.includes("\0")) { throw new ShareHttpError("Text cannot contain NUL bytes", 400); } if (Buffer.byteLength(content, "utf8") > MAX_SHARE_TEXT_BYTES) { throw new ShareHttpError("Text must be 5 MiB or smaller", 413); } return content; } export function validateShareDescription(value: unknown) { if (value === null || value === undefined || value === "") return null; if (typeof value !== "string") { throw new ShareHttpError("Description must be text", 400); } if (value.length > MAX_SHARE_DESCRIPTION_LENGTH) { throw new ShareHttpError("Description must be 5,000 characters or fewer", 413); } return value; } export function validateShareComment(value: unknown) { if (typeof value !== "string" || !value.trim()) { throw new ShareHttpError("Comment cannot be blank", 422); } if (value.length > MAX_SHARE_COMMENT_LENGTH) { throw new ShareHttpError("Comment must be 2,000 characters or fewer", 413); } return value; } export async function validateTextFile(file: File) { if (!file.name.toLowerCase().endsWith(".txt")) { throw new ShareHttpError("File must use the .txt extension", 415); } if (file.size > MAX_SHARE_TEXT_BYTES) { throw new ShareHttpError("Text file must be 5 MiB or smaller", 413); } if (file.size === 0) { throw new ShareHttpError("Text file cannot be empty", 400); } try { const bytes = await file.arrayBuffer(); const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes); return validateShareText(content); } catch (error) { if (error instanceof ShareHttpError) throw error; throw new ShareHttpError("Text file must contain valid UTF-8", 400); } } export async function validateOptionalImage(value: FormDataEntryValue | null) { if (!(value instanceof File) || value.size === 0) return null; const bytes = Buffer.from(await value.arrayBuffer()); try { validateImageUploadFile(value, bytes); } catch (error) { const message = error instanceof Error ? error.message : "Invalid image"; const status = message.includes("50MB") ? 413 : 415; throw new ShareHttpError(message, status); } return { bytes, name: value.name || null, type: value.type, size: value.size, } satisfies ValidatedImage; }