UNPKG

3.91 kBJavaScriptView Raw
1/*!
2 * bodyparser.js - body parser for bweb
3 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
4 * https://github.com/bcoin-org/bweb
5 */
6
7'use strict';
8
9const assert = require('bsert');
10const {StringDecoder} = require('string_decoder');
11const {parseForm} = require('../util');
12
13/**
14 * Body parser middleware.
15 * @param {Object} options
16 * @returns {Function}
17 */
18
19function bodyParser(options) {
20 const opt = new BodyParserOptions(options);
21
22 return async (req, res) => {
23 if (req.hasBody)
24 return;
25
26 try {
27 req.resume();
28 req.body = await parseBody(req, opt);
29 } finally {
30 req.pause();
31 }
32
33 req.hasBody = true;
34 };
35}
36
37/**
38 * Parse request body.
39 * @private
40 * @param {ServerRequest} req
41 * @param {Object} options
42 * @returns {Promise}
43 */
44
45async function parseBody(req, options) {
46 if (req.method === 'GET')
47 return Object.create(null);
48
49 const type = options.type || req.type;
50
51 switch (type) {
52 case 'json': {
53 const data = await readBody(req, options);
54
55 if (!data)
56 return Object.create(null);
57
58 const body = JSON.parse(data);
59
60 if (!body || typeof body !== 'object')
61 throw new Error('JSON body must be an object.');
62
63 return body;
64 }
65 case 'form': {
66 const data = await readBody(req, options);
67
68 if (!data)
69 return Object.create(null);
70
71 return parseForm(data, options.keyLimit);
72 }
73 default: {
74 return Object.create(null);
75 }
76 }
77}
78
79/**
80 * Read and buffer request body.
81 * @param {ServerRequest} req
82 * @param {Object} options
83 * @returns {Promise}
84 */
85
86function readBody(req, options) {
87 return new Promise((resolve, reject) => {
88 return bufferBody(req, options, resolve, reject);
89 });
90}
91
92/**
93 * Read and buffer request body.
94 * @private
95 * @param {ServerRequest} req
96 * @param {Object} options
97 * @param {Function} resolve
98 * @param {Function} reject
99 */
100
101function bufferBody(req, options, resolve, reject) {
102 const decode = new StringDecoder('utf8');
103
104 let hasData = false;
105 let total = 0;
106 let body = '';
107 let timer = null;
108
109 const cleanup = () => {
110 /* eslint-disable */
111 req.removeListener('data', onData);
112 req.removeListener('error', onError);
113 req.removeListener('end', onEnd);
114
115 if (timer != null) {
116 clearTimeout(timer);
117 timer = null;
118 }
119 /* eslint-enable */
120 };
121
122 const onData = (data) => {
123 total += data.length;
124 hasData = true;
125
126 if (total > options.bodyLimit) {
127 reject(new Error('Request body overflow.'));
128 return;
129 }
130
131 body += decode.write(data);
132 };
133
134 const onError = (err) => {
135 cleanup();
136 reject(err);
137 };
138
139 const onEnd = () => {
140 cleanup();
141
142 if (hasData) {
143 resolve(body);
144 return;
145 }
146
147 resolve(null);
148 };
149
150 timer = setTimeout(() => {
151 cleanup();
152 reject(new Error('Request body timed out.'));
153 }, options.timeout);
154
155 req.on('data', onData);
156 req.on('error', onError);
157 req.on('end', onEnd);
158}
159
160class BodyParserOptions {
161 /**
162 * Body Parser Options
163 * @constructor
164 * @param {Object} options
165 */
166
167 constructor(options) {
168 this.keyLimit = 100;
169 this.bodyLimit = 20 << 20;
170 this.type = null;
171 this.timeout = 10 * 1000;
172
173 if (options)
174 this.fromOptions(options);
175 }
176
177 /**
178 * Inject properties from object.
179 * @private
180 * @param {Object} options
181 * @returns {BodyParserOptions}
182 */
183
184 fromOptions(options) {
185 assert(options);
186
187 if (options.keyLimit != null) {
188 assert(typeof options.keyLimit === 'number');
189 this.keyLimit = options.keyLimit;
190 }
191
192 if (options.bodyLimit != null) {
193 assert(typeof options.bodyLimit === 'number');
194 this.bodyLimit = options.bodyLimit;
195 }
196
197 if (options.type != null) {
198 assert(typeof options.type === 'string');
199 this.type = options.type;
200 }
201
202 return this;
203 }
204}
205
206/*
207 * Expose
208 */
209
210module.exports = bodyParser;