1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | const assert = require('bsert');
|
10 | const {StringDecoder} = require('string_decoder');
|
11 | const {parseForm} = require('../util');
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | function 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 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | async 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 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | function readBody(req, options) {
|
87 | return new Promise((resolve, reject) => {
|
88 | return bufferBody(req, options, resolve, reject);
|
89 | });
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | function 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 |
|
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 |
|
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 |
|
160 | class BodyParserOptions {
|
161 | |
162 |
|
163 |
|
164 |
|
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 |
|
179 |
|
180 |
|
181 |
|
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 |
|
208 |
|
209 |
|
210 | module.exports = bodyParser;
|