/
bodyParser.js
207 lines (189 loc) · 7.56 KB
/
bodyParser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
'use strict';
var assert = require('assert-plus');
var errors = require('restify-errors');
var bodyReader = require('./bodyReader');
var jsonParser = require('./jsonBodyParser');
var formParser = require('./formBodyParser');
var multipartParser = require('./multipartBodyParser');
var fieldedTextParser = require('./fieldedTextBodyParser.js');
///--- Globals
var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError;
///--- API
/**
* Blocks your chain on reading and parsing the HTTP request body. Switches on
* `Content-Type` and does the appropriate logic. `application/json`,
* `application/x-www-form-urlencoded` and `multipart/form-data` are currently
* supported.
*
* Parses `POST` bodies to `req.body`. automatically uses one of the following
* parsers based on content type:
* - `urlEncodedBodyParser(options)` - parses url encoded form bodies
* - `jsonBodyParser(options)` - parses JSON POST bodies
* - `multipartBodyParser(options)` - parses multipart form bodies
*
* All bodyParsers support the following options:
* - `options.mapParams` - default false. copies parsed post body values onto
* req.params
* - `options.overrideParams` - default false. only applies when if
* mapParams true. when true, will stomp on req.params value when
* existing value is found.
*
* @public
* @function bodyParser
* @throws {UnsupportedMediaTypeError}
* @param {Object} [options] - an option object
* @param {Number} [options.maxBodySize] - The maximum size in bytes allowed in
* the HTTP body. Useful for limiting clients from hogging server memory.
* @param {Boolean} [options.mapParams] - if `req.params` should be filled with
* parsed parameters from HTTP body.
* @param {Boolean} [options.mapFiles] - if `req.params` should be filled with
* the contents of files sent through a multipart request.
* [formidable](https://github.com/felixge/node-formidable) is used internally
* for parsing, and a file is denoted as a multipart part with the `filename`
* option set in its `Content-Disposition`. This will only be performed if
* `mapParams` is true.
* @param {Boolean} [options.overrideParams] - if an entry in `req.params`
* should be overwritten by the value in the body if the names are the same.
* For instance, if you have the route `/:someval`,
* and someone posts an `x-www-form-urlencoded`
* Content-Type with the body `someval=happy` to `/sad`, the value will be
* `happy` if `overrideParams` is `true`, `sad` otherwise.
* @param {Function} [options.multipartHandler] - a callback to handle any
* multipart part which is not a file.
* If this is omitted, the default handler is invoked which may
* or may not map the parts into `req.params`, depending on
* the `mapParams`-option.
* @param {Function} [options.multipartFileHandler] - a callback to handle any
* multipart file.
* It will be a file if the part has a `Content-Disposition` with the
* `filename` parameter set. This typically happens when a browser sends a
* form and there is a parameter similar to `<input type="file" />`.
* If this is not provided, the default behaviour is to map the contents
* into `req.params`.
* @param {Boolean} [options.keepExtensions] - if you want the uploaded
* files to include the extensions of the original files
* (multipart uploads only).
* Does nothing if `multipartFileHandler` is defined.
* @param {String} [options.uploadDir] - Where uploaded files are
* intermediately stored during transfer before the contents is mapped
* into `req.params`.
* Does nothing if `multipartFileHandler` is defined.
* @param {Boolean} [options.multiples] - if you want to support html5 multiple
* attribute in upload fields.
* @param {String} [options.hash] - If you want checksums calculated for
* incoming files, set this to either `sha1` or `md5`.
* @param {Boolean} [options.rejectUnknown] - Set to `true` if you want to end
* the request with a `UnsupportedMediaTypeError` when none of
* the supported content types was given.
* @param {Boolean} [options.requestBodyOnGet=false] - Parse body of a GET
* request.
* @param {Function} [options.reviver] - `jsonParser` only. If a function,
* this prescribes how the value originally produced by parsing is transformed,
* before being returned. For more information check out
* `JSON.parse(text[, reviver])`.
* @param {Number} [options.maxFieldsSize=2 * 1024 * 1024] - `multipartParser`
* only.
* Limits the amount of memory all fields together (except files)
* can allocate in bytes.
* The default size is `2 * 1024 * 1024` bytes *(2MB)*.
* @returns {Function} Handler
* @example
* server.use(restify.plugins.bodyParser({
* maxBodySize: 0,
* mapParams: true,
* mapFiles: false,
* overrideParams: false,
* multipartHandler: function(part) {
* part.on('data', function(data) {
* // do something with the multipart data
* });
* },
* multipartFileHandler: function(part) {
* part.on('data', function(data) {
* // do something with the multipart file data
* });
* },
* keepExtensions: false,
* uploadDir: os.tmpdir(),
* multiples: true,
* hash: 'sha1',
* rejectUnknown: true,
* requestBodyOnGet: false,
* reviver: undefined,
* maxFieldsSize: 2 * 1024 * 1024
* }));
*/
function bodyParser(options) {
assert.optionalObject(options, 'options');
var opts = options || {};
opts.bodyReader = true;
var read = bodyReader(opts);
var parseForm = formParser(opts);
var parseJson = jsonParser(opts);
var parseMultipart = multipartParser(opts);
var parseFieldedText = fieldedTextParser(opts);
function parseBody(req, res, next) {
// #100 don't parse the body again if we've read it once
if (req._parsedBody) {
next();
return;
} else {
req._parsedBody = true;
}
// Allow use of 'requestBodyOnGet' flag to allow for merging of
// the request body of a GET request into req.params
if (req.method === 'HEAD') {
next();
return;
}
if (req.method === 'GET') {
if (!opts.requestBodyOnGet) {
next();
return;
}
}
if (req.contentLength() === 0 && !req.isChunked()) {
next();
return;
}
var parser;
var type = req.contentType().toLowerCase();
var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json');
// map any +json to application/json
if (jsonPatternMatcher.test(type)) {
type = 'application/json';
}
switch (type) {
case 'application/json':
parser = parseJson[0];
break;
case 'application/x-www-form-urlencoded':
parser = parseForm[0];
break;
case 'multipart/form-data':
parser = parseMultipart;
break;
case 'text/tsv':
parser = parseFieldedText;
break;
case 'text/tab-separated-values':
parser = parseFieldedText;
break;
case 'text/csv':
parser = parseFieldedText;
break;
default:
break;
}
if (parser) {
parser(req, res, next);
} else if (opts && opts.rejectUnknown) {
next(new UnsupportedMediaTypeError(type));
} else {
next();
}
}
return [read, parseBody];
}
module.exports = bodyParser;