Skip to content

Commit

Permalink
Implement a parsedMethods option (#135)
Browse files Browse the repository at this point in the history
Fixes #133
  • Loading branch information
damianb authored and MarkHerhold committed Mar 5, 2019
1 parent d1bf146 commit 53801a2
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 17 deletions.
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -18,7 +18,7 @@ $ npm install koa-body@3

## Breaking Changes in v3/4
To address a potential security issue:
- The `files` property has been moved to `ctx.request.files`. In prior versions, `files` was a property of `ctx.request.body`.
- The `files` property has been moved to `ctx.request.files`. In prior versions, `files` was a property of `ctx.request.body`.
- The `fields` property is flatten (merged) into `ctx.request.body`. In prior versions, `fields` was a property of `ctx.request.body`.

If you do not use multipart uploads, no changes to your code need to be made.
Expand Down Expand Up @@ -110,7 +110,8 @@ console.log('curl -i http://localhost:3000/users -d "name=test"');
- `includeUnparsed` **{Boolean}** Toggles co-body returnRawBody option; if set to true, for form encodedand and JSON requests the raw, unparsed requesty body will be attached to `ctx.reqeust.body` using a `Symbol`, default `false`
- `formidable` **{Object}** Options to pass to the formidable multipart parser
- `onError` **{Function}** Custom error handle, if throw an error, you can customize the response - onError(error, context), default will throw
- `strict` **{Boolean}** If enabled, don't parse GET, HEAD, DELETE requests, default `true`
- `strict` **{Boolean}** ***DEPRECATED*** If enabled, don't parse GET, HEAD, DELETE requests, default `true`
- `parsedMethods` **{String[]}** Declares the HTTP methods where bodies will be parsed, default `['POST', 'PUT', 'PATCH']`. Replaces `strict` option.

## A note about strict mode
> see [http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.3](http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.3)
Expand Down
25 changes: 18 additions & 7 deletions index.d.ts
Expand Up @@ -112,17 +112,17 @@ declare namespace koaBody {
* Toggles co-body strict mode; if true, only parses arrays or objects, default true
*/
jsonStrict?: boolean;

/**
* Toggles co-body returnRawBody mode; if true,
* Toggles co-body returnRawBody mode; if true,
* the raw body will be available using a Symbol for 'unparsedBody'.
*
*
* ```
// Either:
// Either:
const unparsed = require('koa-body/unparsed.js');
const unparsed = Symbol.for('unparsedBody');
// Then later, to access:
// Then later, to access:
ctx.request.body[unparsed]
```
* default false
Expand All @@ -140,7 +140,7 @@ declare namespace koaBody {
onError?: (err: Error, ctx: Koa.Context) => void;

/**
* {Boolean} If enabled, don't parse GET, HEAD, DELETE requests, default true
* {Boolean} If enabled, don't parse GET, HEAD, DELETE requests; deprecated.
*
* GET, HEAD, and DELETE requests have no defined semantics for the request body,
* but this doesn't mean they may not be valid in certain use cases.
Expand All @@ -149,6 +149,17 @@ declare namespace koaBody {
* see http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.3
*/
strict?: boolean;

/**
* {String[]} What HTTP methods to enable body parsing for; should be used in preference to strict mode.
*
* GET, HEAD, and DELETE requests have no defined semantics for the request body,
* but this doesn't mean they may not be valid in certain use cases.
* koa-body will only parse HTTP request bodies for POST, PUT, and PATCH by default
*
* see http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.3
*/
parsedMethods?: string[];
}
}

Expand Down
24 changes: 20 additions & 4 deletions index.js
Expand Up @@ -47,12 +47,28 @@ function requestbody(opts) {
opts.formidable = 'formidable' in opts ? opts.formidable : {};
opts.includeUnparsed = 'includeUnparsed' in opts ? opts.includeUnparsed : false
opts.textLimit = 'textLimit' in opts ? opts.textLimit : '56kb';
opts.strict = 'strict' in opts ? opts.strict : true;

// @todo: next major version, opts.strict support should be removed
if (opts.strict && opts.parsedMethods) {
throw new Error('Cannot use strict and parsedMethods options at the same time.')
}

if ('strict' in opts) {
console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.')
if (opts.strict) {
opts.parsedMethods = ['POST', 'PUT', 'PATCH']
} else {
opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']
}
}

opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH']
opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() })

return function (ctx, next) {
var bodyPromise;
// so don't parse the body in strict mode
if (!opts.strict || ["GET", "HEAD", "DELETE"].indexOf(ctx.method.toUpperCase()) === -1) {
// only parse the body on specifically chosen methods
if (opts.parsedMethods.includes(ctx.method.toUpperCase())) {
try {
if (opts.json && ctx.is('json')) {
bodyPromise = buddy.json(ctx, {
Expand Down Expand Up @@ -103,7 +119,7 @@ function requestbody(opts) {
} else if (opts.includeUnparsed) {
ctx.req.body = body.parsed || {};
if (! ctx.is('text')) {
ctx.req.body[symbolUnparsed] = body.raw;
ctx.req.body[symbolUnparsed] = body.raw;
}
} else {
ctx.req.body = body;
Expand Down
63 changes: 59 additions & 4 deletions test/index.js
Expand Up @@ -325,15 +325,15 @@ describe('koa-body', () => {
.expect(201)
.end( (err, res) => {
if (err) return done(err);

const mostRecentUser = database.users[database.users.length - 1];

assert(requestSpy.calledOnce, 'Spy for /users not called');
const req = requestSpy.firstCall.args[0].request;
req.body[unparsed].should.not.be.Undefined();
req.body[unparsed].should.be.a.String();
req.body[unparsed].should.equal('name=Test&followers=97');

res.body.user.should.have.properties({ name: 'Test', followers: '97' });
res.body.user.should.have.properties(mostRecentUser);
done();
Expand Down Expand Up @@ -370,7 +370,7 @@ describe('koa-body', () => {
done();
});
});

it('should receive text as strings with `includeUnparsed` option', (done) => {

const echoRouterLayer = router.stack.filter(layer => layer.path === "/echo_body");
Expand All @@ -390,7 +390,7 @@ describe('koa-body', () => {

// Raw text requests are still just text
assert.equal(req.body[unparsed], undefined);

// Text response is just text
should(res.body).have.properties({});
should(res.text).equal('plain text content');
Expand Down Expand Up @@ -460,8 +460,63 @@ describe('koa-body', () => {
done();
});
});
});

describe('parsedMethods options', () => {
beforeEach( () => {
//push an additional, to test the multi query
database.users.push({ name: 'charlike' });
});

it('methods declared are parsed', (done) => {
app.use(koaBody({ parsedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'] }));
app.use(router.routes());

request(http.createServer(app.callback()))
.delete('/users/charlike')
.type('application/x-www-form-urlencoded')
.send({ multi: true })
.expect(204)
.end( (err, res) => {
if (err) return done(err);
assert(database.users.find(element => element.name === 'charlike') === undefined);
done();
});
});

it('methods do not get parsed if not declared', (done) => {
app.use(koaBody({ parsedMethods: ['POST', 'PUT', 'PATCH'] }));
app.use(router.routes());

request(http.createServer(app.callback()))
.delete('/users/charlike')
.type('application/x-www-form-urlencoded')
.send({ multi: true })
.expect(204)
.end( (err, res) => {
if (err) return done(err);
assert(database.users.find(element => element.name === 'charlike') !== undefined);
done();
});
});

it('cannot use strict mode and parsedMethods options at the same time', (done) => {
let err;
try {
app.use(koaBody({
parsedMethods: ['POST', 'PUT', 'PATCH'],
strict: true
}));
} catch (_err) {
err = _err;
}

assert(err && err.message === 'Cannot use strict and parsedMethods options at the same time.');

done();
});
});

/**
* JSON request body
*/
Expand Down

0 comments on commit 53801a2

Please sign in to comment.