UNPKG

@adonisjs/bodyparser

Version:

AdonisJs body parser to read and parse HTTP request bodies

233 lines (232 loc) 9.41 kB
"use strict"; /* * @adonisjs/bodyparser * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BodyParserMiddleware = void 0; /// <reference path="../../adonis-typings/bodyparser.ts" /> const os_1 = require("os"); const co_body_1 = __importDefault(require("@poppinss/co-body")); const path_1 = require("path"); const utils_1 = require("@poppinss/utils"); const application_1 = require("@adonisjs/application"); const helpers_1 = require("@poppinss/utils/build/helpers"); const Multipart_1 = require("../Multipart"); const streamFile_1 = require("../Multipart/streamFile"); /** * BodyParser middleware parses the incoming request body and set it as * request body to be read later in the request lifecycle. */ let BodyParserMiddleware = class BodyParserMiddleware { constructor(Config, drive) { this.drive = drive; this.config = Config.get('bodyparser', {}); } /** * Returns config for a given type */ getConfigFor(type) { const config = this.config[type]; config['returnRawBody'] = true; return config; } /** * Ensures that types exists and have length */ ensureTypes(types) { return !!(types && types.length); } /** * Returns a boolean telling if request `content-type` header * matches the expected types or not */ isType(request, types) { return !!(this.ensureTypes(types) && request.is(types)); } /** * Returns a proper Adonis style exception for popular error codes * returned by https://github.com/stream-utils/raw-body#readme. */ getExceptionFor(error) { switch (error.type) { case 'encoding.unsupported': return new utils_1.Exception(error.message, error.status, 'E_ENCODING_UNSUPPORTED'); case 'entity.too.large': return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ENTITY_TOO_LARGE'); case 'request.aborted': return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ABORTED'); default: return error; } } /** * Returns the tmp path for storing the files temporarly */ getTmpPath(config) { if (typeof config.tmpFileName === 'function') { const tmpPath = config.tmpFileName(); return (0, path_1.isAbsolute)(tmpPath) ? tmpPath : (0, path_1.join)((0, os_1.tmpdir)(), tmpPath); } return (0, path_1.join)((0, os_1.tmpdir)(), (0, helpers_1.cuid)()); } /** * Handle HTTP request body by parsing it as per the user * config */ async handle(ctx, next) { /** * Initiating the `__raw_files` private property as an object */ ctx.request['__raw_files'] = {}; const requestMethod = ctx.request.method(); /** * Only process for whitelisted nodes */ if (!this.config.whitelistedMethods.includes(requestMethod)) { ctx.logger.trace(`bodyparser skipping method ${requestMethod}`); return next(); } /** * Return early when request body is empty. Many clients set the `Content-length = 0` * when request doesn't have any body, which is not handled by the below method. * * The main point of `hasBody` is to early return requests with empty body created by * clients with missing headers. */ if (!ctx.request.hasBody()) { ctx.logger.trace('bodyparser skipping empty body'); return next(); } /** * Handle multipart form */ const multipartConfig = this.getConfigFor('multipart'); if (this.isType(ctx.request, multipartConfig.types)) { ctx.logger.trace('bodyparser parsing as multipart body'); ctx.request.multipart = new Multipart_1.Multipart(ctx, { maxFields: multipartConfig.maxFields, limit: multipartConfig.limit, fieldsLimit: multipartConfig.fieldsLimit, convertEmptyStringsToNull: multipartConfig.convertEmptyStringsToNull, }, this.drive); /** * Skip parsing when `autoProcess` is disabled or route matches one * of the defined processManually route patterns. */ if (!multipartConfig.autoProcess || multipartConfig.processManually.indexOf(ctx.route.pattern) > -1) { return next(); } /** * Make sure we are not running any validations on the uploaded files. They are * deferred for the end user when they will access file using `request.file` * method. */ ctx.request.multipart.onFile('*', { deferValidations: true }, async (part, reporter) => { /** * We need to abort the main request when we are unable to process any * file. Otherwise the error will endup on the file object, which * is incorrect. */ try { const tmpPath = this.getTmpPath(multipartConfig); await (0, streamFile_1.streamFile)(part, tmpPath, reporter); return { tmpPath }; } catch (error) { ctx.request.multipart.abort(error); } }); const action = ctx.profiler.profile('bodyparser:multipart'); try { await ctx.request.multipart.process(); action.end(); return next(); } catch (error) { action.end({ error }); throw error; } } /** * Handle url-encoded form data */ const formConfig = this.getConfigFor('form'); if (this.isType(ctx.request, formConfig.types)) { ctx.logger.trace('bodyparser parsing as form request'); const action = ctx.profiler.profile('bodyparser:urlencoded'); try { const { parsed, raw } = await co_body_1.default.form(ctx.request.request, formConfig); ctx.request.setInitialBody(parsed); ctx.request.updateRawBody(raw); action.end(); return next(); } catch (error) { action.end({ error }); throw this.getExceptionFor(error); } } /** * Handle content with JSON types */ const jsonConfig = this.getConfigFor('json'); if (this.isType(ctx.request, jsonConfig.types)) { ctx.logger.trace('bodyparser parsing as json body'); const action = ctx.profiler.profile('bodyparser:json'); try { const { parsed, raw } = await co_body_1.default.json(ctx.request.request, jsonConfig); ctx.request.setInitialBody(parsed); ctx.request.updateRawBody(raw); action.end(); return next(); } catch (error) { action.end({ error }); throw this.getExceptionFor(error); } } /** * Handles raw request body */ const rawConfig = this.getConfigFor('raw'); if (this.isType(ctx.request, rawConfig.types)) { ctx.logger.trace('bodyparser parsing as raw body'); const action = ctx.profiler.profile('bodyparser:raw'); try { const { raw } = await co_body_1.default.text(ctx.request.request, rawConfig); ctx.request.setInitialBody({}); ctx.request.updateRawBody(raw); action.end(); return next(); } catch (error) { action.end({ error }); throw this.getExceptionFor(error); } } await next(); } }; BodyParserMiddleware = __decorate([ (0, application_1.inject)(['Adonis/Core/Config', 'Adonis/Core/Drive']), __metadata("design:paramtypes", [Object, Object]) ], BodyParserMiddleware); exports.BodyParserMiddleware = BodyParserMiddleware;