@auth70/bodyguard
Version:
Fetch API compatible streaming JSON and form data body parser and guard
156 lines (155 loc) • 5.54 kB
JavaScript
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;
}