second
[josuexyz/.git] / node_modules / body-parser / lib / types / json.js
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
15 var bytes = require('bytes')
16 var contentType = require('content-type')
17 var createError = require('http-errors')
18 var debug = require('debug')('body-parser:json')
19 var read = require('../read')
20 var typeis = require('type-is')
21
22 /**
23  * Module exports.
24  */
25
26 module.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
40 var 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
50 function 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
153 function 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
175 function 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
186 function 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
202 function 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
226 function typeChecker (type) {
227   return function checkType (req) {
228     return Boolean(typeis(req, type))
229   }
230 }