UNPKG

sensai

Version:

Because even AI needs a master

175 lines (174 loc) 6.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { default: function() { return _default; }, normalizeCharset: function() { return normalizeCharset; } }); const _querystring = require("../../utils/querystring"); const _error = /*#__PURE__*/ _interop_require_default(require("./error")); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Parses content type header to extract main type and charset * @param contentType The content-type header value * @returns Object containing mainType and charset */ function parseContentType(contentType) { const mainType = contentType.split(";")[0].trim().toLowerCase(); const charsetMatch = contentType.match(/charset=([^;]+)/i); const charset = charsetMatch ? normalizeCharset(charsetMatch[1].trim()) : "utf-8"; return { mainType, charset }; } /** * Normalize HTTP charset to Node.js supported encodings * @notes we do not suppor windows-1252 as it is not a standard encoding * @param charset The charset from HTTP headers * @returns Normalized charset name for Node.js Buffer */ function normalizeCharset(charset) { charset = charset.toLowerCase(); switch(charset){ case "iso-8859-1": return "latin1"; case "us-ascii": return "ascii"; default: return charset; } } /** * Collects the body data from the request stream with proper limits * @param request The IncomingMessage request object * @param contentLength Content length from header * @param limit Maximum allowed size in bytes (default: 1MB) * @returns Promise that resolves to the body as Buffer */ async function getBodyFromRequest(request, contentLength, limit = 1000000 // 1MB ) { return new Promise((resolve, reject)=>{ // Check content length immediately if available if (contentLength && contentLength > limit) { return reject(new _error.default(413, "Request entity too large")); } let complete = false; let buffer = []; let received = 0; // resolve/reject promise and clean up resources function done(err, value) { if (complete) return; complete = true; // Clean up event listeners request.removeListener("data", onData); request.removeListener("end", onEnd); request.removeListener("error", onError); request.removeListener("close", onClose); if (err) { // Unpipe everything and pause the stream request.unpipe(); request.pause(); reject(err); } else { // Free memory after creating the final buffer const result = Buffer.concat(buffer); buffer = []; resolve(result); } } // Handle data chunks function onData(chunk) { received += chunk.length; // Enforce size limit including current chunk if (received > limit) { done(new _error.default(413, "Request entity too large")); } else { buffer.push(chunk); } } // Handle stream completion function onEnd() { // Validate content length if provided if (contentLength && received !== contentLength) { done(new _error.default(400, "Request size did not match content length")); } else { done(null, Buffer.concat(buffer)); } } // Handle errors function onError(err) { done(new _error.default(400, `Request error: ${err.message}`)); } // Handle premature close function onClose() { if (!complete) { done(new _error.default(499, "Client closed request")); } } // Attach listeners request.on("data", onData); request.on("end", onEnd); request.on("error", onError); request.on("close", onClose); }); } /** * Parse the request body based on the content-type header. * Supports JSON, URL-encoded form data, and raw text. * * @param request The IncomingMessage request object * @param limit Maximum allowed size in bytes (default: 1MB) * @returns Promise that resolves to the parsed body */ async function parseBody(request, limit = 1000000 // 1MB ) { const { headers } = request; const contentType = headers["content-type"] || ""; // Parse content type once const { mainType, charset } = parseContentType(contentType); // Get content length or default to 0 if not present const contentLength = Number(headers["content-length"]) || 0; // Collect the body data const bodyBuffer = await getBodyFromRequest(request, contentLength, limit); // If body is empty, return empty object if (!bodyBuffer.length) { return {}; } // Convert buffer to string using the specified charset let body; try { body = bodyBuffer.toString(charset); } catch (err) { // Fallback to UTF-8 if the charset is not supported body = bodyBuffer.toString(); } // Parse based on content type switch(mainType){ case "application/json": try { return JSON.parse(body); } catch (err) { throw new _error.default(400, `Invalid JSON: ${err.message}`); } case "application/x-www-form-urlencoded": return (0, _querystring.parse)(body); default: // If content type is not recognized, return raw body return { raw: body }; } } const _default = parseBody;