UNPKG

4.92 kBJavaScriptView Raw
1/*!
2 * body-parser
3 * Copyright(c) 2014 Jonathan Ong
4 * Copyright(c) 2014-2015 Douglas Christopher Wilson
5 * MIT Licensed
6 */
7
8'use strict'
9
10/**
11 * Module dependencies.
12 * @private
13 */
14
15var bytes = require('bytes')
16var contentType = require('content-type')
17var createError = require('http-errors')
18var debug = require('debug')('body-parser:json')
19var read = require('../read')
20var typeis = require('type-is')
21
22/**
23 * Module exports.
24 */
25
26module.exports = json
27
28/**
29 * RegExp to match the first non-space in a string.
30 *
31 * Allowed whitespace is defined in RFC 7159:
32 *
33 * ws = *(
34 * %x20 / ; Space
35 * %x09 / ; Horizontal tab
36 * %x0A / ; Line feed or New line
37 * %x0D ) ; Carriage return
38 */
39
40var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*(.)/ // eslint-disable-line no-control-regex
41
42/**
43 * Create a middleware to parse JSON bodies.
44 *
45 * @param {object} [options]
46 * @return {function}
47 * @public
48 */
49
50function json (options) {
51 var opts = options || {}
52
53 var limit = typeof opts.limit !== 'number'
54 ? bytes.parse(opts.limit || '100kb')
55 : opts.limit
56 var inflate = opts.inflate !== false
57 var reviver = opts.reviver
58 var strict = opts.strict !== false
59 var type = opts.type || 'application/json'
60 var verify = opts.verify || false
61
62 if (verify !== false && typeof verify !== 'function') {
63 throw new TypeError('option verify must be function')
64 }
65
66 // create the appropriate type checking function
67 var shouldParse = typeof type !== 'function'
68 ? typeChecker(type)
69 : type
70
71 function parse (body) {
72 if (body.length === 0) {
73 // special-case empty json body, as it's a common client-side mistake
74 // TODO: maybe make this configurable or part of "strict" option
75 return {}
76 }
77
78 if (strict) {
79 var first = firstchar(body)
80
81 if (first !== '{' && first !== '[') {
82 debug('strict violation')
83 throw createStrictSyntaxError(body, first)
84 }
85 }
86
87 try {
88 debug('parse json')
89 return JSON.parse(body, reviver)
90 } catch (e) {
91 throw normalizeJsonSyntaxError(e, {
92 message: e.message,
93 stack: e.stack
94 })
95 }
96 }
97
98 return function jsonParser (req, res, next) {
99 if (req._body) {
100 debug('body already parsed')
101 next()
102 return
103 }
104
105 req.body = req.body || {}
106
107 // skip requests without bodies
108 if (!typeis.hasBody(req)) {
109 debug('skip empty body')
110 next()
111 return
112 }
113
114 debug('content-type %j', req.headers['content-type'])
115
116 // determine if request should be parsed
117 if (!shouldParse(req)) {
118 debug('skip parsing')
119 next()
120 return
121 }
122
123 // assert charset per RFC 7159 sec 8.1
124 var charset = getCharset(req) || 'utf-8'
125 if (charset.substr(0, 4) !== 'utf-') {
126 debug('invalid charset')
127 next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
128 charset: charset,
129 type: 'charset.unsupported'
130 }))
131 return
132 }
133
134 // read
135 read(req, res, next, parse, debug, {
136 encoding: charset,
137 inflate: inflate,
138 limit: limit,
139 verify: verify
140 })
141 }
142}
143
144/**
145 * Create strict violation syntax error matching native error.
146 *
147 * @param {string} str
148 * @param {string} char
149 * @return {Error}
150 * @private
151 */
152
153function createStrictSyntaxError (str, char) {
154 var index = str.indexOf(char)
155 var partial = str.substring(0, index) + '#'
156
157 try {
158 JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
159 } catch (e) {
160 return normalizeJsonSyntaxError(e, {
161 message: e.message.replace('#', char),
162 stack: e.stack
163 })
164 }
165}
166
167/**
168 * Get the first non-whitespace character in a string.
169 *
170 * @param {string} str
171 * @return {function}
172 * @private
173 */
174
175function firstchar (str) {
176 return FIRST_CHAR_REGEXP.exec(str)[1]
177}
178
179/**
180 * Get the charset of a request.
181 *
182 * @param {object} req
183 * @api private
184 */
185
186function getCharset (req) {
187 try {
188 return (contentType.parse(req).parameters.charset || '').toLowerCase()
189 } catch (e) {
190 return undefined
191 }
192}
193
194/**
195 * Normalize a SyntaxError for JSON.parse.
196 *
197 * @param {SyntaxError} error
198 * @param {object} obj
199 * @return {SyntaxError}
200 */
201
202function normalizeJsonSyntaxError (error, obj) {
203 var keys = Object.getOwnPropertyNames(error)
204
205 for (var i = 0; i < keys.length; i++) {
206 var key = keys[i]
207 if (key !== 'stack' && key !== 'message') {
208 delete error[key]
209 }
210 }
211
212 // replace stack before message for Node.js 0.10 and below
213 error.stack = obj.stack.replace(error.message, obj.message)
214 error.message = obj.message
215
216 return error
217}
218
219/**
220 * Get the simple type checker.
221 *
222 * @param {string} type
223 * @return {function}
224 */
225
226function typeChecker (type) {
227 return function checkType (req) {
228 return Boolean(typeis(req, type))
229 }
230}