json.js (4918B)
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 }