fix: close PI core chat review gaps

This commit is contained in:
2026-08-24 01:28:06 +08:00
parent 612135f911
commit bec93d0918
15 changed files with 1094 additions and 122 deletions

View File

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