import { BASE } from './env'; /** * Node-side multipart upload helper. Lets adversarial specs post arbitrary * bytes with arbitrary `Content-Type` claims to /api/v1/upload without * driving the UI. Crucial for MIME-spoofing, oversize, polyglot, and * filename-injection tests. * * Field shape matches [backend/src/handlers/upload.rs]: * - file (binary; carries filename + content_type in the part headers) * - caption (text, optional) * - hashtags (CSV text, optional) */ // Node 22+ ships FormData and Blob as globals — no import needed. export type UploadOptions = { filename?: string; contentType?: string; caption?: string; hashtags?: string; }; export async function uploadRaw( token: string, body: Uint8Array | Buffer, opts: UploadOptions = {} ) { const form = new FormData(); const blob = new Blob([body as any], { type: opts.contentType ?? 'application/octet-stream' }); form.append('file', blob as any, opts.filename ?? 'upload.bin'); if (opts.caption !== undefined) form.append('caption', opts.caption); if (opts.hashtags !== undefined) form.append('hashtags', opts.hashtags); return fetch(`${BASE}/api/v1/upload`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: form as any, }); } /** Convenience: read a fixture from disk and upload it. */ export async function uploadFile(token: string, path: string, opts: UploadOptions = {}) { const { readFile } = await import('node:fs/promises'); const body = await readFile(path); return uploadRaw(token, body, opts); } /** Tiny valid JPEG header — magic bytes only, useful for "claim image but is N MB of zeros" tests. */ export const JPEG_MAGIC = new Uint8Array([ 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, ]); /** Tiny valid PNG magic bytes. */ export const PNG_MAGIC = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); /** ELF header — the magic bytes of a Linux executable. `infer` reports `application/x-executable`. */ export const ELF_MAGIC = new Uint8Array([0x7f, 0x45, 0x4c, 0x46]); /** * Upload whose request body PAUSES mid-stream until you release it. * * This exists to make the release-vs-upload race deterministic instead of hoped-for. The bug it * guards is a TOCTOU: `upload` checks `uploads_locked_at` BEFORE reading the body (minutes, for a * big video) and commits the row afterwards — so a release landing in between used to let the photo * commit AFTER the export snapshot, leaving it in the live feed but permanently absent from the * keepsake. * * Racing N normal uploads against a release can't prove anything: if they all happen to commit * first (or all get rejected), the assertion passes on broken code too. Here the server is *stuck* * mid-body with its pre-flight check already passed, so the release provably lands inside the * window. Await `checkPassed`, fire the release, then `finish()`. */ export function uploadPausedMidStream( token: string, head: Buffer, tail: Buffer, opts: UploadOptions = {} ): { checkPassed: Promise; finish: () => void; response: Promise } { const boundary = '----eventsnapPausedBoundary' + Math.random().toString(16).slice(2); const filename = opts.filename ?? 'paused.jpg'; const contentType = opts.contentType ?? 'image/jpeg'; const preamble = Buffer.from( `--${boundary}\r\n` + `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n` + `Content-Type: ${contentType}\r\n\r\n` ); const epilogue = Buffer.from(`\r\n--${boundary}--\r\n`); let releaseBody: () => void; const gate = new Promise((r) => (releaseBody = r)); let markCheckPassed: () => void; const checkPassed = new Promise((r) => (markCheckPassed = r)); const body = new ReadableStream({ async pull(controller) { controller.enqueue(new Uint8Array(preamble)); controller.enqueue(new Uint8Array(head)); // The server has now read the part headers and the first bytes — which means it is PAST its // pre-flight lock check and is sitting in the body loop. This is the window. markCheckPassed(); await gate; controller.enqueue(new Uint8Array(tail)); controller.enqueue(new Uint8Array(epilogue)); controller.close(); }, }); const response = fetch(`${BASE}/api/v1/upload`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': `multipart/form-data; boundary=${boundary}`, }, body, // Node's fetch requires this for a streaming request body. duplex: 'half', } as RequestInit & { duplex: 'half' }); return { checkPassed, finish: () => releaseBody(), response }; }