urlencoded.js (5797B)
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:urlencoded') 19 var deprecate = require('depd')('body-parser') 20 var read = require('../read') 21 var typeis = require('type-is') 22 23 /** 24 * Module exports. 25 */ 26 27 module.exports = urlencoded 28 29 /** 30 * Cache of parser modules. 31 */ 32 33 var parsers = Object.create(null) 34 35 /** 36 * Create a middleware to parse urlencoded bodies. 37 * 38 * @param {object} [options] 39 * @return {function} 40 * @public 41 */ 42 43 function urlencoded (options) { 44 var opts = options || {} 45 46 // notice because option default will flip in next major 47 if (opts.extended === undefined) { 48 deprecate('undefined extended: provide extended option') 49 } 50 51 var extended = opts.extended !== false 52 var inflate = opts.inflate !== false 53 var limit = typeof opts.limit !== 'number' 54 ? bytes.parse(opts.limit || '100kb') 55 : opts.limit 56 var type = opts.type || 'application/x-www-form-urlencoded' 57 var verify = opts.verify || false 58 59 if (verify !== false && typeof verify !== 'function') { 60 throw new TypeError('option verify must be function') 61 } 62 63 // create the appropriate query parser 64 var queryparse = extended 65 ? extendedparser(opts) 66 : simpleparser(opts) 67 68 // create the appropriate type checking function 69 var shouldParse = typeof type !== 'function' 70 ? typeChecker(type) 71 : type 72 73 function parse (body) { 74 return body.length 75 ? queryparse(body) 76 : {} 77 } 78 79 return function urlencodedParser (req, res, next) { 80 if (req._body) { 81 debug('body already parsed') 82 next() 83 return 84 } 85 86 req.body = req.body || {} 87 88 // skip requests without bodies 89 if (!typeis.hasBody(req)) { 90 debug('skip empty body') 91 next() 92 return 93 } 94 95 debug('content-type %j', req.headers['content-type']) 96 97 // determine if request should be parsed 98 if (!shouldParse(req)) { 99 debug('skip parsing') 100 next() 101 return 102 } 103 104 // assert charset 105 var charset = getCharset(req) || 'utf-8' 106 if (charset !== 'utf-8') { 107 debug('invalid charset') 108 next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', { 109 charset: charset, 110 type: 'charset.unsupported' 111 })) 112 return 113 } 114 115 // read 116 read(req, res, next, parse, debug, { 117 debug: debug, 118 encoding: charset, 119 inflate: inflate, 120 limit: limit, 121 verify: verify 122 }) 123 } 124 } 125 126 /** 127 * Get the extended query parser. 128 * 129 * @param {object} options 130 */ 131 132 function extendedparser (options) { 133 var parameterLimit = options.parameterLimit !== undefined 134 ? options.parameterLimit 135 : 1000 136 var parse = parser('qs') 137 138 if (isNaN(parameterLimit) || parameterLimit < 1) { 139 throw new TypeError('option parameterLimit must be a positive number') 140 } 141 142 if (isFinite(parameterLimit)) { 143 parameterLimit = parameterLimit | 0 144 } 145 146 return function queryparse (body) { 147 var paramCount = parameterCount(body, parameterLimit) 148 149 if (paramCount === undefined) { 150 debug('too many parameters') 151 throw createError(413, 'too many parameters', { 152 type: 'parameters.too.many' 153 }) 154 } 155 156 var arrayLimit = Math.max(100, paramCount) 157 158 debug('parse extended urlencoding') 159 return parse(body, { 160 allowPrototypes: true, 161 arrayLimit: arrayLimit, 162 depth: Infinity, 163 parameterLimit: parameterLimit 164 }) 165 } 166 } 167 168 /** 169 * Get the charset of a request. 170 * 171 * @param {object} req 172 * @api private 173 */ 174 175 function getCharset (req) { 176 try { 177 return (contentType.parse(req).parameters.charset || '').toLowerCase() 178 } catch (e) { 179 return undefined 180 } 181 } 182 183 /** 184 * Count the number of parameters, stopping once limit reached 185 * 186 * @param {string} body 187 * @param {number} limit 188 * @api private 189 */ 190 191 function parameterCount (body, limit) { 192 var count = 0 193 var index = 0 194 195 while ((index = body.indexOf('&', index)) !== -1) { 196 count++ 197 index++ 198 199 if (count === limit) { 200 return undefined 201 } 202 } 203 204 return count 205 } 206 207 /** 208 * Get parser for module name dynamically. 209 * 210 * @param {string} name 211 * @return {function} 212 * @api private 213 */ 214 215 function parser (name) { 216 var mod = parsers[name] 217 218 if (mod !== undefined) { 219 return mod.parse 220 } 221 222 // this uses a switch for static require analysis 223 switch (name) { 224 case 'qs': 225 mod = require('qs') 226 break 227 case 'querystring': 228 mod = require('querystring') 229 break 230 } 231 232 // store to prevent invoking require() 233 parsers[name] = mod 234 235 return mod.parse 236 } 237 238 /** 239 * Get the simple query parser. 240 * 241 * @param {object} options 242 */ 243 244 function simpleparser (options) { 245 var parameterLimit = options.parameterLimit !== undefined 246 ? options.parameterLimit 247 : 1000 248 var parse = parser('querystring') 249 250 if (isNaN(parameterLimit) || parameterLimit < 1) { 251 throw new TypeError('option parameterLimit must be a positive number') 252 } 253 254 if (isFinite(parameterLimit)) { 255 parameterLimit = parameterLimit | 0 256 } 257 258 return function queryparse (body) { 259 var paramCount = parameterCount(body, parameterLimit) 260 261 if (paramCount === undefined) { 262 debug('too many parameters') 263 throw createError(413, 'too many parameters', { 264 type: 'parameters.too.many' 265 }) 266 } 267 268 debug('parse urlencoding') 269 return parse(body, undefined, undefined, { maxKeys: parameterLimit }) 270 } 271 } 272 273 /** 274 * Get the simple type checker. 275 * 276 * @param {string} type 277 * @return {function} 278 */ 279 280 function typeChecker (type) { 281 return function checkType (req) { 282 return Boolean(typeis(req, type)) 283 } 284 }