Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic Body Parser #561

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c50fad0
Added support for external parsers to bodyParser.json()
Nov 21, 2017
8d95a02
removed test dependency on json-bigint
Nov 21, 2017
c3c2693
reworked doc to describe json parser() func better
Nov 21, 2017
6c7ab93
added parser() option and doc for .text()
Nov 21, 2017
6521f71
added parser() option and doc for .raw()
Nov 21, 2017
4cc0f6e
added parser() option and doc for .urlencoded()
Nov 21, 2017
7c540ee
cleanup to satisfy linter
Nov 21, 2017
ff854fb
added generic parser
Nov 21, 2017
6af5a94
converted json parser to use generic parser
Nov 21, 2017
85301d6
converted raw parser to use generic parser
Nov 21, 2017
c903e13
converted text parser to use generic parser
Nov 21, 2017
ee31462
converted urlencoded parser to use generic parser
Nov 21, 2017
d1a06d7
cleanup / fix linter warnings
Nov 21, 2017
287814b
removed items from README
Nov 21, 2017
4a7f2d5
added bodyParser.generic() getter
Nov 21, 2017
648db04
cleanup / fix linter warnings
Nov 21, 2017
3861bb8
fixed tests after rebase
sdellysse Apr 17, 2020
273b2e4
satisfying linter
sdellysse Apr 17, 2020
4bdaf7c
Ref'd genParser via the bodyparser getter to signal how third party p…
sdellysse Apr 17, 2020
295a716
removed dep on object-assign, which didnt support node < 0.10
sdellysse Apr 17, 2020
91d49cc
minor text cleanup
sdellysse Apr 17, 2020
1d3e6be
🔧 add debug script
ctcpip May 24, 2024
6b32200
🐛 fix object merging
ctcpip May 24, 2024
7814638
🔥 clean up
ctcpip May 24, 2024
07ab76b
Fix rebase
Phillip9587 Nov 19, 2024
ff07ffa
Remove added npm script
Phillip9587 Nov 19, 2024
a7e4fb0
Fix rebase
Phillip9587 Nov 19, 2024
74cb460
Fix charset validation for urlencoded
Phillip9587 Nov 19, 2024
f61122e
Refactor
Phillip9587 Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,27 @@ specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.

##### parser

The `parser` option is the function called against the request body to convert
it to a Javascript object. If a `reviver` is supplied, it is supplied as the
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved
second argument to this function.

```
parser(body, reviver) -> req.body
```

Defaults to `JSON.parse`.

##### reviver

The `reviver` option is passed directly to `JSON.parse` as the second
argument. You can find more information on this argument
You can find more information on this argument
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).

##### strict

When set to `true`, will only accept arrays and objects; when `false` will
accept anything `JSON.parse` accepts. Defaults to `true`.
accept anything the `parser` accepts. Defaults to `true`.

##### type

Expand Down Expand Up @@ -295,6 +306,16 @@ form. Defaults to `false`.

The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.

##### parser

The `parser` option, if supplied, is used to in place of the default parser to
convert the request body into a Javascript object. If this option is supplied,
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved
both the `extended` and `parameterLimit` options are ignored.

```
parser(body) -> req.body
```

## Errors

The middlewares provided by this module create errors using the
Expand Down
14 changes: 14 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ Object.defineProperty(exports, 'urlencoded', {
get: createParserGetter('urlencoded')
})

/**
* Generic parser used to build parsers.
* @public
*/

Object.defineProperty(exports, 'generic', {
configurable: true,
enumerable: true,
get: createParserGetter('generic')
})
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Create a middleware to parse json and urlencoded bodies.
*
Expand Down Expand Up @@ -123,6 +134,9 @@ function loadParser (parserName) {
case 'urlencoded':
parser = require('./lib/types/urlencoded')
break
case 'generic':
parser = require('./lib/generic-parser')
break
}

// store to prevent invoking require()
Expand Down
160 changes: 160 additions & 0 deletions lib/generic-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*!
* 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:generic')
var isFinished = require('on-finished').isFinished
var read = require('./read')
var typeis = require('type-is')

/**
* Module exports.
*/

module.exports = generic

/**
* Use this to create a middleware that parses request bodies
*
* @param {object} [options]
* @return {function}
* @public
*/

function generic (parserOptions, parserOverrides) {
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved
// Squash the options and the overrides down into one object
var opts = Object.create(parserOptions)
Object.assign(opts, parserOverrides)
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved

var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var charset = opts.charset
var inflate = opts.inflate !== false
var verify = opts.verify || false
var parse = opts.parse || defaultParse
Phillip9587 marked this conversation as resolved.
Show resolved Hide resolved
var defaultReqCharset = opts.defaultCharset || 'utf-8'
var type = opts.type

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

// create the appropriate charset validating function
var validCharset = typeof charset !== 'function'
? charsetValidator(charset)
: charset

return function genericParser (req, res, next) {
if (isFinished(req)) {
debug('body already parsed')
next()
return
}

if (!('body' in req)) {
req.body = undefined
}

// 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 per RFC 7159 sec 8.1
var reqCharset = null
if (charset !== undefined) {
reqCharset = getCharset(req) || defaultReqCharset
if (!validCharset(reqCharset)) {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + reqCharset.toUpperCase() + '"', {
charset: reqCharset,
type: 'charset.unsupported'
}))
return
}
}

// read
read(req, res, next, parse, debug, {
encoding: reqCharset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}

function defaultParse (buf) {
return buf
}

/**
* 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
}
}

/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/

function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}

/**
* Get the simple charset validator.
*
* @param {string} type
* @return {function}
*/

function charsetValidator (charset) {
return function validateCharset (reqCharset) {
return charset === reqCharset
}
}
Loading