UNPKG

@auth70/bodyguard

Version:

Fetch API compatible streaming JSON and form data body parser and guard

156 lines (155 loc) 5.54 kB
export const MAX_KEYS = 100; export const MAX_DEPTH = 10; export const MAX_SIZE = 1024 * 1024; export const MAX_KEY_LENGTH = 100; export const CONTENT_TYPES = [ "application/json", "application/x-www-form-urlencoded", "multipart/form-data", "text/plain", ]; export const ERRORS = { BODY_NOT_AVAILABLE: "BODY_NOT_AVAILABLE", INVALID_TYPE: "INVALID_TYPE", INVALID_CONTENT_TYPE: "INVALID_CONTENT_TYPE", NO_CONTENT_TYPE: "NO_CONTENT_TYPE", MAX_SIZE_EXCEEDED: "MAX_SIZE_EXCEEDED", TOO_MANY_KEYS: "TOO_MANY_KEYS", INVALID_INPUT: "INVALID_INPUT", TOO_DEEP: "TOO_DEEP", KEY_TOO_LONG: "KEY_TOO_LONG", TOO_MANY_FILES: "TOO_MANY_FILES", FILENAME_TOO_LONG: "FILENAME_TOO_LONG", }; /** * Utility functions */ /** * Create a byte stream counter. This is a transform stream that counts the number of bytes. If the number of bytes exceeds the maxSize, it will throw an error. * @param {ReadableStream<Uint8Array>} stream - The input stream * @param {number} maxSize - The maximum number of bytes * @param {(reason?: any) => void} reject - A reject function to call when the max size is exceeded * @returns {TransformStream<Uint8Array>} - The transform stream */ export function createByteStreamCounter(stream, maxSize, reject) { let bytes = 0; return new TransformStream({ transform(chunk, controller) { bytes += chunk.length; if (bytes > maxSize) { if (reject) reject(new Error(ERRORS.MAX_SIZE_EXCEEDED)); else throw new Error(ERRORS.MAX_SIZE_EXCEEDED); } controller.enqueue(chunk); } }); } /** * Possible cast a value to a number or boolean if it matches the criteria. * Also converts plus signs to spaces if convertPluses is true. * @param {string} value - The value to cast * @param {BodyguardConfig | BodyguardFormConfig} config - The configuration * @returns {string | number | boolean} - The casted value */ export function possibleCast(value, config) { value = value.replace(/[\r\n]+$/, ''); if (value.trim() === '') return value; if (!isNaN(Number(value)) && config.castNumbers) return Number(value); if (value === 'true' && config.castBooleans) return true; if (value === 'false' && config.castBooleans) return false; if (config.convertPluses) return value.replace(/\+/g, ' '); return value; } /** * Assign a nested value to an object * @param {Record<string, any>} obj - The object to assign to * @param {string[]} path - The path to assign to * @param {any} value - The value to assign */ export function assignNestedValue(obj, path, value) { let current = obj; for (let i = 0; i < path.length; i++) { const segment = path[i]; const arrayMatch = segment.match(/^(\w+)(?:\[(\d*?)\])?$/); if (arrayMatch && arrayMatch[1]) { const key = arrayMatch[1]; const index = arrayMatch[2]; if (i === path.length - 1) { // last segment if (index !== undefined) { // Explicit index if (index) { // array1[1], array1[2], ... if (!Array.isArray(current[key])) { current[key] = []; } current[key][parseInt(index, 10)] = value; } else { // array1[] behavior if (!Array.isArray(current[key])) { current[key] = []; } current[key].push(value); } } else { current[key] = value; } } else { if (index !== undefined) { if (!Array.isArray(current[key])) { current[key] = []; } if (index) { // If there is an explicit index, use it if (!current[key][parseInt(index, 10)]) { current[key][parseInt(index, 10)] = {}; } current = current[key][parseInt(index, 10)]; } else { // If it's the implicit push behavior if (!current[key].length || typeof current[key][current[key].length - 1] !== 'object') { current[key].push({}); } current = current[key][current[key].length - 1]; } } else { if (!current[key]) { current[key] = {}; } current = current[key]; } } } else { throw new Error("Invalid segment encountered in segment: " + segment + " of path: " + path.join('.')); } } } /** * Extract a nested key into an array of segments * @param {string} keyName - The key name * @returns {string[]} - The segments */ export function extractNestedKey(keyName) { const path = []; let buffer = ''; for (const char of keyName) { if (char === '.') { if (buffer) path.push(buffer); buffer = ''; } else { buffer += char; } } if (buffer) path.push(buffer); return path; }