@adonisjs/bodyparser
Version:
AdonisJs body parser to read and parse HTTP request bodies
233 lines (232 loc) • 9.41 kB
JavaScript
"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;