twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

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 }