3 * Copyright(c) 2014 Jonathan Ong
4 * Copyright(c) 2014-2015 Douglas Christopher Wilson
11 * Module dependencies.
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')
29 * RegExp to match the first non-space in a string.
31 * Allowed whitespace is defined in RFC 7159:
35 * %x09 / ; Horizontal tab
36 * %x0A / ; Line feed or New line
37 * %x0D ) ; Carriage return
40 var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*(.)/ // eslint-disable-line no-control-regex
43 * Create a middleware to parse JSON bodies.
45 * @param {object} [options]
50 function json (options) {
51 var opts = options || {}
53 var limit = typeof opts.limit !== 'number'
54 ? bytes.parse(opts.limit || '100kb')
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
62 if (verify !== false && typeof verify !== 'function') {
63 throw new TypeError('option verify must be function')
66 // create the appropriate type checking function
67 var shouldParse = typeof type !== 'function'
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
79 var first = firstchar(body)
81 if (first !== '{' && first !== '[') {
82 debug('strict violation')
83 throw createStrictSyntaxError(body, first)
89 return JSON.parse(body, reviver)
91 throw normalizeJsonSyntaxError(e, {
98 return function jsonParser (req, res, next) {
100 debug('body already parsed')
105 req.body = req.body || {}
107 // skip requests without bodies
108 if (!typeis.hasBody(req)) {
109 debug('skip empty body')
114 debug('content-type %j', req.headers['content-type'])
116 // determine if request should be parsed
117 if (!shouldParse(req)) {
118 debug('skip parsing')
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() + '"', {
129 type: 'charset.unsupported'
135 read(req, res, next, parse, debug, {
145 * Create strict violation syntax error matching native error.
147 * @param {string} str
148 * @param {string} char
153 function createStrictSyntaxError (str, char) {
154 var index = str.indexOf(char)
155 var partial = str.substring(0, index) + '#'
158 JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
160 return normalizeJsonSyntaxError(e, {
161 message: e.message.replace('#', char),
168 * Get the first non-whitespace character in a string.
170 * @param {string} str
175 function firstchar (str) {
176 return FIRST_CHAR_REGEXP.exec(str)[1]
180 * Get the charset of a request.
182 * @param {object} req
186 function getCharset (req) {
188 return (contentType.parse(req).parameters.charset || '').toLowerCase()
195 * Normalize a SyntaxError for JSON.parse.
197 * @param {SyntaxError} error
198 * @param {object} obj
199 * @return {SyntaxError}
202 function normalizeJsonSyntaxError (error, obj) {
203 var keys = Object.getOwnPropertyNames(error)
205 for (var i = 0; i < keys.length; i++) {
207 if (key !== 'stack' && key !== 'message') {
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
220 * Get the simple type checker.
222 * @param {string} type
226 function typeChecker (type) {
227 return function checkType (req) {
228 return Boolean(typeis(req, type))