Skip to content

Commit

Permalink
refactor: normalize common options for all parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
Phillip9587 committed Feb 18, 2025
1 parent 4a9ed7e commit d93b76d
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 73 deletions.
6 changes: 5 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
unreleased
=========================

* refactor: normalize common options for all parsers

2.1.0 / 2025-02-10
=========================

Expand All @@ -11,7 +16,6 @@
2.0.2 / 2024-10-31
=========================

* extract shared utility functions
* remove `unpipe` package and use native `unpipe()` method

2.0.1 / 2024-09-10
Expand Down
19 changes: 2 additions & 17 deletions lib/types/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
* @private
*/

var bytes = require('bytes')
var createError = require('http-errors')
var debug = require('debug')('body-parser:json')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { getCharset, typeChecker } = require('../utils')
var { getCharset, normalizeOptions } = require('../utils')

/**
* Module exports.
Expand Down Expand Up @@ -53,24 +52,10 @@ var JSON_SYNTAX_REGEXP = /#+/g

function json (options) {
var opts = options || {}
var { inflate, limit, verify, shouldParse } = normalizeOptions(opts, 'application/json')

var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var inflate = opts.inflate !== false
var reviver = opts.reviver
var strict = opts.strict !== false
var type = opts.type || 'application/json'
var verify = opts.verify || false

if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type

function parse (body) {
if (body.length === 0) {
Expand Down
20 changes: 2 additions & 18 deletions lib/types/raw.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
* Module dependencies.
*/

var bytes = require('bytes')
var debug = require('debug')('body-parser:raw')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { typeChecker } = require('../utils')
var { normalizeOptions } = require('../utils')

/**
* Module exports.
Expand All @@ -33,22 +32,7 @@ module.exports = raw

function raw (options) {
var opts = options || {}

var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'application/octet-stream'
var verify = opts.verify || false

if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
var { inflate, limit, verify, shouldParse } = normalizeOptions(opts, 'application/octet-stream')

function parse (buf) {
return buf
Expand Down
19 changes: 2 additions & 17 deletions lib/types/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
* Module dependencies.
*/

var bytes = require('bytes')
var debug = require('debug')('body-parser:text')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var { getCharset, typeChecker } = require('../utils')
var { getCharset, normalizeOptions } = require('../utils')

/**
* Module exports.
Expand All @@ -33,23 +32,9 @@ module.exports = text

function text (options) {
var opts = options || {}
var { inflate, limit, verify, shouldParse } = normalizeOptions(opts, 'text/plain')

var defaultCharset = opts.defaultCharset || 'utf-8'
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'text/plain'
var verify = opts.verify || false

if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type

function parse (buf) {
return buf
Expand Down
19 changes: 2 additions & 17 deletions lib/types/urlencoded.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
* @private
*/

var bytes = require('bytes')
var createError = require('http-errors')
var debug = require('debug')('body-parser:urlencoded')
var isFinished = require('on-finished').isFinished
var read = require('../read')
var typeis = require('type-is')
var qs = require('qs')
var { getCharset, typeChecker } = require('../utils')
var { getCharset, normalizeOptions } = require('../utils')

/**
* Module exports.
Expand All @@ -37,21 +36,12 @@ module.exports = urlencoded

function urlencoded (options) {
var opts = options || {}
var { inflate, limit, verify, shouldParse } = normalizeOptions(opts, 'application/x-www-form-urlencoded')

var extended = Boolean(opts.extended)
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
var charsetSentinel = opts.charsetSentinel
var interpretNumericEntities = opts.interpretNumericEntities

if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}

var defaultCharset = opts.defaultCharset || 'utf-8'
if (defaultCharset !== 'utf-8' && defaultCharset !== 'iso-8859-1') {
throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1')
Expand All @@ -60,11 +50,6 @@ function urlencoded (options) {
// create the appropriate query parser
var queryparse = createQueryParser(opts, extended)

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type

function parse (body, encoding) {
return body.length
? queryparse(body, encoding)
Expand Down
44 changes: 41 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* Copyright(c) Express Contributors
* MIT Licensed
*/

Expand All @@ -10,6 +10,7 @@
* Module dependencies.
*/

var bytes = require('bytes')
var contentType = require('content-type')
var typeis = require('type-is')

Expand All @@ -19,7 +20,7 @@ var typeis = require('type-is')

module.exports = {
getCharset,
typeChecker
normalizeOptions
}

/**
Expand All @@ -32,7 +33,7 @@ module.exports = {
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
} catch {
return undefined
}
}
Expand All @@ -49,3 +50,40 @@ function typeChecker (type) {
return Boolean(typeis(req, type))
}
}

/**
* Normalizes the common options for all parsers.
*
* @param {object} options options to normalize
* @param {string} defaultType default content type
* @returns {object}
*/
function normalizeOptions (options, defaultType) {
if (!defaultType || typeof defaultType !== 'string') {
// Parsers must define a default content type
throw new TypeError('defaultType must be provided')
}

var inflate = options?.inflate !== false
var limit = typeof options?.limit !== 'number'
? bytes.parse(options?.limit || '100kb')
: options?.limit
var type = options?.type || defaultType
var verify = options?.verify || false

if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type

return {
inflate,
limit,
verify,
shouldParse
}
}
123 changes: 123 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use strict'

const assert = require('node:assert')
const { normalizeOptions } = require('../lib/utils.js')

describe('normalizeOptions(options, defaultType)', () => {
it('should return default options when no options are provided', () => {
for (const options of [undefined, null, {}]) {
const result = normalizeOptions(options, 'application/json')
assert.strictEqual(result.inflate, true)
assert.strictEqual(result.limit, 100 * 1024) // 100kb in bytes
assert.strictEqual(result.verify, false)
assert.strictEqual(typeof result.shouldParse, 'function')
}
})

it('should override default options with provided options', () => {
const options = {
inflate: false,
limit: '200kb',
type: 'application/xml',
verify: () => {}
}
const result = normalizeOptions(options, 'application/json')
assert.strictEqual(result.inflate, false)
assert.strictEqual(result.limit, 200 * 1024) // 200kb in bytes
assert.strictEqual(result.verify, options.verify)
assert.strictEqual(typeof result.shouldParse, 'function')
})

it('should remove additional options', () => {
const options = {
inflate: false,
limit: '200kb',
type: 'application/xml',
verify: () => {},
additional: 'option',
something: 'weird'
}
const result = normalizeOptions(options, 'application/json')
assert.strictEqual(result.inflate, false)
assert.strictEqual(result.limit, 200 * 1024) // 200kb in bytes
assert.strictEqual(result.verify, options.verify)
assert.strictEqual(typeof result.shouldParse, 'function')
assert.strictEqual(result.additional, undefined)
assert.strictEqual(result.something, undefined)
})

describe('options', () => {
describe('verify', () => {
it('should throw an error if verify is not a function', () => {
assert.throws(() => {
normalizeOptions({ verify: 'not a function' }, 'application/json')
}, /option verify must be function/)
})

it('should accept a verify function', () => {
const verify = () => {}
const result = normalizeOptions({ verify }, 'application/json')
assert.strictEqual(result.verify, verify)
})
})

describe('limit', () => {
it('should return the default limit if limit is not provided', () => {
const result = normalizeOptions({}, 'application/json')
assert.strictEqual(result.limit, 100 * 1024) // 100kb in bytes
})

it('should accept a number limit', () => {
const result = normalizeOptions({ limit: 1234 }, 'application/json')
assert.strictEqual(result.limit, 1234)
})

it('should parse a string limit', () => {
const result = normalizeOptions({ limit: '200kb' }, 'application/json')
assert.strictEqual(result.limit, 200 * 1024) // 200kb in bytes
})

it('should return null for an invalid limit', () => {
const result = normalizeOptions({ limit: 'invalid' }, 'application/json')
assert.strictEqual(result.limit, null)
})
})

describe('type', () => {
it('should return the default type if type is not provided', () => {
const result = normalizeOptions({}, 'application/json')
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/json', 'content-length': '1024' } }), true)
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/xml', 'content-length': '1024' } }), false)
})

it('should accept a string type', () => {
const result = normalizeOptions({ type: 'application/xml' }, 'application/json')
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/xml', 'content-length': '1024' } }), true)
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/json', 'content-length': '1024' } }), false)
})

it('should accept a type checking function', () => {
const result = normalizeOptions({ type: () => true }, 'application/json')
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/xml' } }), true)
assert.strictEqual(result.shouldParse({ headers: { 'content-type': 'application/json' } }), true)
})
})
})

describe('defaultType', () => {
it('should throw an error if defaultType is not provided', () => {
assert.throws(() => {
normalizeOptions({})
}, /defaultType must be provided/)
assert.throws(() => {
normalizeOptions({}, undefined)
}, /defaultType must be provided/)
})

it('should throw an error if defaultType is not a string', () => {
assert.throws(() => {
normalizeOptions({}, 123)
}, /defaultType must be provided/)
})
})
})

0 comments on commit d93b76d

Please sign in to comment.