/*! * body-parser * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ var bytes = require('bytes') var contentType = require('content-type') var createError = require('http-errors') var debug = require('debug')('body-parser:urlencoded') var deprecate = require('depd')('body-parser') var read = require('../read') var typeis = require('type-is') /** * Module exports. */ module.exports = urlencoded /** * Cache of parser modules. */ var parsers = Object.create(null) /** * Create a middleware to parse urlencoded bodies. * * @param {object} [options] * @return {function} * @public */ function urlencoded (options) { var opts = options || {} // notice because option default will flip in next major if (opts.extended === undefined) { deprecate('undefined extended: provide extended option') } var extended = opts.extended !== false var inflate = opts.inflate !== false var limit = typeof opts.limit !== 'number' ? bytes.parse(opts.limit || '100kb') : opts.limit var type = opts.type || 'application/x-www-form-urlencoded' var verify = opts.verify || false if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } // create the appropriate query parser var queryparse = extended ? extendedparser(opts) : simpleparser(opts) // create the appropriate type checking function var shouldParse = typeof type !== 'function' ? typeChecker(type) : type function parse (body) { return body.length ? queryparse(body) : {} } return function urlencodedParser (req, res, next) { if (req._body) { debug('body already parsed') next() return } req.body = req.body || {} // skip requests without bodies if (!typeis.hasBody(req)) { debug('skip empty body') next() return } debug('content-type %j', req.headers['content-type']) // determine if request should be parsed if (!shouldParse(req)) { debug('skip parsing') next() return } // assert charset var charset = getCharset(req) || 'utf-8' if (charset !== 'utf-8') { debug('invalid charset') next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', { charset: charset, type: 'charset.unsupported' })) return } // read read(req, res, next, parse, debug, { debug: debug, encoding: charset, inflate: inflate, limit: limit, verify: verify }) } } /** * Get the extended query parser. * * @param {object} options */ function extendedparser (options) { var parameterLimit = options.parameterLimit !== undefined ? options.parameterLimit : 1000 var parse = parser('qs') if (isNaN(parameterLimit) || parameterLimit < 1) { throw new TypeError('option parameterLimit must be a positive number') } if (isFinite(parameterLimit)) { parameterLimit = parameterLimit | 0 } return function queryparse (body) { var paramCount = parameterCount(body, parameterLimit) if (paramCount === undefined) { debug('too many parameters') throw createError(413, 'too many parameters', { type: 'parameters.too.many' }) } var arrayLimit = Math.max(100, paramCount) debug('parse extended urlencoding') return parse(body, { allowPrototypes: true, arrayLimit: arrayLimit, depth: Infinity, parameterLimit: parameterLimit }) } } /** * Get the charset of a request. * * @param {object} req * @api private */ function getCharset (req) { try { return (contentType.parse(req).parameters.charset || '').toLowerCase() } catch (e) { return undefined } } /** * Count the number of parameters, stopping once limit reached * * @param {string} body * @param {number} limit * @api private */ function parameterCount (body, limit) { var count = 0 var index = 0 while ((index = body.indexOf('&', index)) !== -1) { count++ index++ if (count === limit) { return undefined } } return count } /** * Get parser for module name dynamically. * * @param {string} name * @return {function} * @api private */ function parser (name) { var mod = parsers[name] if (mod !== undefined) { return mod.parse } // this uses a switch for static require analysis switch (name) { case 'qs': mod = require('qs') break case 'querystring': mod = require('querystring') break } // store to prevent invoking require() parsers[name] = mod return mod.parse } /** * Get the simple query parser. * * @param {object} options */ function simpleparser (options) { var parameterLimit = options.parameterLimit !== undefined ? options.parameterLimit : 1000 var parse = parser('querystring') if (isNaN(parameterLimit) || parameterLimit < 1) { throw new TypeError('option parameterLimit must be a positive number') } if (isFinite(parameterLimit)) { parameterLimit = parameterLimit | 0 } return function queryparse (body) { var paramCount = parameterCount(body, parameterLimit) if (paramCount === undefined) { debug('too many parameters') throw createError(413, 'too many parameters', { type: 'parameters.too.many' }) } debug('parse urlencoding') return parse(body, undefined, undefined, {maxKeys: parameterLimit}) } } /** * Get the simple type checker. * * @param {string} type * @return {function} */ function typeChecker (type) { return function checkType (req) { return Boolean(typeis(req, type)) } }