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

Added support for custom urlencoded parsers #203

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
123 changes: 123 additions & 0 deletions README.md
Expand Up @@ -319,6 +319,129 @@ This error will occur when the request had a `Content-Encoding` header that
contained an unsupported encoding. The encoding is contained in the message
as well as in the `encoding` property. The `status` property is set to `415`.

### bodyParser.generic(options)

Returns middleware that only parses `generic` bodies. This parser accepts
any encoding of the body and supports automatic inflation of `gzip`
and `deflate` encodings.

A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This object will contain the result
from the parser you specify.

#### Options

The `generic` function takes an option `options` object that may contain
any of the following key:

##### parser (required)

This must be a function which takes the `body` optionally followed by `options`.

For example:

```js
function (body, options) {
return JSON.parse(body, options.reviver);
}
```

##### parserOptions

This should be an object containing any options you wish to pass through to the parser.

##### inflate

When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.

##### limit

Controls the maximum request body size. If this is a number, then the value
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'`.

##### parameterLimit

The `parameterLimit` option controls the maximum number of parameters that
are allowed in the URL-encoded data. If a request contains more parameters
than this value, a 413 will be returned to the client. Defaults to `1000`.

##### type

The `type` option is used to determine what media type the middleware will
parse. This option can be a function or a string. If a string, `type` option
is passed directly to the [type-is](https://www.npmjs.org/package/type-is#readme)
library and this can be an extension name (like `urlencoded`), a mime type (like
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
to `application/x-www-form-urlencoded`.

##### verify

The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.

## Errors

The middlewares provided by this module create errors depending on the error
condition during parsing. The errors will typically have a `status` property
that contains the suggested HTTP response code and a `body` property containing
the read body, if available.

The following are the common errors emitted, though any error can come through
for various reasons.

### content encoding unsupported

This error will occur when the request had a `Content-Encoding` header that
contained an encoding but the "inflation" option was set to `false`. The
`status` property is set to `415`.

### request aborted

This error will occur when the request is aborted by the client before reading
the body has finished. The `received` property will be set to the number of
bytes received before the request was aborted and the `expected` property is
set to the number of expected bytes. The `status` property is set to `400`.

### request entity too large

This error will occur when the request body's size is larger than the "limit"
option. The `limit` property will be set to the byte limit and the `length`
property will be set to the request body's length. The `status` property is
set to `413`.

### request size did not match content length

This error will occur when the request's length did not match the length from
the `Content-Length` header. This typically occurs when the request is malformed,
typically when the `Content-Length` header was calculated based on characters
instead of bytes. The `status` property is set to `400`.

### stream encoding should not be set

This error will occur when something called the `req.setEncoding` method prior
to this middleware. This module operates directly on bytes only and you cannot
call `req.setEncoding` when using this module. The `status` property is set to
`500`.

### unsupported charset "BOGUS"

This error will occur when the request had a charset parameter in the
`Content-Type` header, but the `iconv-lite` module does not support it OR the
parser does not support it. The charset is contained in the message as well
as in the `charset` property. The `status` property is set to `415`.

### unsupported content encoding "bogus"

This error will occur when the request had a `Content-Encoding` header that
contained an unsupported encoding. The encoding is contained in the message
as well as in the `encoding` property. The `status` property is set to `415`.

## Examples

### Express/Connect top-level generic
Expand Down
15 changes: 15 additions & 0 deletions index.js
Expand Up @@ -27,6 +27,7 @@ var parsers = Object.create(null)
* @property {function} raw
* @property {function} text
* @property {function} urlencoded
* @property {function} generic
*/

/**
Expand All @@ -48,6 +49,17 @@ Object.defineProperty(exports, 'json', {
get: createParserGetter('json')
})

/**
* Generic parser.
* @public
*/

Object.defineProperty(exports, 'generic', {
configurable: true,
enumerable: true,
get: createParserGetter('generic')
})

/**
* Raw parser.
* @public
Expand Down Expand Up @@ -150,6 +162,9 @@ function loadParser (parserName) {
case 'urlencoded':
parser = require('./lib/types/urlencoded')
break
case 'generic':
parser = require('./lib/types/generic')
break
}

// store to prevent invoking require()
Expand Down
120 changes: 120 additions & 0 deletions lib/types/generic.js
@@ -0,0 +1,120 @@
/*!
* 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 debug = require('debug')('body-parser:generic')
var read = require('../read')
var typeis = require('type-is')

/**
* Module exports.
*/

module.exports = generic

/**
* Create a middleware to parse generic bodies.
*
* @param {object} [options]
* @return {function}
* @public
*/

function generic (opts) {
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 type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type

function parse (body) {
return body.length
? opts.parser(body, opts.parserOptions || {})
: {}
}

return function genericParser (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
}

// read
read(req, res, next, parse, debug, {
debug: debug,
encoding: getCharset(req) || 'utf-8',
inflate: inflate,
limit: limit,
verify: verify
})
}
}

/**
* 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))
}
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -28,6 +28,7 @@
"istanbul": "0.4.4",
"methods": "1.1.2",
"mocha": "2.5.3",
"object-assign": "4.1.0",
"supertest": "1.1.0"
},
"files": [
Expand Down