UNPKG

@thi.ng/server

Version:

Minimal HTTP server with declarative routing, static file serving and freely extensible via pre/post interceptors

64 lines (63 loc) 2.15 kB
import { findSequence } from "@thi.ng/arrays"; import { illegalArgs } from "@thi.ng/errors"; const DECODER = new TextDecoder(); const parseRequestMultipartData = (req) => { const ctype = req.headers["content-type"]; if (!ctype) illegalArgs("missing content-type"); const boundary = /boundary=([a-z0-9-]+)/i.exec(ctype); if (!boundary) illegalArgs("missing boundary"); return parseMultipartData(req, boundary[1]); }; async function* parseMultipartData(req, boundary) { const parser = chunkParser(boundary); for await (let chunk of req) yield* parser(chunk); } const chunkParser = (boundary) => { const boundaryBytes = new TextEncoder().encode(boundary); let buf = new Uint8Array(0); let from = 0; return function* (chunk) { const $buf = new Uint8Array(buf.length + chunk.length); $buf.set(buf); $buf.set(chunk, buf.length); buf = $buf; while (buf.length) { const idx = findSequence(buf, boundaryBytes, from); if (idx < 0) { from = Math.max(buf.length - boundaryBytes.length + 1, 0); return; } if (idx >= 4) { yield parsePart(buf.slice(2, idx - 4)); } buf = buf.slice(idx + boundaryBytes.length); from = 0; } }; }; const MPART_SEP = [13, 10, 13, 10]; const parsePart = (buf) => { const idx = findSequence(buf, MPART_SEP); if (idx < 0) illegalArgs("invalid multipart data"); const headers = DECODER.decode(buf.slice(0, idx)).split("\r\n").reduce((acc, x) => { const idx2 = x.indexOf(":"); let key = x.substring(0, idx2).toLowerCase(); let val = x.substring(idx2 + 1).trim(); if (key === "content-disposition") { const name = /name="([a-z0-9_.-]{1,64})"/i.exec(val); if (name) acc["name"] = name[1]; const filename = /filename="([a-z0-9_.+\-~@ ()\[\]]{1,128})"/i.exec(val); if (filename) acc["filename"] = filename[1]; } acc[key] = val; return acc; }, {}); const body = buf.slice(idx + 4); return headers["content-type"] ? { headers, body } : { headers, body: DECODER.decode(body) }; }; export { chunkParser, parseMultipartData, parsePart, parseRequestMultipartData };