diff --git a/deno_dist/middleware/body-limit/index.ts b/deno_dist/middleware/body-limit/index.ts new file mode 100644 index 000000000..1a27ac87e --- /dev/null +++ b/deno_dist/middleware/body-limit/index.ts @@ -0,0 +1,131 @@ +import type { Context, MiddlewareHandler } from '../../index.ts' + +const bodyTypes = ['body', 'json', 'form', 'text'] as const + +type bodyLimitOptions = { + type?: typeof bodyTypes[number] + limit?: number + handler?: (c: Context) => Response +}[] + +type bodyLimitObject = { + body?: bodyLimitOptions[number] + json?: bodyLimitOptions[number] + form?: bodyLimitOptions[number] + text?: bodyLimitOptions[number] +} + +const defaultOptions: bodyLimitOptions = bodyTypes.map((bodyType) => { + return { + type: bodyType, + limit: NaN, + handler: (c: Context) => { + return c.text('413 Request Entity Too Large', 413) + }, + } +}) + +const allowMethods = ['POST', 'PUT', 'PATCH'] + +const deleteSameType = (options: bodyLimitOptions): bodyLimitObject => { + const objects: bodyLimitObject = {} + + for (let i = 0, len = options.length; i < len; i++) { + const option = options[i] + objects[option.type ?? 'body'] = { + type: 'body', + limit: NaN, + handler: (c: Context) => { + return c.text('413 Request Entity Too Large', 413) + }, + ...option, + } + } + + return objects +} + +/** + * Built-in Middleware for limit body size + * + * @example + * ```ts + * app.post( + * '/hello', + * bodyLimit({ + * type: 'text', // body | json | form | text + * limit: 15 * Uint.b, // byte, + * handler: (c) => { + * return c.text("oveflow :("); + * } + * }), + * (c) => { + * return c.text('pass :)') + * } + * ) + * + * /** + * body: all type + * text: taxt/plain + * json: application/json + * form: application/x-www-form-urlencoded + * *\/ + * ``` + */ +export const bodyLimit = ( + options: bodyLimitOptions | bodyLimitOptions[number] = defaultOptions +): MiddlewareHandler => { + const limitOptions: bodyLimitObject = deleteSameType([...defaultOptions, ...[options].flat()]) + + return async function bodylimit(c: Context, next: () => Promise) { + if (allowMethods.includes(c.req.method.toUpperCase())) { + const req = c.req.raw.clone() + const blob = await req.blob() + const bodySize = blob.size + + let type: typeof bodyTypes[number] = 'body' + const ContentType = req.headers.get('Content-Type')?.trim() ?? '' + + if (ContentType.startsWith('text/plain')) { + type = 'text' + } else if (ContentType.startsWith('application/json')) { + type = 'json' + } else if (ContentType.startsWith('application/x-www-form-urlencoded')) { + type = 'form' + } + + const limitOption = limitOptions[type] + const bodyLimitOption = limitOptions['body'] + + if ( + limitOption && + limitOption.limit && + limitOption.handler && + !isNaN(limitOption.limit) && + bodySize > limitOption.limit + ) { + return limitOption.handler(c) + } else if ( + bodyLimitOption && + bodyLimitOption.limit && + bodyLimitOption.handler && + !isNaN(bodyLimitOption.limit) && + bodySize > bodyLimitOption.limit + ) { + return bodyLimitOption.handler(c) + } + } + + await next() + } +} + +/** + * Uint any + * @example + * ```ts + * const limit = 100 * Uint.kb // 100kb + * const limit2 = 1 * Unit.gb // 1gb + * ``` + */ +export const Uint = { b: 1, kb: 1000, mb: 1000 ** 2, gb: 1000 ** 3, tb: 1000 ** 4, pb: 1000 ** 5 } diff --git a/deno_dist/middleware/body-parser/index.ts b/deno_dist/middleware/body-parser/index.ts deleted file mode 100644 index 40803a3d0..000000000 --- a/deno_dist/middleware/body-parser/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { Context, MiddlewareHandler } from '../../index.ts' - -type bodyParserOptions = { - type?: 'body' | 'json' | 'form' | 'text' - limit?: number - handler?: (c: Context) => Response -} - -const defaultOptions: bodyParserOptions = { - type: 'body', - limit: 0, - handler: (c: Context) => { - return c.text('413 Request Entity Too Large', 413) - }, -} - -const allowMethods = ['POST', 'PUT', 'PATCH'] - -export const bodyParser = ( - options: bodyParserOptions = defaultOptions -): MiddlewareHandler<{ - Variables: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - body: () => T - } -}> => { - const parseOptions: bodyParserOptions = { - ...defaultOptions, - ...options, - } - - return async function bodyParse(c: Context, next: () => Promise) { - if ( - allowMethods.includes(c.req.method.toUpperCase()) && - parseOptions.handler && - parseOptions.limit && - parseOptions.type - ) { - const req = c.req.raw.clone() - const blob = await req.blob() - const bodySize = blob.size - - if (parseOptions.limit !== 0 && bodySize >= parseOptions.limit) { - return parseOptions.handler(c) - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let parsedData: any = blob - - switch (parseOptions.type) { - case 'body': - parsedData = blob - break - case 'text': - parsedData = await blob.text() - break - case 'json': - parsedData = JSON.parse(await blob.text()) - break - case 'form': - parsedData = await c.req.formData() - break - default: - parsedData = await blob.text() - break - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - c.set('body', () => parsedData as T) - } - - await next() - } -} - -/** - * Uint any - * @example - * ```ts - * const limit = 100 * Uint.kb // 100kb - * const limit2 = 1 * Unit.gb // 1gb - * ``` - */ -export const Uint = { b: 1, kb: 1000, mb: 1000 ** 2, gb: 1000 ** 3, tb: 1000 ** 4, pb: 1000 ** 5 }