93 lines
2.8 KiB
TypeScript
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;
|
|
}
|