read.js (3894B)
1 /*! 2 * body-parser 3 * Copyright(c) 2014-2015 Douglas Christopher Wilson 4 * MIT Licensed 5 */ 6 7 'use strict' 8 9 /** 10 * Module dependencies. 11 * @private 12 */ 13 14 var createError = require('http-errors') 15 var getBody = require('raw-body') 16 var iconv = require('iconv-lite') 17 var onFinished = require('on-finished') 18 var zlib = require('zlib') 19 20 /** 21 * Module exports. 22 */ 23 24 module.exports = read 25 26 /** 27 * Read a request into a buffer and parse. 28 * 29 * @param {object} req 30 * @param {object} res 31 * @param {function} next 32 * @param {function} parse 33 * @param {function} debug 34 * @param {object} options 35 * @private 36 */ 37 38 function read (req, res, next, parse, debug, options) { 39 var length 40 var opts = options 41 var stream 42 43 // flag as parsed 44 req._body = true 45 46 // read options 47 var encoding = opts.encoding !== null 48 ? opts.encoding 49 : null 50 var verify = opts.verify 51 52 try { 53 // get the content stream 54 stream = contentstream(req, debug, opts.inflate) 55 length = stream.length 56 stream.length = undefined 57 } catch (err) { 58 return next(err) 59 } 60 61 // set raw-body options 62 opts.length = length 63 opts.encoding = verify 64 ? null 65 : encoding 66 67 // assert charset is supported 68 if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) { 69 return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', { 70 charset: encoding.toLowerCase(), 71 type: 'charset.unsupported' 72 })) 73 } 74 75 // read body 76 debug('read body') 77 getBody(stream, opts, function (error, body) { 78 if (error) { 79 var _error 80 81 if (error.type === 'encoding.unsupported') { 82 // echo back charset 83 _error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', { 84 charset: encoding.toLowerCase(), 85 type: 'charset.unsupported' 86 }) 87 } else { 88 // set status code on error 89 _error = createError(400, error) 90 } 91 92 // read off entire request 93 stream.resume() 94 onFinished(req, function onfinished () { 95 next(createError(400, _error)) 96 }) 97 return 98 } 99 100 // verify 101 if (verify) { 102 try { 103 debug('verify body') 104 verify(req, res, body, encoding) 105 } catch (err) { 106 next(createError(403, err, { 107 body: body, 108 type: err.type || 'entity.verify.failed' 109 })) 110 return 111 } 112 } 113 114 // parse 115 var str = body 116 try { 117 debug('parse body') 118 str = typeof body !== 'string' && encoding !== null 119 ? iconv.decode(body, encoding) 120 : body 121 req.body = parse(str) 122 } catch (err) { 123 next(createError(400, err, { 124 body: str, 125 type: err.type || 'entity.parse.failed' 126 })) 127 return 128 } 129 130 next() 131 }) 132 } 133 134 /** 135 * Get the content stream of the request. 136 * 137 * @param {object} req 138 * @param {function} debug 139 * @param {boolean} [inflate=true] 140 * @return {object} 141 * @api private 142 */ 143 144 function contentstream (req, debug, inflate) { 145 var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase() 146 var length = req.headers['content-length'] 147 var stream 148 149 debug('content-encoding "%s"', encoding) 150 151 if (inflate === false && encoding !== 'identity') { 152 throw createError(415, 'content encoding unsupported', { 153 encoding: encoding, 154 type: 'encoding.unsupported' 155 }) 156 } 157 158 switch (encoding) { 159 case 'deflate': 160 stream = zlib.createInflate() 161 debug('inflate body') 162 req.pipe(stream) 163 break 164 case 'gzip': 165 stream = zlib.createGunzip() 166 debug('gunzip body') 167 req.pipe(stream) 168 break 169 case 'identity': 170 stream = req 171 stream.length = length 172 break 173 default: 174 throw createError(415, 'unsupported content encoding "' + encoding + '"', { 175 encoding: encoding, 176 type: 'encoding.unsupported' 177 }) 178 } 179 180 return stream 181 }