Browse files

Added limit option to json(), urlencoded(), and multipart()

  • Loading branch information...
1 parent ba7cdf2 commit a155d362122ed4fa1dc556c763c6a78cf112e42f @tj tj committed May 18, 2012
Showing with 162 additions and 86 deletions.
  1. +1 −0 Makefile
  2. +28 −0 examples/upload.js
  3. +20 −13 lib/middleware/json.js
  4. +1 −23 lib/middleware/limit.js
  5. +49 −38 lib/middleware/multipart.js
  6. +22 −12 lib/middleware/urlencoded.js
  7. +22 −0 lib/utils.js
  8. +1 −0 test/json.js
  9. +1 −0 test/multipart.js
  10. +16 −0 test/shared/index.js
  11. +1 −0 test/urlencoded.js
View
1 Makefile
@@ -9,6 +9,7 @@ HTML = $(SRC:.js=.html)
test:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
+ --timeout 300 \
$(TESTS)
docs: $(HTML)
View
28 examples/upload.js
@@ -0,0 +1,28 @@
+
+/**
+ * Module dependencies.
+ */
+
+var connect = require('../');
+
+connect()
+ .use(connect.bodyParser())
+ .use(form)
+ .use(upload)
+ .listen(3000);
+
+function form(req, res, next) {
+ if ('GET' !== req.method) return next();
+ res.setHeader('Content-Type', 'text/html');
+ res.end('<form method="post" enctype="multipart/form-data">'
+ + '<input type="file" name="images" multiple="multiple" />'
+ + '<input type="submit" value="Upload" />'
+ + '</form>');
+}
+
+function upload(req, res, next) {
+ if ('POST' !== req.method) return next();
+ req.files.images.forEach(function(file){
+ console.log(' uploaded : %s %skb', file.name, file.size / 1024 | 0);
+ });
+}
View
33 lib/middleware/json.js
@@ -9,7 +9,8 @@
* Module dependencies.
*/
-var utils = require('../utils');
+var utils = require('../utils')
+ , _limit = require('./limit');
/**
* JSON:
@@ -21,6 +22,7 @@ var utils = require('../utils');
*
* - `strict` when `false` anything `JSON.parse()` accepts will be parsed
* - `reviver` used as the second "reviver" argument for JSON.parse
+ * - `limit` byte limit defaulting to "1mb"
*
* @param {Object} options
* @return {Function}
@@ -33,6 +35,8 @@ exports = module.exports = function(options){
? false
: true;
+ var limit = _limit(options.limit || '1mb');
+
return function json(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
@@ -44,18 +48,21 @@ exports = module.exports = function(options){
req._body = true;
// parse
- var buf = '';
- req.setEncoding('utf8');
- req.on('data', function(chunk){ buf += chunk });
- req.on('end', function(){
- if (strict && '{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
- try {
- req.body = JSON.parse(buf, options.reviver);
- next();
- } catch (err){
- err.status = 400;
- next(err);
- }
+ limit(req, res, function(err){
+ if (err) return next(err);
+ var buf = '';
+ req.setEncoding('utf8');
+ req.on('data', function(chunk){ buf += chunk });
+ req.on('end', function(){
+ if (strict && '{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
+ try {
+ req.body = JSON.parse(buf, options.reviver);
+ next();
+ } catch (err){
+ err.status = 400;
+ next(err);
+ }
+ });
});
}
};
View
24 lib/middleware/limit.js
@@ -29,7 +29,7 @@ var utils = require('../utils');
*/
module.exports = function limit(bytes){
- if ('string' == typeof bytes) bytes = parse(bytes);
+ if ('string' == typeof bytes) bytes = utils.parseBytes(bytes);
if ('number' != typeof bytes) throw new Error('limit() bytes required');
return function limit(req, res, next){
var received = 0
@@ -53,25 +53,3 @@ module.exports = function limit(bytes){
next();
};
};
-
-/**
- * Parse byte `size` string.
- *
- * @param {String} size
- * @return {Number}
- * @api private
- */
-
-function parse(size) {
- var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
- , n = parseFloat(parts[1])
- , type = parts[2];
-
- var map = {
- kb: 1024
- , mb: 1024 * 1024
- , gb: 1024 * 1024 * 1024
- };
-
- return map[type] * n;
-}
View
87 lib/middleware/multipart.js
@@ -11,6 +11,7 @@
*/
var formidable = require('formidable')
+ , _limit = require('./limit')
, utils = require('../utils')
, qs = require('qs');
@@ -29,13 +30,19 @@ var formidable = require('formidable')
*
* app.use(connect.multipart({ uploadDir: path }));
*
+ * Options:
+ *
+ * - `limit` byte limit defaulting to "1mb"
+ *
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function(options){
options = options || {};
+ var limit = _limit(options.limit || '20mb');
+
return function multipart(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
@@ -51,49 +58,53 @@ exports = module.exports = function(options){
req._body = true;
// parse
- var form = new formidable.IncomingForm
- , data = {}
- , files = {}
- , done;
-
- Object.keys(options).forEach(function(key){
- form[key] = options[key];
- });
-
- function ondata(name, val, data){
- if (Array.isArray(data[name])) {
- data[name].push(val);
- } else if (data[name]) {
- data[name] = [data[name], val];
- } else {
- data[name] = val;
+ limit(req, res, function(err){
+ if (err) return next(err);
+
+ var form = new formidable.IncomingForm
+ , data = {}
+ , files = {}
+ , done;
+
+ Object.keys(options).forEach(function(key){
+ form[key] = options[key];
+ });
+
+ function ondata(name, val, data){
+ if (Array.isArray(data[name])) {
+ data[name].push(val);
+ } else if (data[name]) {
+ data[name] = [data[name], val];
+ } else {
+ data[name] = val;
+ }
}
- }
- form.on('field', function(name, val){
- ondata(name, val, data);
- });
-
- form.on('file', function(name, val){
- ondata(name, val, files);
- });
+ form.on('field', function(name, val){
+ ondata(name, val, data);
+ });
- form.on('error', function(err){
- next(err);
- done = true;
- });
+ form.on('file', function(name, val){
+ ondata(name, val, files);
+ });
- form.on('end', function(){
- if (done) return;
- try {
- req.body = qs.parse(data);
- req.files = qs.parse(files);
- next();
- } catch (err) {
+ form.on('error', function(err){
next(err);
- }
+ done = true;
+ });
+
+ form.on('end', function(){
+ if (done) return;
+ try {
+ req.body = qs.parse(data);
+ req.files = qs.parse(files);
+ next();
+ } catch (err) {
+ next(err);
+ }
+ });
+
+ form.parse(req);
});
-
- form.parse(req);
}
};
View
34 lib/middleware/urlencoded.js
@@ -11,6 +11,7 @@
*/
var utils = require('../utils')
+ , _limit = require('./limit')
, qs = require('qs');
/**
@@ -19,13 +20,19 @@ var utils = require('../utils')
* Parse x-ww-form-urlencoded request bodies,
* providing the parsed object as `req.body`.
*
+ * Options:
+ *
+ * - `limit` byte limit defaulting to "1mb"
+ *
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function(options){
options = options || {};
+ var limit = _limit(options.limit || '1mb');
+
return function urlencoded(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
@@ -37,18 +44,21 @@ exports = module.exports = function(options){
req._body = true;
// parse
- var buf = '';
- req.setEncoding('utf8');
- req.on('data', function(chunk){ buf += chunk });
- req.on('end', function(){
- try {
- req.body = buf.length
- ? qs.parse(buf, options)
- : {};
- next();
- } catch (err){
- next(err);
- }
+ limit(req, res, function(err){
+ if (err) return next(err);
+ var buf = '';
+ req.setEncoding('utf8');
+ req.on('data', function(chunk){ buf += chunk });
+ req.on('end', function(){
+ try {
+ req.body = buf.length
+ ? qs.parse(buf, options)
+ : {};
+ next();
+ } catch (err){
+ next(err);
+ }
+ });
});
}
};
View
22 lib/utils.js
@@ -506,3 +506,25 @@ exports.parseUrl = function(req){
return req._parsedUrl = parse(req.url);
}
};
+
+/**
+ * Parse byte `size` string.
+ *
+ * @param {String} size
+ * @return {Number}
+ * @api private
+ */
+
+exports.parseBytes = function(size) {
+ var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
+ , n = parseFloat(parts[1])
+ , type = parts[2];
+
+ var map = {
+ kb: 1024
+ , mb: 1024 * 1024
+ , gb: 1024 * 1024 * 1024
+ };
+
+ return map[type] * n;
+};
View
1 test/json.js
@@ -17,6 +17,7 @@ app.use(function(err, req, res, next){
describe('connect.json()', function(){
should['default request body'](app);
+ should['limit body to']('1mb', 'application/json', app);
it('should parse JSON', function(done){
app.request()
View
1 test/multipart.js
@@ -12,6 +12,7 @@ app.use(function(req, res){
describe('connect.multipart()', function(){
should['default request body'](app);
+ should['limit body to']('20mb', 'multipart/form-data', app);
it('should ignore GET', function(done){
app.request()
View
16 test/shared/index.js
@@ -1,4 +1,6 @@
+var utils = require('../../lib/utils');
+
exports['default request body'] = function(app){
it('should default to {}', function(done){
app.request()
@@ -9,3 +11,17 @@ exports['default request body'] = function(app){
})
})
};
+
+exports['limit body to'] = function(size, type, app){
+ it('should limit body to ' + size, function(done){
+ var bytes = utils.parseBytes(size);
+ app.request()
+ .post('/')
+ .set('Content-Length', bytes + 1)
+ .set('Content-Type', type)
+ .end(function(res){
+ res.should.have.status(413);
+ done();
+ })
+ })
+}
View
1 test/urlencoded.js
@@ -12,6 +12,7 @@ app.use(function(req, res){
describe('connect.urlencoded()', function(){
should['default request body'](app);
+ should['limit body to']('1mb', 'application/x-www-form-urlencoded', app);
it('should support all http methods', function(done){
app.request()

0 comments on commit a155d36

Please sign in to comment.