import { randomBytes } from 'node:crypto'; import { createServer, type ServerResponse } from 'node:http'; import type { Socket } from 'node:net'; import { extname } from 'node:path'; const MIME_TYPES: Readonly> = { '.avif': 'image/avif', '.css': 'text/css; charset=utf-8', '.gif': 'image/gif', '.html': 'text/html; charset=utf-8', '.ico': 'image/x-icon', '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.js': 'text/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.map': 'application/json; charset=utf-8', '.mp3': 'audio/mpeg', '.mp4': 'video/mp4', '.mjs': 'text/javascript; charset=utf-8', '.otf': 'font/otf', '.ogg': 'audio/ogg', '.png': 'image/png', '.svg': 'image/svg+xml; charset=utf-8', '.ttf': 'font/ttf', '.txt': 'text/plain; charset=utf-8', '.wasm': 'application/wasm', '.webm': 'video/webm', '.webp': 'image/webp', '.woff': 'font/woff', '.woff2': 'font/woff2', '.wav': 'audio/wav', }; const MAX_FILES = 2_000; const MAX_BYTES = 50 * 1024 * 1024; declare const staticArtifactSnapshotBrand: unique symbol; export interface StaticArtifactSnapshot { readonly [staticArtifactSnapshotBrand]: true; } export interface StaticArtifactFile { readonly path: string; readonly bytes: Buffer; } export interface StaticReleaseServer { readonly entryUrl: string; close(): Promise; } const snapshotFiles = new WeakMap>(); export function createStaticArtifactSnapshot( files: readonly StaticArtifactFile[], ): StaticArtifactSnapshot { if (!Array.isArray(files) || files.length === 0 || files.length > MAX_FILES) { throw new Error('Static artifact file set is invalid.'); } const owned = new Map(); const destinationKeys = new Set(); let totalBytes = 0; for (const file of files) { if (!file || !isSafeArtifactPath(file.path) || !Buffer.isBuffer(file.bytes)) { throw new Error('Static artifact file is invalid.'); } const destinationKey = file.path.toLowerCase(); if (destinationKeys.has(destinationKey)) { throw new Error('Static artifact paths must be unique.'); } destinationKeys.add(destinationKey); const bytes = Buffer.from(file.bytes); totalBytes += bytes.length; if (totalBytes > MAX_BYTES) throw new Error('Static artifact is too large.'); owned.set(file.path, bytes); } const entry = owned.get('index.html'); if (!entry?.length) throw new Error('Static artifact entry is missing.'); const handle = Object.create(null) as StaticArtifactSnapshot; Object.defineProperty(handle, 'toJSON', { value: () => { throw new Error('Static artifact snapshots are Main-owned and cannot be serialized.'); }, }); Object.freeze(handle); snapshotFiles.set(handle, owned); return handle; } export function staticArtifactSnapshotFiles( snapshot: StaticArtifactSnapshot, ): readonly StaticArtifactFile[] { const files = snapshotFiles.get(snapshot); if (!files) throw new Error('Static artifact snapshot must be Main-owned.'); return Array.from(files, ([path, bytes]) => ({ path, bytes: Buffer.from(bytes) })); } export async function startStaticReleaseServer( snapshot: StaticArtifactSnapshot, ): Promise { const files = snapshotFiles.get(snapshot); if (!files) throw new Error('Static artifact snapshot must be Main-owned.'); const nonce = randomBytes(24).toString('hex'); const sockets = new Set(); const server = createServer((request, response) => { response.setHeader('Cache-Control', 'no-store'); response.setHeader('X-Content-Type-Options', 'nosniff'); if (request.method !== 'GET' && request.method !== 'HEAD') { response.setHeader('Allow', 'GET, HEAD'); sendEmpty(response, 405); return; } try { const rawUrl = request.url ?? ''; if (rawUrl.includes('\0') || rawUrl.includes('\\')) throw new Error('unsafe'); const url = new URL(rawUrl, 'http://127.0.0.1'); const prefix = `/${nonce}/`; if (!url.pathname.startsWith(prefix)) throw new Error('outside'); const rawPath = url.pathname.slice(prefix.length); if (!rawPath || rawPath.endsWith('/')) throw new Error('directory'); let decoded: string; try { decoded = decodeURIComponent(rawPath); } catch { throw new Error('encoding'); } if (!isSafeArtifactPath(decoded)) throw new Error('unsafe'); const bytes = files.get(decoded); if (!bytes) throw new Error('missing'); response.statusCode = 200; response.setHeader('Content-Type', MIME_TYPES[extname(decoded).toLowerCase()] ?? 'application/octet-stream'); response.setHeader('Content-Length', String(bytes.length)); response.end(request.method === 'HEAD' ? undefined : bytes); } catch { sendEmpty(response, 404); } }); server.on('connection', (socket) => { sockets.add(socket); socket.once('close', () => sockets.delete(socket)); }); try { await new Promise((resolveListen, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', () => { server.removeListener('error', reject); resolveListen(); }); }); } catch (error) { for (const socket of sockets) socket.destroy(); throw error; } const address = server.address(); if (!address || typeof address === 'string') throw new Error('Static artifact server did not bind TCP.'); let closed: Promise | null = null; return { entryUrl: `http://127.0.0.1:${address.port}/${nonce}/index.html`, close: () => { closed ??= new Promise((resolveClose) => { server.close(() => resolveClose()); server.closeAllConnections?.(); for (const socket of sockets) socket.destroy(); setTimeout(resolveClose, 1_000).unref?.(); }); return closed; }, }; } function isSafeArtifactPath(path: string): boolean { if (typeof path !== 'string' || !path || path.startsWith('/') || path.includes('\\') || path.includes('\0')) return false; const parts = path.split('/'); if (parts.some((part) => !part || part === '.' || part === '..' || part.includes(':') || /[. ]$/.test(part))) return false; return !parts.some((part) => /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part)); } function sendEmpty(response: ServerResponse, status: number): void { if (response.headersSent) return; response.statusCode = status; response.setHeader('Content-Length', '0'); response.end(); }