Files

93 lines
2.8 KiB
TypeScript

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;
}