urlencoded.js
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 createError = require('http-errors') 16 var debug = require('debug')('body-parser:urlencoded') 17 var isFinished = require('on-finished').isFinished 18 var read = require('../read') 19 var typeis = require('type-is') 20 var qs = require('qs') 21 var { getCharset, normalizeOptions } = require('../utils') 22 23 /** 24 * Module exports. 25 */ 26 27 module.exports = urlencoded 28 29 /** 30 * Create a middleware to parse urlencoded bodies. 31 * 32 * @param {object} [options] 33 * @return {function} 34 * @public 35 */ 36 37 function urlencoded (options) { 38 var { inflate, limit, verify, shouldParse } = normalizeOptions(options, 'application/x-www-form-urlencoded') 39 40 var defaultCharset = options?.defaultCharset || 'utf-8' 41 if (defaultCharset !== 'utf-8' && defaultCharset !== 'iso-8859-1') { 42 throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1') 43 } 44 45 // create the appropriate query parser 46 var queryparse = createQueryParser(options) 47 48 function parse (body, encoding) { 49 return body.length 50 ? queryparse(body, encoding) 51 : {} 52 } 53 54 return function urlencodedParser (req, res, next) { 55 if (isFinished(req)) { 56 debug('body already parsed') 57 next() 58 return 59 } 60 61 if (!('body' in req)) { 62 req.body = undefined 63 } 64 65 // skip requests without bodies 66 if (!typeis.hasBody(req)) { 67 debug('skip empty body') 68 next() 69 return 70 } 71 72 debug('content-type %j', req.headers['content-type']) 73 74 // determine if request should be parsed 75 if (!shouldParse(req)) { 76 debug('skip parsing') 77 next() 78 return 79 } 80 81 // assert charset 82 var charset = getCharset(req) || defaultCharset 83 if (charset !== 'utf-8' && charset !== 'iso-8859-1') { 84 debug('invalid charset') 85 next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', { 86 charset: charset, 87 type: 'charset.unsupported' 88 })) 89 return 90 } 91 92 // read 93 read(req, res, next, parse, debug, { 94 encoding: charset, 95 inflate, 96 limit, 97 verify 98 }) 99 } 100 } 101 102 /** 103 * Get the extended query parser. 104 * 105 * @param {object} options 106 */ 107 108 function createQueryParser (options) { 109 var extended = Boolean(options?.extended) 110 var parameterLimit = options?.parameterLimit !== undefined 111 ? options?.parameterLimit 112 : 1000 113 var charsetSentinel = options?.charsetSentinel 114 var interpretNumericEntities = options?.interpretNumericEntities 115 var depth = extended ? (options?.depth !== undefined ? options?.depth : 32) : 0 116 117 if (isNaN(parameterLimit) || parameterLimit < 1) { 118 throw new TypeError('option parameterLimit must be a positive number') 119 } 120 121 if (isNaN(depth) || depth < 0) { 122 throw new TypeError('option depth must be a zero or a positive number') 123 } 124 125 if (isFinite(parameterLimit)) { 126 parameterLimit = parameterLimit | 0 127 } 128 129 return function queryparse (body, encoding) { 130 var paramCount = parameterCount(body, parameterLimit) 131 132 if (paramCount === undefined) { 133 debug('too many parameters') 134 throw createError(413, 'too many parameters', { 135 type: 'parameters.too.many' 136 }) 137 } 138 139 var arrayLimit = extended ? Math.max(100, paramCount) : 0 140 141 debug('parse ' + (extended ? 'extended ' : '') + 'urlencoding') 142 try { 143 return qs.parse(body, { 144 allowPrototypes: true, 145 arrayLimit: arrayLimit, 146 depth: depth, 147 charsetSentinel: charsetSentinel, 148 interpretNumericEntities: interpretNumericEntities, 149 charset: encoding, 150 parameterLimit: parameterLimit, 151 strictDepth: true 152 }) 153 } catch (err) { 154 if (err instanceof RangeError) { 155 throw createError(400, 'The input exceeded the depth', { 156 type: 'querystring.parse.rangeError' 157 }) 158 } else { 159 throw err 160 } 161 } 162 } 163 } 164 165 /** 166 * Count the number of parameters, stopping once limit reached 167 * 168 * @param {string} body 169 * @param {number} limit 170 * @api private 171 */ 172 173 function parameterCount (body, limit) { 174 var len = body.split('&').length 175 176 return len > limit ? undefined : len - 1 177 }