UNPKG

9.35 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 convertEmptyStringsToNull: multipartConfig.convertEmptyStringsToNull,
127 }, this.drive);
128 /**
129 * Skip parsing when `autoProcess` is disabled or route matches one
130 * of the defined processManually route patterns.
131 */
132 if (!multipartConfig.autoProcess ||
133 multipartConfig.processManually.indexOf(ctx.route.pattern) > -1) {
134 return next();
135 }
136 /**
137 * Make sure we are not running any validations on the uploaded files. They are
138 * deferred for the end user when they will access file using `request.file`
139 * method.
140 */
141 ctx.request.multipart.onFile('*', { deferValidations: true }, async (part, reporter) => {
142 /**
143 * We need to abort the main request when we are unable to process any
144 * file. Otherwise the error will endup on the file object, which
145 * is incorrect.
146 */
147 try {
148 const tmpPath = this.getTmpPath(multipartConfig);
149 await (0, streamFile_1.streamFile)(part, tmpPath, reporter);
150 return { tmpPath };
151 }
152 catch (error) {
153 ctx.request.multipart.abort(error);
154 }
155 });
156 const action = ctx.profiler.profile('bodyparser:multipart');
157 try {
158 await ctx.request.multipart.process();
159 action.end();
160 return next();
161 }
162 catch (error) {
163 action.end({ error });
164 throw error;
165 }
166 }
167 /**
168 * Handle url-encoded form data
169 */
170 const formConfig = this.getConfigFor('form');
171 if (this.isType(ctx.request, formConfig.types)) {
172 ctx.logger.trace('bodyparser parsing as form request');
173 const action = ctx.profiler.profile('bodyparser:urlencoded');
174 try {
175 const { parsed, raw } = await co_body_1.default.form(ctx.request.request, formConfig);
176 ctx.request.setInitialBody(parsed);
177 ctx.request.updateRawBody(raw);
178 action.end();
179 return next();
180 }
181 catch (error) {
182 action.end({ error });
183 throw this.getExceptionFor(error);
184 }
185 }
186 /**
187 * Handle content with JSON types
188 */
189 const jsonConfig = this.getConfigFor('json');
190 if (this.isType(ctx.request, jsonConfig.types)) {
191 ctx.logger.trace('bodyparser parsing as json body');
192 const action = ctx.profiler.profile('bodyparser:json');
193 try {
194 const { parsed, raw } = await co_body_1.default.json(ctx.request.request, jsonConfig);
195 ctx.request.setInitialBody(parsed);
196 ctx.request.updateRawBody(raw);
197 action.end();
198 return next();
199 }
200 catch (error) {
201 action.end({ error });
202 throw this.getExceptionFor(error);
203 }
204 }
205 /**
206 * Handles raw request body
207 */
208 const rawConfig = this.getConfigFor('raw');
209 if (this.isType(ctx.request, rawConfig.types)) {
210 ctx.logger.trace('bodyparser parsing as raw body');
211 const action = ctx.profiler.profile('bodyparser:raw');
212 try {
213 const { raw } = await co_body_1.default.text(ctx.request.request, rawConfig);
214 ctx.request.setInitialBody({});
215 ctx.request.updateRawBody(raw);
216 action.end();
217 return next();
218 }
219 catch (error) {
220 action.end({ error });
221 throw this.getExceptionFor(error);
222 }
223 }
224 await next();
225 }
226};
227BodyParserMiddleware = __decorate([
228 (0, application_1.inject)(['Adonis/Core/Config', 'Adonis/Core/Drive']),
229 __metadata("design:paramtypes", [Object, Object])
230], BodyParserMiddleware);
231exports.BodyParserMiddleware = BodyParserMiddleware;