Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: felixge/node-formidable
base: v0.9.5
...
head fork: felixge/node-formidable
compare: v0.9.6
Checking mergeability… Don't worry, you can still create the pull request.
  • 6 commits
  • 10 files changed
  • 0 commit comments
  • 1 contributor
Commits on Jul 16, 2010
@felixge Revert "Feature: IncomingForm.timeout"
This reverts commit 5e2612c.

There seems to be problems with this, causing non-stalled uploads to
report timeouts as well. Need to investigate.
15f0079
Commits on Jul 17, 2010
@felixge Fix: Handling of empty header values and field data
This patch introduces two new events to the multipart parser:
'onHeaderEnd' and 'onHeadersEnd'. These make it much easier to properly
handle the output of the parser.

Another nice addition is a system test that verifies a complete upload
from start to finish.
99c1354
@felixge Add .npmignore file 3b67d93
@felixge Make clean for test/tmp bf21f0c
@felixge Feature: IncomingForm.maxFieldSize
The parser is now considered safe. A malicious client can no longer
allocate huge amounts of memory by sending a big field.
d99eea0
@felixge Bump version 18de5c6
View
2  .npmignore
@@ -0,0 +1,2 @@
+test/tmp/
+*.upload
View
7 Makefile
@@ -1,4 +1,7 @@
test:
- @find test/{simple,integration}/test-*.js | xargs -n 1 -t node
+ @find test/{simple,integration,system}/test-*.js | xargs -n 1 -t node
-.PHONY: test
+clean:
+ rm test/tmp/*
+
+.PHONY: test clean
View
13 Readme.md
@@ -14,7 +14,6 @@ A node.js module for dealing with web forms.
### Todo
-* Limit buffer size for fields
* Implement QuerystringParser the same way as MultipartParser
## Installation
@@ -74,12 +73,6 @@ Creates a new incoming form.
The encoding to use for incoming form fields.
-#### incomingForm.timeout = 30000
-
-The time in ms after which a stalled upload causes an error. Can be set to
-`false` to disable this feature. The resulting error object will have a
-property `timeout` that is set to `true`.
-
#### incomingForm.uploadDir = '/tmp'
The directory for placing file uploads in. You can later on move them using `fs.rename()`.
@@ -92,6 +85,12 @@ If you want the files written to `incomingForm.uploadDir` to include the extensi
Either 'multipart' or 'urlencoded' depending on the incoming request.
+#### incomingForm.maxFieldSize = 2 * 1024 * 1024
+
+Limits the amount of memory a field (not file) can allocate in bytes.
+I this value is exceeded, an `'error'` event is emitted. The default
+size is 2MB.
+
#### incomingForm.bytesReceived
The amount of bytes received for this form so far.
View
80 lib/formidable/incoming_form.js
@@ -14,10 +14,10 @@ function IncomingForm() {
this.error = null;
this.ended = false;
+ this.maxFieldSize = 2 * 1024 * 1024;
this.keepExtensions = false;
this.uploadDir = '/tmp';
this.encoding = 'utf-8';
- this.timeout = 30000;
this.headers = null;
this.type = null;
@@ -26,6 +26,7 @@ function IncomingForm() {
this._parser = null;
this._flushing = 0;
+ this._fieldsSize = 0;
};
sys.inherits(IncomingForm, EventEmitter);
exports.IncomingForm = IncomingForm;
@@ -63,15 +64,6 @@ IncomingForm.prototype.parse = function(req, cb) {
this.writeHeaders(req.headers);
var self = this;
- if (this.timeout) {
- req.connection.setTimeout(this.timeout);
- req.connection.addListener('timeout', function() {
- var err = new Error('timeout, no data received for '+self.timeout+'ms')
- err.timeout = true;
- self._error(err);
- });
- }
-
req
.addListener('error', function(err) {
self._error(err);
@@ -156,13 +148,16 @@ IncomingForm.prototype.handlePart = function(part) {
, decoder = new StringDecoder(this.encoding);
part.addListener('data', function(buffer) {
+ self._fieldsSize += buffer.length;
+ if (self._fieldsSize > self.maxFieldSize) {
+ self._error(new Error('maxFieldSize exceeded, received '+self._fieldsSize+' bytes of field data'));
+ return;
+ }
value += decoder.write(buffer);
});
part.addListener('end', function() {
- if (value) {
- self.emit('field', part.name, value);
- }
+ self.emit('field', part.name, value);
});
return;
}
@@ -243,29 +238,9 @@ IncomingForm.prototype._initMultipart = function(boundary) {
var parser = new MultipartParser()
, self = this
- , headerField = ''
- , headerValue = ''
- , part
- , addHeader = function() {
- headerField = headerField.toLowerCase();
- part.headers[headerField] = headerValue;
-
- var m;
- if (headerField == 'content-disposition') {
- if (m = headerValue.match(/name="([^"]+)"/i)) {
- part.name = m[1];
- }
-
- if (m = headerValue.match(/filename="([^"]+)"/i)) {
- part.filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
- }
- } else if (headerField == 'content-type') {
- part.mime = headerValue;
- }
-
- headerField = '';
- headerValue = '';
- };
+ , headerField
+ , headerValue
+ , part;
parser.initWithBoundary(boundary);
@@ -275,12 +250,11 @@ IncomingForm.prototype._initMultipart = function(boundary) {
part.name = null;
part.filename = null;
part.mime = null;
+ headerField = '';
+ headerValue = '';
};
parser.onHeaderField = function(b, start, end) {
- if (headerValue) {
- addHeader();
- }
headerField += b.toString(self.encoding, start, end);
};
@@ -288,12 +262,32 @@ IncomingForm.prototype._initMultipart = function(boundary) {
headerValue += b.toString(self.encoding, start, end);
};
- parser.onPartData = function(b, start, end) {
- if (headerValue) {
- addHeader();
- self.onPart(part);
+ parser.onHeaderEnd = function() {
+ headerField = headerField.toLowerCase();
+ part.headers[headerField] = headerValue;
+
+ var m;
+ if (headerField == 'content-disposition') {
+ if (m = headerValue.match(/name="([^"]+)"/i)) {
+ part.name = m[1];
+ }
+
+ if (m = headerValue.match(/filename="([^"]+)"/i)) {
+ part.filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
+ }
+ } else if (headerField == 'content-type') {
+ part.mime = headerValue;
}
+ headerField = '';
+ headerValue = '';
+ };
+
+ parser.onHeadersEnd = function() {
+ self.onPart(part);
+ };
+
+ parser.onPartData = function(b, start, end) {
part.emit('data', b.slice(start, end));
};
View
2  lib/formidable/multipart_parser.js
@@ -181,6 +181,7 @@ MultipartParser.prototype.write = function(buffer) {
case S.HEADER_VALUE:
if (c == CR) {
dataCallback('headerValue', true);
+ callback('headerEnd');
state = S.HEADER_VALUE_ALMOST_DONE;
}
break;
@@ -195,6 +196,7 @@ MultipartParser.prototype.write = function(buffer) {
return i;
}
+ callback('headersEnd');
state = S.PART_DATA_START;
break;
case S.PART_DATA_START:
View
2  package.json
@@ -1,5 +1,5 @@
{ "name" : "formidable"
-, "version": "0.9.5"
+, "version": "0.9.6"
, "dependencies": {"gently": ">=0.7.0"}
, "directories" : { "lib" : "./lib/formidable" }
, "main" : "./lib/formidable/index"
View
BIN  test/fixture/multi_video.upload
Binary file not shown
View
28 test/integration/test-multipart-parser.js
@@ -15,9 +15,9 @@ Object.keys(fixtures).forEach(function(name) {
, parts = []
, part = null
- , headerField = null
- , headerValue = null
- , endCalled = false;
+ , headerField
+ , headerValue
+ , endCalled = '';
parser.initWithBoundary(fixture.boundary);
parser.onPartBegin = function() {
@@ -28,27 +28,21 @@ Object.keys(fixtures).forEach(function(name) {
};
parser.onHeaderField = function(b, start, end) {
- var str = b.toString('ascii', start, end);
- if (headerValue) {
- part.headers[headerField] = headerValue;
- headerField = '';
- headerValue = '';
- }
- headerField += str;
+ headerField += b.toString('ascii', start, end);
};
parser.onHeaderValue = function(b, start, end) {
- var str = b.toString('ascii', start, end);
- headerValue += str;
+ headerValue += b.toString('ascii', start, end);
}
+ parser.onHeaderEnd = function() {
+ part.headers[headerField] = headerValue;
+ headerField = '';
+ headerValue = '';
+ };
+
parser.onPartData = function(b, start, end) {
var str = b.toString('ascii', start, end);
- if (headerField) {
- part.headers[headerField] = headerValue;
- headerValue = '';
- headerField = '';
- }
part.data += b.binarySlice(start, end);
}
View
49 test/simple/test-incoming-form.js
@@ -29,33 +29,24 @@ test(function constructor() {
assert.strictEqual(form.keepExtensions, false);
assert.strictEqual(form.uploadDir, '/tmp');
assert.strictEqual(form.encoding, 'utf-8');
- assert.strictEqual(form.timeout, 30000);
assert.strictEqual(form.bytesReceived, null);
assert.strictEqual(form.bytesExpected, null);
+ assert.strictEqual(form.maxFieldSize, 2 * 1024 * 1024);
assert.strictEqual(form._parser, null);
assert.strictEqual(form._flushing, 0);
+ assert.strictEqual(form._fieldsSize, 0);
assert.ok(form instanceof EventEmitterStub);
assert.equal(form.constructor.name, 'IncomingForm');
});
test(function parse() {
- var REQ = {headers: {}, connection: {}}
- , emit = {}
- , reqEmit = {};
+ var REQ = {headers: {}}
+ , emit = {};
gently.expect(form, 'writeHeaders', function(headers) {
assert.strictEqual(headers, REQ.headers);
});
- gently.expect(REQ.connection, 'setTimeout', function(timeout) {
- assert.equal(timeout, form.timeout);
- });
-
- gently.expect(REQ.connection, 'addListener', function(event, fn) {
- assert.equal(event, 'timeout');
- reqEmit[event] = fn;
- });
-
var events = ['error', 'data', 'end'];
gently.expect(REQ, 'addListener', events.length, function(event, fn) {
assert.equal(event, events.shift());
@@ -126,15 +117,6 @@ test(function parse() {
assert.strictEqual(form.resume(), false);
})();
-
- (function testReqEmitTimeout() {
- gently.expect(form, '_error', function(err) {
- assert.equal(err.message, 'timeout, no data received for '+form.timeout+'ms');
- assert.equal(err.timeout, true);
- });
-
- reqEmit.timeout();
- })();
(function testEmitError() {
var ERR = new Error('something bad happened');
@@ -186,8 +168,6 @@ test(function parse() {
, REQ = {headers: {}}
, parseCalled = 0;
- form.timeout = null;
-
gently.expect(form, 'writeHeaders');
gently.expect(REQ, 'addListener', 3, function() {
return this;
@@ -423,8 +403,11 @@ test(function _initMultipart() {
PARSER.onHeaderField(new Buffer('content-disposition'), 10, 19);
PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 0, 14);
PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 14, 24);
+ PARSER.onHeaderEnd();
PARSER.onHeaderField(new Buffer('foo'), 0, 3);
PARSER.onHeaderValue(new Buffer('bar'), 0, 3);
+ PARSER.onHeaderEnd();
+ PARSER.onHeadersEnd();
PARSER.onPartData(new Buffer('hello world'), 0, 5);
PARSER.onPartData(new Buffer('hello world'), 5, 11);
PARSER.onPartEnd();
@@ -460,8 +443,11 @@ test(function _initMultipart() {
PARSER.onPartBegin();
PARSER.onHeaderField(new Buffer('content-disposition'), 0, 19);
PARSER.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sunset.jpg"'), 0, 85);
+ PARSER.onHeaderEnd();
PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12);
PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10);
+ PARSER.onHeaderEnd();
+ PARSER.onHeadersEnd();
PARSER.onPartData(new Buffer('... contents of file1.txt ...'), 0, 29);
PARSER.onPartEnd();
})();
@@ -566,6 +552,21 @@ test(function handlePart() {
PART.emit('end');
})();
+ (function testFieldSize() {
+ form.maxFieldSize = 8;
+ var PART = new events.EventEmitter();
+ PART.name = 'my_field';
+
+ gently.expect(form, '_error', function(err) {
+ assert.equal(err.message, 'maxFieldSize exceeded, received 9 bytes of field data');
+ });
+
+ form.handlePart(PART);
+ form._fieldsSize = 1;
+ PART.emit('data', new Buffer(7));
+ PART.emit('data', new Buffer(1));
+ })();
+
(function testFilePart() {
var PART = new events.EventEmitter()
, FILE = new events.EventEmitter()
View
52 test/system/test-multi-video-upload.js
@@ -0,0 +1,52 @@
+require('../common');
+var BOUNDARY = '---------------------------10102754414578508781458777923'
+ , FIXTURE = TEST_FIXTURES+'/multi_video.upload'
+ , fs = require('fs')
+ , sys = require('sys')
+ , http = require('http')
+ , formidable = require('formidable')
+ , server = http.createServer();
+
+server.addListener('request', function(req, res) {
+ var form = new formidable.IncomingForm()
+ , files = {};
+
+ form.uploadDir = TEST_TMP;
+ form.parse(req);
+
+ form
+ .addListener('field', function(field, value) {
+ assert.equal(field, 'title');
+ assert.equal(value, '');
+ })
+ .addListener('file', function(field, file) {
+ assert.equal(field, 'upload');
+ files[file.filename] = true;
+ })
+ .addListener('end', function() {
+ assert.deepEqual
+ ( files
+ , { 'shortest_video.flv': true
+ , 'shortest_video.mp4' :true
+ }
+ );
+ server.close();
+ res.writeHead(200);
+ res.end('good');
+ });
+});
+
+server.listen(TEST_PORT, function() {
+ var client = http.createClient(TEST_PORT)
+ , headers = {'content-type': 'multipart/form-data; boundary='+BOUNDARY}
+ , request = client.request('POST', '/', headers)
+ , fixture = new fs.ReadStream(FIXTURE);
+
+ fixture
+ .addListener('data', function(b) {;
+ request.write(b);
+ })
+ .addListener('end', function() {
+ request.end();
+ });
+});

No commit comments for this range

Something went wrong with that request. Please try again.