fix: close PI core chat review gaps
This commit is contained in:
@@ -3,6 +3,7 @@ import type { HostApiContext } from '../context';
|
||||
import { sendJson } from '../route-utils';
|
||||
|
||||
const MAX_ATTACHMENT_BYTES = 16 * 1024 * 1024;
|
||||
const ATTACHMENT_ID_PATTERN = /^[A-Za-z0-9-]{1,64}$/;
|
||||
const SUPPORTED_IMAGE_MIMES = new Set([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
@@ -10,6 +11,37 @@ const SUPPORTED_IMAGE_MIMES = new Set([
|
||||
'image/gif',
|
||||
]);
|
||||
|
||||
function matchesImageSignature(data: Uint8Array, mime: string): boolean {
|
||||
if (mime === 'image/png') {
|
||||
return data.length >= 8
|
||||
&& data[0] === 0x89
|
||||
&& data[1] === 0x50
|
||||
&& data[2] === 0x4e
|
||||
&& data[3] === 0x47
|
||||
&& data[4] === 0x0d
|
||||
&& data[5] === 0x0a
|
||||
&& data[6] === 0x1a
|
||||
&& data[7] === 0x0a;
|
||||
}
|
||||
if (mime === 'image/jpeg') {
|
||||
return data.length >= 3
|
||||
&& data[0] === 0xff
|
||||
&& data[1] === 0xd8
|
||||
&& data[2] === 0xff;
|
||||
}
|
||||
if (mime === 'image/gif') {
|
||||
if (data.length < 6) return false;
|
||||
const header = Buffer.from(data.subarray(0, 6)).toString('ascii');
|
||||
return header === 'GIF87a' || header === 'GIF89a';
|
||||
}
|
||||
if (mime === 'image/webp') {
|
||||
return data.length >= 12
|
||||
&& Buffer.from(data.subarray(0, 4)).toString('ascii') === 'RIFF'
|
||||
&& Buffer.from(data.subarray(8, 12)).toString('ascii') === 'WEBP';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function contentType(req: IncomingMessage): string {
|
||||
const value = req.headers['content-type'];
|
||||
return (Array.isArray(value) ? value[0] : value)?.split(';', 1)[0]?.trim().toLowerCase() ?? '';
|
||||
@@ -17,7 +49,7 @@ function contentType(req: IncomingMessage): string {
|
||||
|
||||
async function readBoundedBody(req: IncomingMessage): Promise<Uint8Array> {
|
||||
const declared = Number(req.headers['content-length']);
|
||||
if (Number.isFinite(declared) && (declared <= 0 || declared > MAX_ATTACHMENT_BYTES)) {
|
||||
if (Number.isFinite(declared) && declared > MAX_ATTACHMENT_BYTES) {
|
||||
throw Object.assign(new Error('Attachment size is invalid'), { status: 413 });
|
||||
}
|
||||
const chunks: Buffer[] = [];
|
||||
@@ -86,12 +118,24 @@ export async function handleCodingAttachmentRoutes(
|
||||
if (!SUPPORTED_IMAGE_MIMES.has(mime)) {
|
||||
throw Object.assign(new Error('Attachment MIME is invalid'), { status: 400 });
|
||||
}
|
||||
const attachment = await attachments.put(await readBoundedBody(req), mime);
|
||||
const body = await readBoundedBody(req);
|
||||
if (!matchesImageSignature(body, mime)) {
|
||||
throw Object.assign(new Error('Attachment body is not the declared image type'), { status: 400 });
|
||||
}
|
||||
const attachment = await attachments.put(body, mime);
|
||||
sendJson(res, 201, attachment);
|
||||
return true;
|
||||
}
|
||||
|
||||
const attachmentId = decodeURIComponent(contentMatch?.[1] ?? '');
|
||||
let attachmentId: string;
|
||||
try {
|
||||
attachmentId = decodeURIComponent(contentMatch?.[1] ?? '');
|
||||
} catch {
|
||||
throw Object.assign(new Error('Attachment id is invalid'), { status: 400 });
|
||||
}
|
||||
if (!ATTACHMENT_ID_PATTERN.test(attachmentId)) {
|
||||
throw Object.assign(new Error('Attachment id is invalid'), { status: 400 });
|
||||
}
|
||||
const record = await attachments.read(attachmentId);
|
||||
if (!SUPPORTED_IMAGE_MIMES.has(record.mime)) {
|
||||
throw Object.assign(new Error('Attachment MIME is invalid'), { status: 400 });
|
||||
|
||||
Reference in New Issue
Block a user