UNPKG

9.41 kBJavaScriptView Raw
1"use strict";
2/*
3 * @adonisjs/bodyparser
4 *
5 * (c) Harminder Virk <virk@adonisjs.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13 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;
14 return c > 3 && r && Object.defineProperty(target, key, r), r;
15};
16var __metadata = (this && this.__metadata) || function (k, v) {
17 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
18};
19var __importDefault = (this && this.__importDefault) || function (mod) {
20 return (mod && mod.__esModule) ? mod : { "default": mod };
21};
22Object.defineProperty(exports, "__esModule", { value: true });
23exports.BodyParserMiddleware = void 0;
24/// <reference path="../../adonis-typings/bodyparser.ts" />
25const os_1 = require("os");
26const co_body_1 = __importDefault(require("@poppinss/co-body"));
27const path_1 = require("path");
28const utils_1 = require("@poppinss/utils");
29const application_1 = require("@adonisjs/application");
30const helpers_1 = require("@poppinss/utils/build/helpers");
31const Multipart_1 = require("../Multipart");
32const streamFile_1 = require("../Multipart/streamFile");
33/**
34 * BodyParser middleware parses the incoming request body and set it as
35 * request body to be read later in the request lifecycle.
36 */
37let BodyParserMiddleware = class BodyParserMiddleware {
38 constructor(Config, drive) {
39 this.drive = drive;
40 this.config = Config.get('bodyparser', {});
41 }
42 /**
43 * Returns config for a given type
44 */
45 getConfigFor(type) {
46 const config = this.config[type];
47 config['returnRawBody'] = true;
48 return config;
49 }
50 /**
51 * Ensures that types exists and have length
52 */
53 ensureTypes(types) {
54 return !!(types && types.length);
55 }
56 /**
57 * Returns a boolean telling if request `content-type` header
58 * matches the expected types or not
59 */
60 isType(request, types) {
61 return !!(this.ensureTypes(types) && request.is(types));
62 }
63 /**
64 * Returns a proper Adonis style exception for popular error codes
65 * returned by https://github.com/stream-utils/raw-body#readme.
66 */
67 getExceptionFor(error) {
68 switch (error.type) {
69 case 'encoding.unsupported':
70 return new utils_1.Exception(error.message, error.status, 'E_ENCODING_UNSUPPORTED');
71 case 'entity.too.large':
72 return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ENTITY_TOO_LARGE');
73 case 'request.aborted':
74 return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ABORTED');
75 default:
76 return error;
77 }
78 }
79 /**
80 * Returns the tmp path for storing the files temporarly
81 */
82 getTmpPath(config) {
83 if (typeof config.tmpFileName === 'function') {
84 const tmpPath = config.tmpFileName();
85 return (0, path_1.isAbsolute)(tmpPath) ? tmpPath : (0, path_1.join)((0, os_1.tmpdir)(), tmpPath);
86 }
87 return (0, path_1.join)((0, os_1.tmpdir)(), (0, helpers_1.cuid)());
88 }
89 /**
90 * Handle HTTP request body by parsing it as per the user
91 * config
92 */
93 async handle(ctx, next) {
94 /**
95 * Initiating the `__raw_files` private property as an object
96 */
97 ctx.request['__raw_files'] = {};
98 const requestMethod = ctx.request.method();
99 /**
100 * Only process for whitelisted nodes
101 */
102 if (!this.config.whitelistedMethods.includes(requestMethod)) {
103 ctx.logger.trace(`bodyparser skipping method ${requestMethod}`);
104 return next();
105 }
106 /**
107 * Return early when request body is empty. Many clients set the `Content-length = 0`
108 * when request doesn't have any body, which is not handled by the below method.
109 *
110 * The main point of `hasBody` is to early return requests with empty body created by
111 * clients with missing headers.
112 */
113 if (!ctx.request.hasBody()) {
114 ctx.logger.trace('bodyparser skipping empty body');
115 return next();
116 }
117 /**
118 * Handle multipart form
119 */
120 const multipartConfig = this.getConfigFor('multipart');
121 if (this.isType(ctx.request, multipartConfig.types)) {
122 ctx.logger.trace('bodyparser parsing as multipart body');
123 ctx.request.multipart = new Multipart_1.Multipart(ctx, {
124 maxFields: multipartConfig.maxFields,
125 limit: multipartConfig.limit,
126 fieldsLimit: multipartConfig.fieldsLimit,
127 convertEmptyStringsToNull: multipartConfig.convertEmptyStringsToNull,
128 }, this.drive);
129 /**
130 * Skip parsing when `autoProcess` is disabled or route matches one
131 * of the defined processManually route patterns.
132 */
133 if (!multipartConfig.autoProcess ||
134 multipartConfig.processManually.indexOf(ctx.route.pattern) > -1) {
135 return next();
136 }
137 /**
138 * Make sure we are not running any validations on the uploaded files. They are
139 * deferred for the end user when they will access file using `request.file`
140 * method.
141 */
142 ctx.request.multipart.onFile('*', { deferValidations: true }, async (part, reporter) => {
143 /**
144 * We need to abort the main request when we are unable to process any
145 * file. Otherwise the error will endup on the file object, which
146 * is incorrect.
147 */
148 try {
149 const tmpPath = this.getTmpPath(multipartConfig);
150 await (0, streamFile_1.streamFile)(part, tmpPath, reporter);
151 return { tmpPath };
152 }
153 catch (error) {
154 ctx.request.multipart.abort(error);
155 }
156 });
157 const action = ctx.profiler.profile('bodyparser:multipart');
158 try {
159 await ctx.request.multipart.process();
160 action.end();
161 return next();
162 }
163 catch (error) {
164 action.end({ error });
165 throw error;
166 }
167 }
168 /**
169 * Handle url-encoded form data
170 */
171 const formConfig = this.getConfigFor('form');
172 if (this.isType(ctx.request, formConfig.types)) {
173 ctx.logger.trace('bodyparser parsing as form request');
174 const action = ctx.profiler.profile('bodyparser:urlencoded');
175 try {
176 const { parsed, raw } = await co_body_1.default.form(ctx.request.request, formConfig);
177 ctx.request.setInitialBody(parsed);
178 ctx.request.updateRawBody(raw);
179 action.end();
180 return next();
181 }
182 catch (error) {
183 action.end({ error });
184 throw this.getExceptionFor(error);
185 }
186 }
187 /**
188 * Handle content with JSON types
189 */
190 const jsonConfig = this.getConfigFor('json');
191 if (this.isType(ctx.request, jsonConfig.types)) {
192 ctx.logger.trace('bodyparser parsing as json body');
193 const action = ctx.profiler.profile('bodyparser:json');
194 try {
195 const { parsed, raw } = await co_body_1.default.json(ctx.request.request, jsonConfig);
196 ctx.request.setInitialBody(parsed);
197 ctx.request.updateRawBody(raw);
198 action.end();
199 return next();
200 }
201 catch (error) {
202 action.end({ error });
203 throw this.getExceptionFor(error);
204 }
205 }
206 /**
207 * Handles raw request body
208 */
209 const rawConfig = this.getConfigFor('raw');
210 if (this.isType(ctx.request, rawConfig.types)) {
211 ctx.logger.trace('bodyparser parsing as raw body');
212 const action = ctx.profiler.profile('bodyparser:raw');
213 try {
214 const { raw } = await co_body_1.default.text(ctx.request.request, rawConfig);
215 ctx.request.setInitialBody({});
216 ctx.request.updateRawBody(raw);
217 action.end();
218 return next();
219 }
220 catch (error) {
221 action.end({ error });
222 throw this.getExceptionFor(error);
223 }
224 }
225 await next();
226 }
227};
228BodyParserMiddleware = __decorate([
229 (0, application_1.inject)(['Adonis/Core/Config', 'Adonis/Core/Drive']),
230 __metadata("design:paramtypes", [Object, Object])
231], BodyParserMiddleware);
232exports.BodyParserMiddleware = BodyParserMiddleware;