Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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.7
...
head fork: felixge/node-formidable
compare: v0.9.10
  • 20 commits
  • 20 files changed
  • 9 commit comments
  • 4 contributors
Commits on Jul 28, 2010
@felixge Work around race condition
It seems like connections are dying randomly when not sending
a content-length header.
ac041fc
Commits on Sep 08, 2010
@substack substack can now just do `new formidable` e94f24e
@substack substack `formdiable()` now does the same thing as `new formidable` 6e5c78b
@felixge Test cases for substack's patches 8f5e7f4
@felixge Bump version 6ccf2d5
Commits on Sep 17, 2010
@felixge Fix: Handle quotes in filenames 7b60942
Commits on Oct 18, 2010
@felixge Compatibility with node HEAD 720f4a3
@felixge Missing module for previous commit 77b9465
@felixge Feature: incomingForm 'fileBegin' event
This event is useful if you need to buffer the uploaded file to disk, but
you are interested in streaming it to somewhere else while it is still
being uploaded.

You may need this if you are streaming the file to a destination that is
potentially slower at receiving it than the client that is uploading. By
buffering to disk you can ensure maximum upload speed for the client, while
having the flexibility to streaming the file on disk with a different speed.
cf45cdc
@felixge Refactoring addListener -> on 0e54a07
@felixge Go back to traditional coding style
Comma first doesn't seem light it has caught on, so this commit changes
the projects coding style back to the Google-ish standard formatting.
ed4e297
Commits on Oct 19, 2010
@felixge Bump version 229dc69
Commits on Oct 24, 2010
@felixge Fix comma error introduced in refactoring df18901
Commits on Oct 30, 2010
@felixge Better project description
Also added a paragraph about the state of the module with a tiny advertisment for
transloadit ; ).
0cba820
Commits on Nov 12, 2010
@felixge Fix global variable leak
See report here: 792503a#L0R252
82050b7
Commits on Nov 25, 2010
@felixge Make Makefile more portable 66fbaae
Commits on Dec 07, 2010
@aheckmann aheckmann fix global var leaks (comma last issue) 8e6cb2e
Commits on Dec 15, 2010
@ncr ncr Remove parsing of optional CRLF epilogoue, change parser so it ignore…
…s everything after last boundary
4800b7f
@felixge Bringing back the old rfc1867 test
The test without the trailing \r\n is now a separate test.
633f0b5
@felixge Bump version 2b02144
View
4 Makefile
@@ -1,7 +1,9 @@
+SHELL := /bin/bash
+
test:
@find test/{simple,integration,system}/test-*.js | xargs -n 1 -t node
clean:
rm test/tmp/*
-.PHONY: test clean
+.PHONY: test clean
View
61 Readme.md
@@ -2,7 +2,13 @@
## Purpose
-A node.js module for dealing with web forms.
+A node.js module for parsing form data, especially file uploads.
+
+## Current status
+
+This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading
+and encoding images and videos. It has been battle-tested against hundreds of GB of file uploads from
+a big variety of clients and is considered production-ready.
## Features
@@ -34,9 +40,10 @@ Note: Formidable requires [gently](http://github.com/felixge/node-gently) to run
Parse an incoming file upload.
- var formidable = require('formidable')
- , http = require('http')
- , sys = require('sys');
+ var formidable = require('formidable'),
+ http = require('http'),
+
+ sys = require('sys');
http.createServer(function(req, res) {
if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
@@ -52,13 +59,13 @@ Parse an incoming file upload.
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
- res.end
- ( '<form action="/upload" enctype="multipart/form-data" method="post">'
- + '<input type="text" name="title"><br>'
- + '<input type="file" name="upload" multiple="multiple"><br>'
- + '<input type="submit" value="Upload">'
- + '</form>'
- );
+ res.end(
+ '<form action="/upload" enctype="multipart/form-data" method="post">'+
+ '<input type="text" name="title"><br>'+
+ '<input type="file" name="upload" multiple="multiple"><br>'+
+ '<input type="submit" value="Upload">'+
+ '</form>'
+ );
});
## API
@@ -136,14 +143,15 @@ Emitted after each incoming chunk of data that has been parsed. Can be used to r
Emitted whenever a field / value pair has been received.
-#### Event: 'file' (name, file)
+#### Event: 'fileBegin' (name, file)
-Emitted whenever a field / file pair has been received. `file` is a JS object with the following properties:
+Emitted whenever a new file is detected in the upload stream. Use this even if
+you want to stream the file to somewhere else while buffering the upload on
+the file system.
- { path: 'the path in the uploadDir this file was written to'
- , filename: 'the name this file had on the computer of the uploader'
- , mime: 'the mime type specified by the user agent of the uploader'
- }
+#### Event: 'file' (name, file)
+
+Emitted whenever a field / file pair has been received. `file` is an instance of `File`.
#### Event: 'error' (err)
@@ -153,6 +161,25 @@ Emitted when there is an error processing the incoming form. A request that expe
Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response.
+### formdiable.File
+
+#### file.length = 0
+
+The size of the uploade file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet.
+
+#### file.path = null
+
+The path this file is being written to. You can modify this in the `'fileBegin'` event in
+case you are unhappy with the way formidable generates a temporary path for your files.
+
+#### file.filename = null
+
+The name this file had according to the uploading client.
+
+#### file.mime = null
+
+The mime type of this file, according to the uploading client.
+
## License
Formidable is licensed under the MIT license.
View
42 benchmark/bench-multipart-parser.js
@@ -1,18 +1,18 @@
require('../test/common');
-var multipartParser = require('formidable/multipart_parser')
- , MultipartParser = multipartParser.MultipartParser
- , parser = new MultipartParser()
- , Buffer = require('buffer').Buffer
- , boundary = '-----------------------------168072824752491622650073'
- , mb = 100
- , buffer = createMultipartBuffer(boundary, mb * 1024 * 1024)
- , callbacks =
- { partBegin: -1
- , partEnd: -1
- , headerField: -1
- , headerValue: -1
- , partData: -1
- , end: -1
+var multipartParser = require('formidable/multipart_parser'),
+ MultipartParser = multipartParser.MultipartParser,
+ parser = new MultipartParser(),
+ Buffer = require('buffer').Buffer,
+ boundary = '-----------------------------168072824752491622650073',
+ mb = 100,
+ buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
+ callbacks =
+ { partBegin: -1,
+ partEnd: -1,
+ headerField: -1,
+ headerValue: -1,
+ partData: -1,
+ end: -1,
};
@@ -41,10 +41,10 @@ parser.onEnd = function() {
callbacks.end++;
};
-var start = +new Date()
- , nparsed = parser.write(buffer)
- , duration = +new Date - start
- , mbPerSec = (mb / (duration / 1000)).toFixed(2);
+var start = +new Date(),
+ nparsed = parser.write(buffer),
+ duration = +new Date - start,
+ mbPerSec = (mb / (duration / 1000)).toFixed(2);
p(mbPerSec+' mb/sec');
@@ -63,4 +63,8 @@ function createMultipartBuffer(boundary, size) {
return buffer;
}
-assert.callbacks(callbacks);
+process.on('exit', function() {
+ for (var k in callbacks) {
+ assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
+ }
+});
View
38 example/post.js
@@ -1,36 +1,36 @@
require('../test/common');
-var http = require('http')
- , sys = require('sys')
- , formidable = require('formidable')
- , server;
+var http = require('http'),
+ util = require('util'),
+ formidable = require('formidable'),
+ server;
server = http.createServer(function(req, res) {
if (req.url == '/') {
res.writeHead(200, {'content-type': 'text/html'});
- res.end
- ( '<form action="/post" method="post">'
- + '<input type="text" name="title"><br>'
- + '<input type="text" name="data[foo][]"><br>'
- + '<input type="submit" value="Submit">'
- + '</form>'
- )
+ res.end(
+ '<form action="/post" method="post">'+
+ '<input type="text" name="title"><br>'+
+ '<input type="text" name="data[foo][]"><br>'+
+ '<input type="submit" value="Submit">'+
+ '</form>'
+ );
} else if (req.url == '/post') {
- var form = new formidable.IncomingForm()
- , fields = [];
+ var form = new formidable.IncomingForm(),
+ fields = [];
form
- .addListener('error', function(err) {
+ .on('error', function(err) {
res.writeHead(200, {'content-type': 'text/plain'});
- res.end('error:\n\n'+sys.inspect(err));
+ res.end('error:\n\n'+util.inspect(err));
})
- .addListener('field', function(field, value) {
+ .on('field', function(field, value) {
p([field, value]);
fields.push([field, value]);
})
- .addListener('end', function() {
+ .on('end', function() {
puts('-> post done');
res.writeHead(200, {'content-type': 'text/plain'});
- res.end('received fields:\n\n '+sys.inspect(fields));
+ res.end('received fields:\n\n '+util.inspect(fields));
});
form.parse(req);
} else {
@@ -40,4 +40,4 @@ server = http.createServer(function(req, res) {
});
server.listen(TEST_PORT);
-sys.puts('listening on http://localhost:'+TEST_PORT+'/');
+util.puts('listening on http://localhost:'+TEST_PORT+'/');
View
40 example/upload.js
@@ -1,41 +1,41 @@
require('../test/common');
-var http = require('http')
- , sys = require('sys')
- , formidable = require('formidable')
- , server;
+var http = require('http'),
+ util = require('util'),
+ formidable = require('formidable'),
+ server;
server = http.createServer(function(req, res) {
if (req.url == '/') {
res.writeHead(200, {'content-type': 'text/html'});
- res.end
- ( '<form action="/upload" enctype="multipart/form-data" method="post">'
- + '<input type="text" name="title"><br>'
- + '<input type="file" name="upload" multiple="multiple"><br>'
- + '<input type="submit" value="Upload">'
- + '</form>'
- )
+ res.end(
+ '<form action="/upload" enctype="multipart/form-data" method="post">'+
+ '<input type="text" name="title"><br>'+
+ '<input type="file" name="upload" multiple="multiple"><br>'+
+ '<input type="submit" value="Upload">'+
+ '</form>'
+ );
} else if (req.url == '/upload') {
- var form = new formidable.IncomingForm()
- , files = []
- , fields = [];
+ var form = new formidable.IncomingForm(),
+ files = [],
+ fields = [];
form.uploadDir = TEST_TMP;
form
- .addListener('field', function(field, value) {
+ .on('field', function(field, value) {
p([field, value]);
fields.push([field, value]);
})
- .addListener('file', function(field, file) {
+ .on('file', function(field, file) {
p([field, file]);
files.push([field, file]);
})
- .addListener('end', function() {
+ .on('end', function() {
puts('-> upload done');
res.writeHead(200, {'content-type': 'text/plain'});
- res.write('received fields:\n\n '+sys.inspect(fields));
+ res.write('received fields:\n\n '+util.inspect(fields));
res.write('\n\n');
- res.end('received files:\n\n '+sys.inspect(files));
+ res.end('received files:\n\n '+util.inspect(files));
});
form.parse(req);
} else {
@@ -45,4 +45,4 @@ server = http.createServer(function(req, res) {
});
server.listen(TEST_PORT);
-sys.puts('listening on http://localhost:'+TEST_PORT+'/');
+util.puts('listening on http://localhost:'+TEST_PORT+'/');
View
43 lib/formidable/file.js
@@ -0,0 +1,43 @@
+if (global.GENTLY) require = GENTLY.hijack(require);
+
+var util = require('./util'),
+ WriteStream = require('fs').WriteStream,
+ EventEmitter = require('events').EventEmitter;
+
+function File(properties) {
+ EventEmitter.call(this);
+
+ this.length = 0;
+ this.path = null;
+ this.filename = null;
+ this.mime = null;
+
+ this._writeStream = null;
+
+ for (var key in properties) {
+ this[key] = properties[key];
+ }
+}
+module.exports = File;
+util.inherits(File, EventEmitter);
+
+File.prototype.open = function() {
+ this._writeStream = new WriteStream(this.path);
+};
+
+File.prototype.write = function(buffer, cb) {
+ var self = this;
+ this._writeStream.write(buffer, function() {
+ self.length += buffer.length;
+ self.emit('progress', self.length);
+ cb();
+ });
+};
+
+File.prototype.end = function(cb) {
+ var self = this;
+ this._writeStream.end(function() {
+ self.emit('end');
+ cb();
+ });
+};
View
1  lib/formidable/formidable.js
@@ -1 +0,0 @@
-exports.IncomingForm = require('./incoming_form').IncomingForm;
View
75 lib/formidable/incoming_form.js
@@ -1,14 +1,15 @@
if (global.GENTLY) require = GENTLY.hijack(require);
-var sys = require('sys')
- , path = require('path')
- , WriteStream = require('fs').WriteStream
- , MultipartParser = require('./multipart_parser').MultipartParser
- , QuerystringParser = require('./querystring_parser').QuerystringParser
- , StringDecoder = require('string_decoder').StringDecoder
- , EventEmitter = require('events').EventEmitter;
+var util = require('./util'),
+ path = require('path'),
+ File = require('./file'),
+ MultipartParser = require('./multipart_parser').MultipartParser,
+ QuerystringParser = require('./querystring_parser').QuerystringParser,
+ StringDecoder = require('string_decoder').StringDecoder,
+ EventEmitter = require('events').EventEmitter;
function IncomingForm() {
+ if (!(this instanceof IncomingForm)) return new IncomingForm;
EventEmitter.call(this);
this.error = null;
@@ -28,7 +29,7 @@ function IncomingForm() {
this._flushing = 0;
this._fieldsSize = 0;
};
-sys.inherits(IncomingForm, EventEmitter);
+util.inherits(IncomingForm, EventEmitter);
exports.IncomingForm = IncomingForm;
IncomingForm.prototype.parse = function(req, cb) {
@@ -65,13 +66,13 @@ IncomingForm.prototype.parse = function(req, cb) {
var self = this;
req
- .addListener('error', function(err) {
+ .on('error', function(err) {
self._error(err);
})
- .addListener('data', function(buffer) {
+ .on('data', function(buffer) {
self.write(buffer);
})
- .addListener('end', function() {
+ .on('end', function() {
if (self.error) {
return;
}
@@ -85,16 +86,16 @@ IncomingForm.prototype.parse = function(req, cb) {
if (cb) {
var fields = {}, files = {};
this
- .addListener('field', function(name, value) {
+ .on('field', function(name, value) {
fields[name] = value;
})
- .addListener('file', function(name, file) {
+ .on('file', function(name, file) {
files[name] = file;
})
- .addListener('error', function(err) {
+ .on('error', function(err) {
cb(err, fields, files);
})
- .addListener('end', function() {
+ .on('end', function() {
cb(null, fields, files);
});
}
@@ -147,7 +148,7 @@ IncomingForm.prototype.handlePart = function(part) {
var value = ''
, decoder = new StringDecoder(this.encoding);
- part.addListener('data', function(buffer) {
+ part.on('data', function(buffer) {
self._fieldsSize += buffer.length;
if (self._fieldsSize > self.maxFieldsSize) {
self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
@@ -156,7 +157,7 @@ IncomingForm.prototype.handlePart = function(part) {
value += decoder.write(buffer);
});
- part.addListener('end', function() {
+ part.on('end', function() {
self.emit('field', part.name, value);
});
return;
@@ -164,25 +165,27 @@ IncomingForm.prototype.handlePart = function(part) {
this._flushing++;
- var file = new WriteStream(this._uploadPath(part.filename));
- part.addListener('data', function(buffer) {
+ var file = new File({
+ path: this._uploadPath(part.filename),
+ filename: part.filename,
+ mime: part.mime,
+ });
+
+ this.emit('fileBegin', part.name, file);
+
+ file.open();
+
+ part.on('data', function(buffer) {
self.pause();
file.write(buffer, function() {
self.resume();
});
});
- part.addListener('end', function() {
+ part.on('end', function() {
file.end(function() {
self._flushing--;
- self.emit
- ( 'file'
- , part.name
- , { path: file.path
- , filename: part.filename
- , mime: part.mime
- }
- );
+ self.emit('file', part.name, file);
self._maybeEnd();
});
});
@@ -236,11 +239,11 @@ IncomingForm.prototype._newParser = function() {
IncomingForm.prototype._initMultipart = function(boundary) {
this.type = 'multipart';
- var parser = new MultipartParser()
- , self = this
- , headerField
- , headerValue
- , part;
+ var parser = new MultipartParser(),
+ self = this,
+ headerField,
+ headerValue,
+ part;
parser.initWithBoundary(boundary);
@@ -272,7 +275,7 @@ IncomingForm.prototype._initMultipart = function(boundary) {
part.name = m[1];
}
- if (m = headerValue.match(/filename="([^"]+)"/i)) {
+ if (m = headerValue.match(/filename="([^;]+)"/i)) {
part.filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
}
} else if (headerField == 'content-type') {
@@ -323,7 +326,7 @@ IncomingForm.prototype._initUrlencoded = function() {
IncomingForm.prototype._uploadPath = function(filename) {
var name = '';
- for (i = 0; i < 32; i++) {
+ for (var i = 0; i < 32; i++) {
name += Math.floor(Math.random() * 16).toString(16);
}
@@ -340,4 +343,4 @@ IncomingForm.prototype._maybeEnd = function() {
}
this.emit('end');
-};
+};
View
4 lib/formidable/index.js
@@ -1 +1,3 @@
-module.exports = require('./formidable');
+var IncomingForm = require('./incoming_form').IncomingForm;
+IncomingForm.IncomingForm = IncomingForm;
+module.exports = IncomingForm;
View
128 lib/formidable/multipart_parser.js
@@ -1,36 +1,36 @@
-var Buffer = require('buffer').Buffer
- , s = 0
- , S =
- { PARSER_UNINITIALIZED: s++
- , START: s++
- , START_BOUNDARY: s++
- , HEADER_FIELD_START: s++
- , HEADER_FIELD: s++
- , HEADER_VALUE_START: s++
- , HEADER_VALUE: s++
- , HEADER_VALUE_ALMOST_DONE: s++
- , HEADERS_ALMOST_DONE: s++
- , PART_DATA_START: s++
- , PART_DATA: s++
- , PART_END: s++
- , END: s++
- }
+var Buffer = require('buffer').Buffer,
+ s = 0,
+ S =
+ { PARSER_UNINITIALIZED: s++,
+ START: s++,
+ START_BOUNDARY: s++,
+ HEADER_FIELD_START: s++,
+ HEADER_FIELD: s++,
+ HEADER_VALUE_START: s++,
+ HEADER_VALUE: s++,
+ HEADER_VALUE_ALMOST_DONE: s++,
+ HEADERS_ALMOST_DONE: s++,
+ PART_DATA_START: s++,
+ PART_DATA: s++,
+ PART_END: s++,
+ END: s++,
+ },
- , f = 1
- , F =
- { PART_BOUNDARY: f
- , LAST_BOUNDARY: f *= 2
- }
+ f = 1,
+ F =
+ { PART_BOUNDARY: f,
+ LAST_BOUNDARY: f *= 2,
+ },
- , LF = 10
- , CR = 13
- , SPACE = 32
- , HYPHEN = 45
- , COLON = 58
- , A = 97
- , Z = 122
+ LF = 10,
+ CR = 13,
+ SPACE = 32,
+ HYPHEN = 45,
+ COLON = 58,
+ A = 97,
+ Z = 122,
- , lower = function(c) {
+ lower = function(c) {
return c | 0x20;
};
@@ -63,29 +63,29 @@ MultipartParser.prototype.initWithBoundary = function(str) {
};
MultipartParser.prototype.write = function(buffer) {
- var self = this
- , i = 0
- , len = buffer.length
- , prevIndex = this.index
- , index = this.index
- , state = this.state
- , flags = this.flags
- , lookbehind = this.lookbehind
- , boundary = this.boundary
- , boundaryChars = this.boundaryChars
- , boundaryLength = this.boundary.length
- , boundaryEnd = boundaryLength - 1
- , bufferLength = buffer.length
- , c
- , cl
+ var self = this,
+ i = 0,
+ len = buffer.length,
+ prevIndex = this.index,
+ index = this.index,
+ state = this.state,
+ flags = this.flags,
+ lookbehind = this.lookbehind,
+ boundary = this.boundary,
+ boundaryChars = this.boundaryChars,
+ boundaryLength = this.boundary.length,
+ boundaryEnd = boundaryLength - 1,
+ bufferLength = buffer.length,
+ c,
+ cl,
- , mark = function(name) {
+ mark = function(name) {
self[name+'Mark'] = i;
- }
- , clear = function(name) {
+ },
+ clear = function(name) {
delete self[name+'Mark'];
- }
- , callback = function(name, buffer, start, end) {
+ },
+ callback = function(name, buffer, start, end) {
if (start !== undefined && start === end) {
return;
}
@@ -94,8 +94,8 @@ MultipartParser.prototype.write = function(buffer) {
if (callbackSymbol in self) {
self[callbackSymbol](buffer, start, end);
}
- }
- , dataCallback = function(name, clear) {
+ },
+ dataCallback = function(name, clear) {
var markSymbol = name+'Mark';
if (!(markSymbol in self)) {
return;
@@ -250,27 +250,15 @@ MultipartParser.prototype.write = function(buffer) {
}
} else if (flags & F.LAST_BOUNDARY) {
if (c == HYPHEN) {
- index++;
+ callback('partEnd');
+ callback('end');
+ state = S.END;
} else {
index = 0;
}
} else {
index = 0;
}
- } else if (index - 2 == boundary.length) {
- if (c == CR) {
- index++;
- } else {
- index = 0;
- }
- } else if (index - boundary.length == 3) {
- index = 0;
- if (c == LF) {
- callback('partEnd');
- callback('end');
- state = S.END;
- break;
- }
}
if (index > 0) {
@@ -288,7 +276,9 @@ MultipartParser.prototype.write = function(buffer) {
// it could be the beginning of a new sequence
i--;
}
-
+
+ break;
+ case S.END:
break;
default:
return i;
@@ -310,4 +300,4 @@ MultipartParser.prototype.end = function() {
if (this.state != S.END) {
return new Error('MultipartParser.end(): stream ended unexpectedly');
}
-};
+};
View
6 lib/formidable/util.js
@@ -0,0 +1,6 @@
+// Backwards compatibility ...
+try {
+ module.exports = require('util');
+} catch (e) {
+ module.exports = require('sys');
+}
View
12 package.json
@@ -1,6 +1,6 @@
-{ "name" : "formidable"
-, "version": "0.9.7"
-, "dependencies": {"gently": ">=0.7.0"}
-, "directories" : { "lib" : "./lib/formidable" }
-, "main" : "./lib/formidable/index"
-}
+{ "name" : "formidable",
+ "version": "0.9.10",
+ "dependencies": {"gently": ">=0.7.0"},
+ "directories" : { "lib" : "./lib/formidable" },
+ "main" : "./lib/formidable/index"
+}
View
49 test/common.js
@@ -1,9 +1,8 @@
-var path = require('path')
- , fs = require('fs')
- , sys = require('sys')
- , timeout;
+var path = require('path'),
+ fs = require('fs');
require.paths.unshift(path.dirname(__dirname)+'/lib');
+var util = require('formidable/util');
try {
global.Gently = require('gently');
@@ -13,49 +12,11 @@ try {
global.GENTLY = new Gently();
-global.puts = sys.puts;
+global.puts = util.puts;
global.p = function() {
- sys.error(sys.inspect.apply(null, arguments));
+ util.error(util.inspect.apply(null, arguments));
};
global.assert = require('assert');
global.TEST_PORT = 13532;
global.TEST_FIXTURES = path.join(__dirname, 'fixture');
global.TEST_TMP = path.join(__dirname, 'tmp');
-
-assert.timeout = function(ms) {
- return setTimeout(function() {
- timeout = 'timeout (after '+ms+' ms): ';
- process.emit('exit');
-
- throw new Error('timeout after '+ms+' ms');
- }, ms);
-};
-
-assert.callbacks = function(callbacks) {
- process.addListener('exit', function() {
- for (var k in callbacks) {
- assert.equal(0, callbacks[k], (timeout || '')+k+' count off by '+callbacks[k]);
- }
- });
-};
-
-assert.properties = function(obj, properties) {
- properties.forEach(function(property) {
- assert.ok(property in obj, 'has property: '+property);
- });
-
- for (var property in obj) {
- if (!obj.hasOwnProperty(property)) {
- continue;
- }
-
- if (typeof obj[property] == 'function') {
- continue;
- }
-
- assert.ok(
- properties.indexOf(property) > -1,
- 'does not have property: '+property
- );
- }
-};
View
67 test/fixture/multipart.js
@@ -1,9 +1,6 @@
-var puts = require('sys').puts;
-var p = require('sys').p;
-
exports['rfc1867'] =
- { boundary: 'AaB03x'
- , raw:
+ { boundary: 'AaB03x',
+ raw:
'--AaB03x\r\n'+
'content-disposition: form-data; name="field1"\r\n'+
'\r\n'+
@@ -13,25 +10,53 @@ exports['rfc1867'] =
'Content-Type: text/plain\r\n'+
'\r\n'+
'... contents of file1.txt ...\r\r\n'+
- '--AaB03x--\r\n'
- , parts:
- [ { headers:
- { 'content-disposition': 'form-data; name="field1"'
- }
- , data: 'Joe Blow\r\nalmost tricked you!'
+ '--AaB03x--\r\n',
+ parts:
+ [ { headers: {
+ 'content-disposition': 'form-data; name="field1"',
+ },
+ data: 'Joe Blow\r\nalmost tricked you!',
+ },
+ { headers: {
+ 'content-disposition': 'form-data; name="pics"; filename="file1.txt"',
+ 'Content-Type': 'text/plain',
+ },
+ data: '... contents of file1.txt ...\r',
}
- , { headers:
- { 'content-disposition': 'form-data; name="pics"; filename="file1.txt"'
- , 'Content-Type': 'text/plain'
- }
- , data: '... contents of file1.txt ...\r'
+ ]
+ };
+
+exports['noTrailing\r\n'] =
+ { boundary: 'AaB03x',
+ raw:
+ '--AaB03x\r\n'+
+ 'content-disposition: form-data; name="field1"\r\n'+
+ '\r\n'+
+ 'Joe Blow\r\nalmost tricked you!\r\n'+
+ '--AaB03x\r\n'+
+ 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+
+ 'Content-Type: text/plain\r\n'+
+ '\r\n'+
+ '... contents of file1.txt ...\r\r\n'+
+ '--AaB03x--',
+ parts:
+ [ { headers: {
+ 'content-disposition': 'form-data; name="field1"',
+ },
+ data: 'Joe Blow\r\nalmost tricked you!',
+ },
+ { headers: {
+ 'content-disposition': 'form-data; name="pics"; filename="file1.txt"',
+ 'Content-Type': 'text/plain',
+ },
+ data: '... contents of file1.txt ...\r',
}
]
};
exports['emptyHeader'] =
- { boundary: 'AaB03x'
- , raw:
+ { boundary: 'AaB03x',
+ raw:
'--AaB03x\r\n'+
'content-disposition: form-data; name="field1"\r\n'+
': foo\r\n'+
@@ -42,6 +67,6 @@ exports['emptyHeader'] =
'Content-Type: text/plain\r\n'+
'\r\n'+
'... contents of file1.txt ...\r\r\n'+
- '--AaB03x--\r\n'
- , expectError: true
- };
+ '--AaB03x--\r\n',
+ expectError: true,
+ };
View
36 test/integration/test-multipart-parser.js
@@ -1,23 +1,23 @@
require('../common');
-var CHUNK_LENGTH = 10
- , multipartParser = require('formidable/multipart_parser')
- , MultipartParser = multipartParser.MultipartParser
- , parser = new MultipartParser()
- , fixtures = require('../fixture/multipart')
- , Buffer = require('buffer').Buffer;
+var CHUNK_LENGTH = 10,
+ multipartParser = require('formidable/multipart_parser'),
+ MultipartParser = multipartParser.MultipartParser,
+ parser = new MultipartParser(),
+ fixtures = require('../fixture/multipart'),
+ Buffer = require('buffer').Buffer;
Object.keys(fixtures).forEach(function(name) {
- var fixture = fixtures[name]
- , buffer = new Buffer(Buffer.byteLength(fixture.raw, 'binary'))
- , offset = 0
- , chunk
- , nparsed
+ var fixture = fixtures[name],
+ buffer = new Buffer(Buffer.byteLength(fixture.raw, 'binary')),
+ offset = 0,
+ chunk,
+ nparsed,
- , parts = []
- , part = null
- , headerField
- , headerValue
- , endCalled = '';
+ parts = [],
+ part = null,
+ headerField,
+ headerValue,
+ endCalled = '';
parser.initWithBoundary(fixture.boundary);
parser.onPartBegin = function() {
@@ -43,7 +43,7 @@ Object.keys(fixtures).forEach(function(name) {
parser.onPartData = function(b, start, end) {
var str = b.toString('ascii', start, end);
- part.data += b.binarySlice(start, end);
+ part.data += b.slice(start, end);
}
parser.onEnd = function() {
@@ -77,4 +77,4 @@ Object.keys(fixtures).forEach(function(name) {
assert.ok(endCalled);
assert.deepEqual(parts, fixture.parts);
-});
+});
View
102 test/simple/test-file.js
@@ -0,0 +1,102 @@
+require('../common');
+var WriteStreamStub = GENTLY.stub('fs', 'WriteStream');
+
+var File = require('formidable/file'),
+ EventEmitter = require('events').EventEmitter,
+ file,
+ gently;
+
+function test(test) {
+ gently = new Gently();
+ file = new File();
+ test();
+ gently.verify(test.name);
+}
+
+test(function constructor() {
+ assert.ok(file instanceof EventEmitter);
+ assert.strictEqual(file.length, 0);
+ assert.strictEqual(file.path, null);
+ assert.strictEqual(file.filename, null);
+ assert.strictEqual(file.mime, null);
+
+ assert.strictEqual(file._writeStream, null);
+
+ (function testSetProperties() {
+ var file2 = new File({foo: 'bar'});
+ assert.equal(file2.foo, 'bar');
+ })();
+});
+
+test(function open() {
+ var WRITE_STREAM;
+ file.path = '/foo';
+
+ gently.expect(WriteStreamStub, 'new', function (path) {
+ WRITE_STREAM = this;
+ assert.strictEqual(path, file.path);
+ });
+
+ file.open();
+ assert.strictEqual(file._writeStream, WRITE_STREAM);
+});
+
+test(function write() {
+ var BUFFER = {length: 10},
+ CB_STUB,
+ CB = function() {
+ CB_STUB.apply(this, arguments);
+ };
+
+ file._writeStream = {};
+
+ gently.expect(file._writeStream, 'write', function (buffer, cb) {
+ assert.strictEqual(buffer, BUFFER);
+
+ gently.expect(file, 'emit', function (event, bytesWritten) {
+ assert.equal(event, 'progress');
+ assert.equal(bytesWritten, file.length);
+ });
+
+ CB_STUB = gently.expect(function writeCb() {
+ assert.equal(file.length, 10);
+ });
+
+ cb();
+
+ gently.expect(file, 'emit', function (event, bytesWritten) {
+ assert.equal(event, 'progress');
+ assert.equal(bytesWritten, file.length);
+ });
+
+ CB_STUB = gently.expect(function writeCb() {
+ assert.equal(file.length, 20);
+ });
+
+ cb();
+ });
+
+ file.write(BUFFER, CB);
+});
+
+test(function end() {
+ var CB_STUB,
+ CB = function() {
+ CB_STUB.apply(this, arguments);
+ };
+
+ file._writeStream = {};
+
+ gently.expect(file._writeStream, 'end', function (cb) {
+ gently.expect(file, 'emit', function (event) {
+ assert.equal(event, 'end');
+ });
+
+ CB_STUB = gently.expect(function endCb() {
+ });
+
+ cb();
+ });
+
+ file.end(CB);
+});
View
126 test/simple/test-incoming-form.js
@@ -1,17 +1,18 @@
require('../common');
-var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser')
- , QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser')
- , EventEmitterStub = GENTLY.stub('events', 'EventEmitter')
- , WriteStreamStub = GENTLY.stub('fs', 'WriteStream');
-
-var IncomingForm = require('formidable/incoming_form').IncomingForm
- , events = require('events')
- , fs = require('fs')
- , path = require('path')
- , Buffer = require('buffer').Buffer
- , fixtures = require('../fixture/multipart')
- , form
- , gently;
+var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser'),
+ QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser'),
+ EventEmitterStub = GENTLY.stub('events', 'EventEmitter'),
+ FileStub = GENTLY.stub('./file');
+
+var formidable = require('formidable'),
+ IncomingForm = formidable.IncomingForm,
+ events = require('events'),
+ fs = require('fs'),
+ path = require('path'),
+ Buffer = require('buffer').Buffer,
+ fixtures = require('../fixture/multipart'),
+ form,
+ gently;
function test(test) {
gently = new Gently();
@@ -37,6 +38,18 @@ test(function constructor() {
assert.strictEqual(form._fieldsSize, 0);
assert.ok(form instanceof EventEmitterStub);
assert.equal(form.constructor.name, 'IncomingForm');
+
+ (function testSimpleConstructor() {
+ gently.expect(EventEmitterStub, 'call');
+ var form = IncomingForm();
+ assert.ok(form instanceof IncomingForm);
+ })();
+
+ (function testSimpleConstructorShortcut() {
+ gently.expect(EventEmitterStub, 'call');
+ var form = formidable();
+ assert.ok(form instanceof IncomingForm);
+ })();
});
test(function parse() {
@@ -48,7 +61,7 @@ test(function parse() {
});
var events = ['error', 'data', 'end'];
- gently.expect(REQ, 'addListener', events.length, function(event, fn) {
+ gently.expect(REQ, 'on', events.length, function(event, fn) {
assert.equal(event, events.shift());
emit[event] = fn;
return this;
@@ -117,7 +130,7 @@ test(function parse() {
assert.strictEqual(form.resume(), false);
})();
-
+
(function testEmitError() {
var ERR = new Error('something bad happened');
gently.expect(form, '_error',function(err) {
@@ -125,7 +138,7 @@ test(function parse() {
});
emit.error(ERR);
})();
-
+
(function testEmitData() {
var BUFFER = [1, 2, 3];
gently.expect(form, 'write', function(buffer) {
@@ -160,57 +173,56 @@ test(function parse() {
emit.end();
})();
})();
-
(function testWithCallback() {
gently.expect(EventEmitterStub, 'call');
- var form = new IncomingForm()
- , REQ = {headers: {}}
- , parseCalled = 0;
-
+ var form = new IncomingForm(),
+ REQ = {headers: {}},
+ parseCalled = 0;
+
gently.expect(form, 'writeHeaders');
- gently.expect(REQ, 'addListener', 3, function() {
+ gently.expect(REQ, 'on', 3, function() {
return this;
});
-
- gently.expect(form, 'addListener', 4, function(event, fn) {
+
+ gently.expect(form, 'on', 4, function(event, fn) {
if (event == 'field') {
fn('field1', 'foo');
fn('field1', 'bar');
fn('field2', 'nice');
}
-
+
if (event == 'file') {
fn('file1', '1');
fn('file1', '2');
fn('file2', '3');
}
-
+
if (event == 'end') {
fn();
}
return this;
});
-
+
form.parse(REQ, gently.expect(function parseCbOk(err, fields, files) {
assert.deepEqual(fields, {field1: 'bar', field2: 'nice'});
assert.deepEqual(files, {file1: '2', file2: '3'});
}));
-
+
gently.expect(form, 'writeHeaders');
- gently.expect(REQ, 'addListener', 3, function() {
+ gently.expect(REQ, 'on', 3, function() {
return this;
});
-
+
var ERR = new Error('test');
- gently.expect(form, 'addListener', 3, function(event, fn) {
+ gently.expect(form, 'on', 3, function(event, fn) {
if (event == 'field') {
fn('foo', 'bar');
}
-
+
if (event == 'error') {
fn(ERR);
- gently.expect(form, 'addListener');
+ gently.expect(form, 'on');
}
return this;
});
@@ -241,8 +253,8 @@ test(function writeHeaders() {
});
test(function write() {
- var parser = {}
- , BUFFER = [1, 2, 3];
+ var parser = {},
+ BUFFER = [1, 2, 3];
form._parser = parser;
form.bytesExpected = 523423;
@@ -356,8 +368,8 @@ test(function parseContentLength() {
});
test(function _initMultipart() {
- var BOUNDARY = '123'
- , PARSER;
+ var BOUNDARY = '123',
+ PARSER;
gently.expect(MultipartParserStub, 'new', function() {
PARSER = this;
@@ -422,12 +434,12 @@ test(function _initMultipart() {
gently.expect(form, 'onPart', function(part) {
assert.deepEqual
( part.headers
- , { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sunset.jpg"'
+ , { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'
, 'content-type': 'text/plain'
}
);
assert.equal(part.name, 'field2');
- assert.equal(part.filename, 'Sunset.jpg');
+ assert.equal(part.filename, 'Sun"et.jpg');
assert.equal(part.mime, 'text/plain');
gently.expect(part, 'emit', function(event, b) {
@@ -442,7 +454,7 @@ 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.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'), 0, 85);
PARSER.onHeaderEnd();
PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12);
PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10);
@@ -568,9 +580,9 @@ test(function handlePart() {
})();
(function testFilePart() {
- var PART = new events.EventEmitter()
- , FILE = new events.EventEmitter()
- , PATH = '/foo/bar';
+ var PART = new events.EventEmitter(),
+ FILE = new events.EventEmitter(),
+ PATH = '/foo/bar';
PART.name = 'my_file';
PART.filename = 'sweet.txt';
@@ -581,9 +593,19 @@ test(function handlePart() {
return PATH;
});
- gently.expect(WriteStreamStub, 'new', function(path) {
- assert.equal(path, PATH);
+ gently.expect(FileStub, 'new', function(properties) {
+ assert.equal(properties.path, PATH);
+ assert.equal(properties.filename, PART.filename);
+ assert.equal(properties.mime, PART.mime);
FILE = this;
+
+ gently.expect(form, 'emit', function (event, field, file) {
+ assert.equal(event, 'fileBegin');
+ assert.strictEqual(field, PART.name);
+ assert.strictEqual(file, FILE);
+ });
+
+ gently.expect(FILE, 'open');
});
form.handlePart(PART);
@@ -603,13 +625,7 @@ test(function handlePart() {
gently.expect(FILE, 'end', function(cb) {
gently.expect(form, 'emit', function(event, field, file) {
assert.equal(event, 'file');
- assert.deepEqual
- ( file
- , { path: FILE.path
- , filename: PART.filename
- , mime: PART.mime
- }
- );
+ assert.strictEqual(file, FILE);
});
gently.expect(form, '_maybeEnd');
@@ -641,8 +657,8 @@ test(function _uploadPath() {
(function testFileExtension() {
form.keepExtensions = true;
- var FILENAME = 'foo.jpg'
- , EXT = '.bar';
+ var FILENAME = 'foo.jpg',
+ EXT = '.bar';
gently.expect(GENTLY.hijacked.path, 'extname', function(filename) {
assert.equal(filename, FILENAME);
@@ -673,4 +689,4 @@ test(function _maybeEnd() {
form.ended = true;
form._flushing = 0;
form._maybeEnd();
-});
+});
View
16 test/simple/test-multipart-parser.js
@@ -1,9 +1,9 @@
require('../common');
-var multipartParser = require('formidable/multipart_parser')
- , MultipartParser = multipartParser.MultipartParser
- , events = require('events')
- , Buffer = require('buffer').Buffer
- , parser;
+var multipartParser = require('formidable/multipart_parser'),
+ MultipartParser = multipartParser.MultipartParser,
+ events = require('events'),
+ Buffer = require('buffer').Buffer,
+ parser;
function test(test) {
parser = new MultipartParser();
@@ -30,8 +30,8 @@ test(function initWithBoundary() {
});
test(function parserError() {
- var boundary = 'abc'
- , buffer = new Buffer(5);
+ var boundary = 'abc',
+ buffer = new Buffer(5);
parser.initWithBoundary(boundary);
buffer.write('--ad', 'ascii', 0);
@@ -47,4 +47,4 @@ test(function end() {
parser.state = multipartParser.END;
assert.strictEqual(parser.end(), undefined);
})();
-});
+});
View
10 test/simple/test-querystring-parser.js
@@ -1,8 +1,8 @@
require('../common');
-var QuerystringParser = require('formidable/querystring_parser').QuerystringParser
- , Buffer = require('buffer').Buffer
- , gently
- , parser;
+var QuerystringParser = require('formidable/querystring_parser').QuerystringParser,
+ Buffer = require('buffer').Buffer,
+ gently,
+ parser;
function test(test) {
gently = new Gently();
@@ -42,4 +42,4 @@ test(function end() {
parser.buffer = 'my buffer';
parser.end();
assert.equal(parser.buffer, '');
-});
+});
View
74 test/system/test-multi-video-upload.js
@@ -1,35 +1,51 @@
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();
+var BOUNDARY = '---------------------------10102754414578508781458777923',
+ FIXTURE = TEST_FIXTURES+'/multi_video.upload',
+ fs = require('fs'),
+ util = require('formidable/util'),
+ http = require('http'),
+ formidable = require('formidable'),
+ server = http.createServer();
-server.addListener('request', function(req, res) {
- var form = new formidable.IncomingForm()
- , files = {};
+server.on('request', function(req, res) {
+ var form = new formidable.IncomingForm(),
+ uploads = {};
form.uploadDir = TEST_TMP;
form.parse(req);
form
- .addListener('field', function(field, value) {
+ .on('fileBegin', function(field, file) {
+ assert.equal(field, 'upload');
+
+ var tracker = {file: file, progress: [], ended: false};
+ uploads[file.filename] = tracker;
+ file
+ .on('progress', function(bytesReceived) {
+ tracker.progress.push(bytesReceived);
+ assert.equal(bytesReceived, file.length);
+ })
+ .on('end', function() {
+ tracker.ended = true;
+ });
+ })
+ .on('field', function(field, value) {
assert.equal(field, 'title');
assert.equal(value, '');
})
- .addListener('file', function(field, file) {
+ .on('file', function(field, file) {
assert.equal(field, 'upload');
- files[file.filename] = true;
+ assert.strictEqual(uploads[file.filename].file, file);
})
- .addListener('end', function() {
- assert.deepEqual
- ( files
- , { 'shortest_video.flv': true
- , 'shortest_video.mp4' :true
- }
- );
+ .on('end', function() {
+ assert.ok(uploads['shortest_video.flv']);
+ assert.ok(uploads['shortest_video.flv'].ended);
+ assert.ok(uploads['shortest_video.flv'].progress.length > 3);
+ assert.equal(uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.length);
+ assert.ok(uploads['shortest_video.mp4']);
+ assert.ok(uploads['shortest_video.mp4'].ended);
+ assert.ok(uploads['shortest_video.mp4'].progress.length > 3);
+
server.close();
res.writeHead(200);
res.end('good');
@@ -37,16 +53,20 @@ server.addListener('request', function(req, res) {
});
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);
+ var client = http.createClient(TEST_PORT),
+ stat = fs.statSync(FIXTURE),
+ headers = {
+ 'content-type': 'multipart/form-data; boundary='+BOUNDARY,
+ 'content-length': stat.size,
+ }
+ request = client.request('POST', '/', headers),
+ fixture = new fs.ReadStream(FIXTURE);
fixture
- .addListener('data', function(b) {;
+ .on('data', function(b) {
request.write(b);
})
- .addListener('end', function() {
+ .on('end', function() {
request.end();
});
-});
+});

Showing you all comments on commits in this comparison.

@dvv

Please, remove the trailing comma here -- json doesn't allow pending commas. W/o this the package cannot be require()d by nodules

TIA,
--Vladimir

@dvv

Please, append comma here

--Vladimir

@mscdex

Shouldn't there be a comma at the end here? :-)

@gf3

Hehehe

@dvv

This is just readme :)

@aheckmann

Comma first! :p

@felixge
Owner

Well, JSON is fucking stupid for not allowing a trailing comma. But since V8 doesn't care for JavaScript itself it's usually not a problem.

Anyway - I agree, comma-first was nice, but since nobody else seems to embrace it I'd rather go with an accepted convention for my modules.

@aheckmann

Twice in the past two weeks I've been bitten by this exact issue in older code. Yeah, it'd be great if more people began using it. I'm glad I've switched.

@aheckmann

whoop!

Something went wrong with that request. Please try again.