56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
import type { H3Event } from "h3";
|
|
import { createError, getRequestHeader } from "h3";
|
|
export async function readBounded(
|
|
event: H3Event,
|
|
max: number,
|
|
): Promise<Buffer> {
|
|
const declared = getRequestHeader(event, "content-length");
|
|
if (declared && (!/^\d+$/u.test(declared) || Number(declared) > max))
|
|
throw createError({
|
|
statusCode: 413,
|
|
statusMessage: "Request body exceeds the limit.",
|
|
});
|
|
const chunks: Buffer[] = [];
|
|
let total = 0;
|
|
for await (const value of event.node.req) {
|
|
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
total += chunk.byteLength;
|
|
if (total > max)
|
|
throw createError({
|
|
statusCode: 413,
|
|
statusMessage: "Request body exceeds the limit.",
|
|
});
|
|
chunks.push(chunk);
|
|
}
|
|
return Buffer.concat(chunks, total);
|
|
}
|
|
export async function readJson<T>(event: H3Event, max: number): Promise<T> {
|
|
const body = await readBounded(event, max);
|
|
try {
|
|
return JSON.parse(body.toString("utf8")) as T;
|
|
} catch {
|
|
throw createError({ statusCode: 400, statusMessage: "Malformed JSON." });
|
|
}
|
|
}
|
|
export function requireContentType(event: H3Event, expected: string) {
|
|
const type = (
|
|
(getRequestHeader(event, "content-type") || "").split(";", 1)[0] || ""
|
|
)
|
|
.trim()
|
|
.toLowerCase();
|
|
if (type !== expected)
|
|
throw createError({
|
|
statusCode: 415,
|
|
statusMessage: `Content-Type must be ${expected}.`,
|
|
});
|
|
}
|
|
export function bearerToken(event: H3Event): string | undefined {
|
|
const value = getRequestHeader(event, "authorization") || "";
|
|
return /^Bearer ([A-Za-z0-9_-]{20,})$/u.exec(value)?.[1];
|
|
}
|
|
export function publicHeaders(event: H3Event) {
|
|
event.node.res.setHeader("cache-control", "no-store");
|
|
event.node.res.setHeader("referrer-policy", "no-referrer");
|
|
event.node.res.setHeader("x-content-type-options", "nosniff");
|
|
}
|