/ node_modules / body-parser / lib / types / urlencoded.js
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  }