Skip to content

Commit

Permalink
Use ctx.request.files instead of ctx.request.body.files (#89)
Browse files Browse the repository at this point in the history
Use `ctx.request.files` instead of `ctx.request.body.files` - Fixes #75
  • Loading branch information
MarkHerhold committed Jun 3, 2018
1 parent 952deb6 commit d7dd15d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 81 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ koa-body [![Build Status](https://travis-ci.org/dlau/koa-body.svg?branch=koa2)](
$ npm install koa-body
```

## Legacy Koa1 support
## Legacy Koa v1 support
```
$ npm install koa-body@1
$ 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`. If you do not use multipart uploads, no changes to your code need to be made.

Versions 1 and 2 of `koa-body` are deprecated and replaced with versions 3 and 4, respectively.

## Features
- can handle three type requests
* **multipart/form-data**
Expand All @@ -29,7 +34,7 @@ $ npm install koa-body@1
```sh
npm install koa
npm install koa-body
nvm install v7.9.0 #Note - koa requires node v7.6.0 for ES2015/async support
nvm install v8.11.2 # Note - Koa requires node v7.6.0+ for async/await support
```
index.js:
```js
Expand All @@ -55,7 +60,7 @@ Content-Length: 29
Date: Wed, 03 May 2017 02:09:44 GMT
Connection: keep-alive

Request Body: {"name":"test"}%
Request Body: {"name":"test"}%
```

**For a more comprehensive example, see** `examples/multipart.js`
Expand Down
6 changes: 3 additions & 3 deletions examples/multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ router.get('/', (ctx) => {

router.post('/', koaBody,
(ctx) => {
console.log(ctx.request.body.fields);
console.log('fields: ', ctx.request.fields);
// => {username: ""} - if empty

console.log(ctx.request.body.files);
console.log('files: ', ctx.request.files);
/* => {uploads: [
{
"size": 748831,
Expand All @@ -58,7 +58,7 @@ router.post('/', koaBody,
}
]}
*/
ctx.body = JSON.stringify(ctx.request.body, null, 2)
ctx.body = JSON.stringify(ctx.request.body, null, 2);
}
)

Expand Down
38 changes: 18 additions & 20 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as Koa from "koa";
import '@types/koa';

declare module "koa" {
interface Request {
body: any;
}
interface Context {
body: any;
interface Request extends BaseRequest {
files?: Array<any>;
}
}

Expand All @@ -15,54 +13,54 @@ declare namespace koaBody {
* {Integer} The expected number of bytes in this form, default null
*/
bytesExpected?: number

/**
* {Integer} Limits the number of fields that the querystring parser will decode, default 1000
*/
maxFields?: number;

/**
* {Integer} Limits the amount of memory all fields together (except files) can allocate in bytes.
* {Integer} Limits the amount of memory all fields together (except files) can allocate in bytes.
* If this value is exceeded, an 'error' event is emitted, default 2mb (2 * 1024 * 1024)
*/
maxFieldsSize?: number;

/**
* {String} Sets the directory for placing file uploads in, default os.tmpDir()
*/
uploadDir?: string;

/**
* {Boolean} Files written to uploadDir will include the extensions of the original files, default false
*/
keepExtensions?: boolean;

/**
* {String} If you want checksums calculated for incoming files, set this to either 'sha1' or 'md5', default false
*/
hash?: string;

/**
* {Boolean} Multiple file uploads or no, default true
*/
multiples?: boolean;
/**
* {Function} Special callback on file begin. The function is executed directly by formidable.
* {Function} Special callback on file begin. The function is executed directly by formidable.
* It can be used to rename files before saving them to disk. See https://github.com/felixge/node-formidable#filebegin
*/
onFileBegin?: (name: string, file: any) => void;
}
interface IKoaBodyOptions {
/**
* {Boolean} Patch request body to Node's ctx.req, default false
*
*
* Note: You can patch request body to Node or Koa in same time if you want.
*/
patchNode?: boolean;

/**
* {Boolean} Patch request body to Koa's ctx.request, default true
*
*
* Note: You can patch request body to Node or Koa in same time if you want.
*/
patchKoa?: boolean;
Expand Down Expand Up @@ -119,17 +117,17 @@ declare namespace koaBody {

/**
* {Boolean} If enabled, don't parse GET, HEAD, DELETE requests, default true
*
* 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.
*
* 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 is strict by default
*
*
* see http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.3
*/
strict?: boolean;
}
}

declare function koaBody (options?: koaBody.IKoaBodyOptions): Koa.Middleware;

export = koaBody;
28 changes: 25 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,44 @@ function requestbody(opts) {
})
.then(function(body) {
if (opts.patchNode) {
ctx.req.body = body;
if (isMultiPart(ctx, opts)) {
ctx.req.body = body.fields;
ctx.req.files = body.files;
} else {
ctx.req.body = body;
}
}
if (opts.patchKoa) {
ctx.request.body = body;
if (isMultiPart(ctx, opts)) {
ctx.request.body = body.fields;
ctx.request.files = body.files;
} else {
ctx.request.body = body;
}
}
return next();
})
};
}

/**
* Check if multipart handling is enabled and that this is a multipart request
*
* @param {Object} ctx
* @param {Object} opts
* @return {Boolean} true if request is multipart and being treated as so
* @api private
*/
function isMultiPart(ctx, opts) {
return opts.multipart && ctx.is('multipart');
}

/**
* Donable formidable
*
* @param {Stream} ctx
* @param {Object} opts
* @return {Object}
* @return {Promise}
* @api private
*/
function formy(ctx, opts) {
Expand Down
105 changes: 54 additions & 51 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,23 @@ describe('koa-body', () => {
ctx.body = user;
})
.post('/users', (ctx, next) => {
const user = ctx.request.body.fields || ctx.request.body;
const user = ctx.request.body;

if(!user) {
ctx.status = 400;
return next();
}
database.users.push(user);
ctx.status = 201;

// return request contents to validate
ctx.body = {
_files: ctx.request.files, // files we populate
user: user // original request data
};
})
.post('/echo_body', (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.request.body;
})
.delete('/users/:user', (ctx, next) => {
Expand Down Expand Up @@ -100,7 +110,7 @@ describe('koa-body', () => {
/**
* MULTIPART - FIELDS
*/
it('should receive `multipart` requests - fields on .body.fields object', (done) => {
it('should receive `multipart` requests - fields on .body object', (done) => {
app.use(koaBody({ multipart: true }));
app.use(router.routes());

Expand All @@ -113,14 +123,12 @@ describe('koa-body', () => {
if (err) return done(err);

const mostRecentUser = database.users[database.users.length - 1];
res.body.fields.should.have.property('name', mostRecentUser.name);
res.body.fields.should.have.property('followers', mostRecentUser.followers);

res.body.fields.name.should.equal('daryl');
res.body.fields.followers.should.equal('30');
res.body.user.should.have.property('name', mostRecentUser.name);
res.body.user.should.have.property('followers', mostRecentUser.followers);

res.body.fields.should.have.property('name', 'daryl');
res.body.fields.should.have.property('followers', '30');
res.body.user.should.have.property('name', 'daryl');
res.body.user.should.have.property('followers', '30');

done();
});
Expand Down Expand Up @@ -153,43 +161,43 @@ describe('koa-body', () => {
.expect(201)
.end( (err, res) => {
if (err) return done(err);
res.body.fields.names.should.be.an.Array().and.have.lengthOf(2);
res.body.fields.names[0].should.equal('John');
res.body.fields.names[1].should.equal('Paul');
res.body.files.firstField.should.be.an.Object;
res.body.files.firstField.name.should.equal('package.json');
should(fs.statSync(res.body.files.firstField.path)).be.ok;
fs.unlinkSync(res.body.files.firstField.path);

res.body.files.secondField.should.be.an.Array().and.have.lengthOf(2);
res.body.files.secondField.should.containDeep([{
res.body.user.names.should.be.an.Array().and.have.lengthOf(2);
res.body.user.names[0].should.equal('John');
res.body.user.names[1].should.equal('Paul');
res.body._files.firstField.should.be.an.Object;
res.body._files.firstField.name.should.equal('package.json');
should(fs.statSync(res.body._files.firstField.path)).be.ok;
fs.unlinkSync(res.body._files.firstField.path);

res.body._files.secondField.should.be.an.Array().and.have.lengthOf(2);
res.body._files.secondField.should.containDeep([{
name: 'index.js'
}]);
res.body.files.secondField.should.containDeep([{
res.body._files.secondField.should.containDeep([{
name: 'package.json'
}]);
should(fs.statSync(res.body.files.secondField[0].path)).be.ok;
should(fs.statSync(res.body.files.secondField[1].path)).be.ok;
fs.unlinkSync(res.body.files.secondField[0].path);
fs.unlinkSync(res.body.files.secondField[1].path);
should(fs.statSync(res.body._files.secondField[0].path)).be.ok;
should(fs.statSync(res.body._files.secondField[1].path)).be.ok;
fs.unlinkSync(res.body._files.secondField[0].path);
fs.unlinkSync(res.body._files.secondField[1].path);

res.body.files.thirdField.should.be.an.Array().and.have.lengthOf(3);
res.body._files.thirdField.should.be.an.Array().and.have.lengthOf(3);

res.body.files.thirdField.should.containDeep([{
res.body._files.thirdField.should.containDeep([{
name: 'LICENSE'
}]);
res.body.files.thirdField.should.containDeep([{
res.body._files.thirdField.should.containDeep([{
name: 'README.md'
}]);
res.body.files.thirdField.should.containDeep([{
res.body._files.thirdField.should.containDeep([{
name: 'package.json'
}]);
should(fs.statSync(res.body.files.thirdField[0].path)).be.ok;
fs.unlinkSync(res.body.files.thirdField[0].path);
should(fs.statSync(res.body.files.thirdField[1].path)).be.ok;
fs.unlinkSync(res.body.files.thirdField[1].path);
should(fs.statSync(res.body.files.thirdField[2].path)).be.ok;
fs.unlinkSync(res.body.files.thirdField[2].path);
should(fs.statSync(res.body._files.thirdField[0].path)).be.ok;
fs.unlinkSync(res.body._files.thirdField[0].path);
should(fs.statSync(res.body._files.thirdField[1].path)).be.ok;
fs.unlinkSync(res.body._files.thirdField[1].path);
should(fs.statSync(res.body._files.thirdField[2].path)).be.ok;
fs.unlinkSync(res.body._files.thirdField[2].path);

done();
});
Expand Down Expand Up @@ -219,10 +227,10 @@ describe('koa-body', () => {
.end( (err, res) => {
if (err) return done(err);

res.body.files.firstField.should.be.an.Object;
res.body.files.firstField.name.should.equal('backage.json');
should(fs.statSync(res.body.files.firstField.path)).be.ok;
fs.unlinkSync(res.body.files.firstField.path);
res.body._files.firstField.should.be.an.Object;
res.body._files.firstField.name.should.equal('backage.json');
should(fs.statSync(res.body._files.firstField.path)).be.ok;
fs.unlinkSync(res.body._files.firstField.path);

done();
});
Expand All @@ -248,14 +256,9 @@ describe('koa-body', () => {
if (err) return done(err);

const mostRecentUser = database.users[database.users.length - 1];
res.body.should.have.property('name', mostRecentUser.name);
res.body.should.have.property('followers', mostRecentUser.followers);

res.body.name.should.equal('example');
res.body.followers.should.equal('41');

res.body.should.have.property('name', 'example');
res.body.should.have.property('followers', '41');
res.body.user.should.have.properties(mostRecentUser);
res.body.user.should.have.properties({ name: 'example', followers: '41' });

done();
});
Expand All @@ -270,15 +273,15 @@ describe('koa-body', () => {
app.use(router.routes());

request(http.createServer(app.callback()))
.post('/users')
.post('/echo_body')
.type('text')
.send('plain text')
.expect(201)
.expect(200)
.end( (err, res) => {
if (err) return done(err);

const mostRecentUser = database.users[database.users.length - 1];
res.text.should.equal(mostRecentUser);
res.type.should.equal('text/plain');
res.text.should.equal('plain text');

done();
});
Expand Down Expand Up @@ -343,9 +346,9 @@ describe('koa-body', () => {
.expect(201)
.end((err, res) => {
const mostRecentUser = database.users[database.users.length - 1];
res.body.should.have.property('name', mostRecentUser.name);
res.body.name.should.equal('json');
done(err);
res.body.user.should.have.properties({ followers: "313", name: "json" });

done(err);
});
});
});
Expand Down

0 comments on commit d7dd15d

Please sign in to comment.