[Pkg-javascript-commits] [node-multiparty] 01/13: Imported Upstream version 2.2.0

Andrew Kelley andrewrk-guest at moszumanska.debian.org
Sat Jul 5 00:49:17 UTC 2014


This is an automated email from the git hooks/post-receive script.

andrewrk-guest pushed a commit to branch master
in repository node-multiparty.

commit b552c95546266506328aa92c3c179ef9a51be345
Author: Jérémy Lal <kapouer at melix.org>
Date:   Thu Oct 17 23:16:40 2013 +0200

    Imported Upstream version 2.2.0
---
 .gitignore                                         |   1 +
 .jshintrc                                          |  70 +++
 .travis.yml                                        |   6 +
 CHANGELOG.md                                       | 191 +++++++
 LICENSE                                            |   7 +
 README.md                                          | 177 ++++++
 examples/azureblobstorage.js                       |  41 ++
 examples/s3.js                                     |  74 +++
 examples/upload.js                                 |  37 ++
 index.js                                           | 618 +++++++++++++++++++++
 package.json                                       |  35 ++
 test/bench-multipart-parser.js                     |  76 +++
 test/fixture/file/beta-sticker-1.png               | Bin 0 -> 1660 bytes
 test/fixture/file/binaryfile.tar.gz                | Bin 0 -> 301 bytes
 test/fixture/file/blank.gif                        | Bin 0 -> 49 bytes
 test/fixture/file/funkyfilename.txt                |   1 +
 test/fixture/file/menu_separator.png               | Bin 0 -> 931 bytes
 test/fixture/file/pf1y5.png                        | Bin 0 -> 768323 bytes
 test/fixture/file/plain.txt                        |   1 +
 test/fixture/http/encoding/beta-sticker-1.png.http |  12 +
 test/fixture/http/encoding/binaryfile.tar.gz.http  |  12 +
 test/fixture/http/encoding/blank.gif.http          |  12 +
 test/fixture/http/encoding/menu_seperator.png.http |  12 +
 test/fixture/http/encoding/pf1y5.png.http          | Bin 0 -> 769113 bytes
 test/fixture/http/encoding/plain.txt.http          |  13 +
 test/fixture/http/no-filename/filename-name.http   |  13 +
 test/fixture/http/no-filename/generic.http         |  13 +
 test/fixture/http/preamble/crlf.http               |  13 +
 test/fixture/http/preamble/preamble.http           |  13 +
 .../fixture/http/special-chars-in-filename/info.md |   3 +
 .../special-chars-in-filename/osx-chrome-13.http   |  26 +
 .../special-chars-in-filename/osx-firefox-3.6.http |  24 +
 .../special-chars-in-filename/osx-safari-5.http    |  23 +
 .../special-chars-in-filename/xp-chrome-12.http    |  24 +
 .../http/special-chars-in-filename/xp-ie-7.http    |  22 +
 .../http/special-chars-in-filename/xp-ie-8.http    |  22 +
 .../special-chars-in-filename/xp-safari-5.http     |  22 +
 .../fixture/http/workarounds/missing-hyphens1.http |  12 +
 .../fixture/http/workarounds/missing-hyphens2.http |  12 +
 test/fixture/js/encoding.js                        |  69 +++
 test/fixture/js/no-filename.js                     |   9 +
 test/fixture/js/preamble.js                        |   9 +
 test/fixture/js/special-chars-in-filename.js       |  30 +
 test/fixture/js/workarounds.js                     |   8 +
 test/fixture/multi_video.upload                    | Bin 0 -> 1953781 bytes
 test/fixture/multipart.js                          |  72 +++
 test/record.js                                     |  47 ++
 test/standalone/test-connection-aborted.js         |  27 +
 test/standalone/test-content-transfer-encoding.js  |  52 ++
 test/standalone/test-invalid.js                    |  35 ++
 test/standalone/test-issue-15.js                   |  88 +++
 test/standalone/test-issue-19.js                   |  44 ++
 test/standalone/test-issue-21.js                   |  66 +++
 test/standalone/test-issue-4.js                    |  51 ++
 test/standalone/test-issue-46.js                   |  49 ++
 test/standalone/test-issue-5.js                    |  39 ++
 test/test.js                                       | 117 ++++
 57 files changed, 2450 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..a93b50c
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,70 @@
+{
+    // Settings
+    "passfail"      : false,  // Stop on first error.
+    "maxerr"        : 100,    // Maximum errors before stopping.
+
+
+    // Predefined globals whom JSHint will ignore.
+    "browser"       : false,   // Standard browser globals e.g. `window`, `document`.
+
+    "node"          : true,
+    "rhino"         : false,
+    "couch"         : false,
+    "wsh"           : false,   // Windows Scripting Host.
+
+    "jquery"        : false,
+    "prototypejs"   : false,
+    "mootools"      : false,
+    "dojo"          : false,
+
+
+    "predef"     : [
+      "describe", "it", "before", "after"
+    ],
+
+    // Development.
+    "debug"         : true,   // Allow debugger statements e.g. browser breakpoints.
+    "devel"         : true,   // Allow development statements e.g. `console.log();`.
+
+
+    // EcmaScript 5.
+    "es5"           : true,   // Allow EcmaScript 5 syntax.
+    "strict"        : false,  // Require `use strict` pragma in every file.
+    "globalstrict"  : true,   // Allow global "use strict" (also enables 'strict').
+
+
+    // The Good Parts.
+    "asi"           : true,  // Tolerate Automatic Semicolon Insertion (no semicolons).
+    "laxbreak"      : false,   // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
+    "laxcomma"      : true,
+    "bitwise"       : false,   // Prohibit bitwise operators (&, |, ^, etc.).
+    "boss"          : true,  // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
+    "curly"         : false,  // Require {} for every new block or scope.
+    "eqeqeq"        : true,   // Require triple equals i.e. `===`.
+    "eqnull"        : true,  // Tolerate use of `== null`.
+    "evil"          : false,  // Tolerate use of `eval`.
+    "expr"          : false,  // Tolerate `ExpressionStatement` as Programs.
+    "forin"         : false,  // Prohibt `for in` loops without `hasOwnProperty`.
+    "immed"         : true,   // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
+    "latedef"       : false,   // Prohibit variable use before definition.
+    "loopfunc"      : false,  // Allow functions to be defined within loops.
+    "noarg"         : true,   // Prohibit use of `arguments.caller` and `arguments.callee`.
+    "regexp"        : false,   // Prohibit `.` and `[^...]` in regular expressions.
+    "regexdash"     : false,  // Tolerate unescaped last dash i.e. `[-...]`.
+    "scripturl"     : false,   // Tolerate script-targeted URLs.
+    "shadow"        : false,  // Allows re-define variables later in code e.g. `var x=1; x=2;`.
+    "supernew"      : false,  // Tolerate `new function () { ... };` and `new Object;`.
+    "undef"         : true,   // Require all non-global variables be declared before they are used.
+
+
+    // Persone styling prefrences.
+    "newcap"        : true,   // Require capitalization of all constructor functions e.g. `new F()`.
+    "noempty"       : true,   // Prohibit use of empty blocks.
+    "nonew"         : true,   // Prohibit use of constructors for side-effects.
+    "nomen"         : false,  // Prohibit use of initial or trailing underbars in names.
+    "onevar"        : false,  // Allow only one `var` statement per function.
+    "plusplus"      : false,  // Prohibit use of `++` & `--`.
+    "sub"           : false,   // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
+    "trailing"      : true,   // Prohibit trailing whitespaces.
+    "white"         : false   // Check against strict whitespace and indentation rules.
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b1fc4b0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+before_script:
+  - ulimit -n 500
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ea54d9a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,191 @@
+### 2.2.0
+
+ * additional callback API to support multiple files with same field name
+ * fix assertion crash when max field count is exceeded
+ * fix assertion crash when client aborts an invalid request
+ * (>=v0.10 only) unpipe the request when an error occurs to save resources.
+ * update readable-stream to ~1.1.9
+ * fix assertion crash when EMFILE occurrs
+ * (no more assertions - only 'error' events)
+
+### 2.1.9
+
+ * relax content-type detection regex. (thanks amitaibu)
+
+### 2.1.8
+
+ * replace deprecated Buffer.write(). (thanks hueniverse)
+
+### 2.1.7
+
+ * add repository field to package.json
+
+### 2.1.6
+
+ * expose `hash` as an option to `Form`. (thanks wookiehangover)
+
+### 2.1.5
+
+ * fix possible 'close' event before all temp files are done
+
+### 2.1.4
+
+ * fix crash for invalid requests
+
+### 2.1.3
+
+ * add `file.size`
+
+### 2.1.2
+
+ * proper backpressure support
+ * update s3 example
+
+### 2.1.1
+
+ * fix uploads larger than 2KB
+ * fix both s3 and upload example
+ * add part.byteCount and part.byteOffset
+
+### 2.1.0 (recalled)
+
+ * Complete rewrite. See README for changes and new API.
+
+### v1.0.13
+
+* Only update hash if update method exists (Sven Lito)
+* According to travis v0.10 needs to go quoted (Sven Lito)
+* Bumping build node versions (Sven Lito)
+* Additional fix for empty requests (Eugene Girshov)
+* Change the default to 1000, to match the new Node behaviour. (OrangeDog)
+* Add ability to control maxKeys in the querystring parser. (OrangeDog)
+* Adjust test case to work with node 0.9.x (Eugene Girshov)
+* Update package.json (Sven Lito)
+* Path adjustment according to eb4468b (Markus Ast)
+
+### v1.0.12
+
+* Emit error on aborted connections (Eugene Girshov)
+* Add support for empty requests (Eugene Girshov)
+* Fix name/filename handling in Content-Disposition (jesperp)
+* Tolerate malformed closing boundary in multipart (Eugene Girshov)
+* Ignore preamble in multipart messages (Eugene Girshov)
+* Add support for application/json (Mike Frey, Carlos Rodriguez)
+* Add support for Base64 encoding (Elmer Bulthuis)
+* Add File#toJSON (TJ Holowaychuk)
+* Remove support for Node.js 0.4 & 0.6 (Andrew Kelley)
+* Documentation improvements (Sven Lito, Andre Azevedo)
+* Add support for application/octet-stream (Ion Lupascu, Chris Scribner)
+* Use os.tmpDir() to get tmp directory (Andrew Kelley)
+* Improve package.json (Andrew Kelley, Sven Lito)
+* Fix benchmark script (Andrew Kelley)
+* Fix scope issue in incoming_forms (Sven Lito)
+* Fix file handle leak on error (OrangeDog)
+
+### v1.0.11
+
+* Calculate checksums for incoming files (sreuter)
+* Add definition parameters to "IncomingForm" as an argument (Math-)
+
+### v1.0.10
+
+* Make parts to be proper Streams (Matt Robenolt)
+
+### v1.0.9
+
+* Emit progress when content length header parsed (Tim Koschützki)
+* Fix Readme syntax due to GitHub changes (goob)
+* Replace references to old 'sys' module in Readme with 'util' (Peter Sugihara)
+
+### v1.0.8
+
+* Strip potentially unsafe characters when using `keepExtensions: true`.
+* Switch to utest / urun for testing
+* Add travis build
+
+### v1.0.7
+
+* Remove file from package that was causing problems when installing on windows. (#102)
+* Fix typos in Readme (Jason Davies).
+
+### v1.0.6
+
+* Do not default to the default to the field name for file uploads where
+  filename="".
+
+### v1.0.5
+
+* Support filename="" in multipart parts
+* Explain unexpected end() errors in parser better
+
+**Note:** Starting with this version, formidable emits 'file' events for empty
+file input fields. Previously those were incorrectly emitted as regular file
+input fields with value = "".
+
+### v1.0.4
+
+* Detect a good default tmp directory regardless of platform. (#88)
+
+### v1.0.3
+
+* Fix problems with utf8 characters (#84) / semicolons in filenames (#58)
+* Small performance improvements
+* New test suite and fixture system
+
+### v1.0.2
+
+* Exclude node\_modules folder from git
+* Implement new `'aborted'` event
+* Fix files in example folder to work with recent node versions
+* Make gently a devDependency
+
+[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2)
+
+### v1.0.1
+
+* Fix package.json to refer to proper main directory. (#68, Dean Landolt)
+
+[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1)
+
+### v1.0.0
+
+* Add support for multipart boundaries that are quoted strings. (Jeff Craig)
+
+This marks the beginning of development on version 2.0 which will include
+several architectural improvements.
+
+[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0)
+
+### v0.9.11
+
+* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki)
+* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class
+
+**Important:** The old property names of the File class will be removed in a
+future release.
+
+[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11)
+
+### Older releases
+
+These releases were done before starting to maintain the above Changelog:
+
+* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10)
+* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9)
+* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8)
+* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7)
+* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6)
+* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5)
+* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4)
+* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3)
+* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2)
+* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
+* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8488a40
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Copyright (C) 2011-2013 Felix Geisendörfer, Andrew Kelley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f149ac0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,177 @@
+[![Build Status](https://travis-ci.org/superjoe30/node-multiparty.png?branch=master)](https://travis-ci.org/superjoe30/node-multiparty)
+# multiparty
+
+Parse http requests with content-type `multipart/form-data`, also known as file uploads.
+
+See also [busboy](https://github.com/mscdex/busboy) - a
+[faster](https://github.com/mscdex/dicer/wiki/Benchmarks) alternative
+which may be worth looking into.
+
+### Why the fork?
+
+ * This module uses the Node.js v0.10 streams properly, *even in Node.js v0.8*
+ * It will not create a temp file for you unless you want it to.
+ * Counts bytes and does math to help you figure out the `Content-Length` of
+   each part.
+ * You can easily stream uploads to s3 with
+   [knox](https://github.com/LearnBoost/knox), for [example](examples/s3.js).
+ * Less bugs. This code is simpler, has all deprecated functionality removed,
+   has cleaner tests, and does not try to do anything beyond multipart stream
+   parsing.
+
+## Installation
+
+```
+npm install multiparty
+```
+
+## Usage
+
+ * See [examples](examples).
+
+Parse an incoming `multipart/form-data` request.
+
+```js
+var multiparty = require('multiparty')
+  , http = require('http')
+  , util = require('util')
+
+http.createServer(function(req, res) {
+  if (req.url === '/upload' && req.method === 'POST') {
+    // parse a file upload
+    var form = new multiparty.Form();
+
+    form.parse(req, function(err, fields, files) {
+      res.writeHead(200, {'content-type': 'text/plain'});
+      res.write('received upload:\n\n');
+      res.end(util.inspect({fields: fields, files: files}));
+    });
+
+    return;
+  }
+
+  // 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>'
+  );
+}).listen(8080);
+```
+
+## API
+
+### multiparty.Form
+```js
+var form = new multiparty.Form(options)
+```
+Creates a new form. Options:
+
+ * `encoding` - sets encoding for the incoming form fields. Defaults to `utf8`.
+ * `maxFieldSize` - Limits the amount of memory a field (not a file) can
+   allocate in bytes. If this value is exceeded, an `error` event is emitted.
+   The default size is 2MB.
+ * `maxFields` - Limits the number of fields that will be parsed before
+   emitting an `error` event. A file counts as a field in this case.
+   Defaults to 1000.
+ * `autoFields` - Enables `field` events. This is automatically set to `true`
+   if you add a `field` listener.
+ * `autoFiles` - Enables `file` events. This is automatically set to `true`
+   if you add a `file` listener.
+ * `uploadDir` - Only relevant when `autoFiles` is `true`. The directory for
+   placing file uploads in. You can move them later using `fs.rename()`.
+   Defaults to `os.tmpDir()`.
+ * `hash` - Only relevant when `autoFiles` is `true`. If you want checksums
+   calculated for incoming files, set this to either `sha1` or `md5`.
+   Defaults to off.
+
+#### form.parse(request, [cb])
+
+Parses an incoming node.js `request` containing form data. If `cb` is
+provided, `autoFields` and `autoFiles` are set to `true` and all fields and
+files are collected and passed to the callback:
+
+```js
+form.parse(req, function(err, fieldsObject, filesObject, fieldsList, filesList) {
+  // ...
+});
+```
+
+It is often convenient to access a field or file by name. In this situation,
+use `fieldsObject` or `filesObject`. However sometimes, as in the case of a
+`<input type="file" multiple="multiple">` the multipart stream will contain
+multiple files of the same input name, and you are interested in all of them.
+In this case, use `filesList`.
+
+Another example is when you do not care what the field name of a file is; you
+are merely interested in a single upload. In this case, set `maxFields` to 1
+(assuming no other fields expected besides the file) and use `filesList[0]`.
+
+#### form.bytesReceived
+
+The amount of bytes received for this form so far.
+
+#### form.bytesExpected
+
+The expected number of bytes in this form.
+
+### Events
+
+#### 'error' (err)
+
+You definitely want to handle this event. If not your server *will* crash when
+users submit bogus multipart requests!
+
+#### 'part' (part)
+
+Emitted when a part is encountered in the request. `part` is a
+`ReadableStream`. It also has the following properties:
+
+ * `headers` - the headers for this part. For example, you may be interested
+   in `content-type`.
+ * `name` - the field name for this part
+ * `filename` - only if the part is an incoming file
+ * `byteOffset` - the byte offset of this part in the request body
+ * `byteCount` - assuming that this is the last part in the request,
+   this is the size of this part in bytes. You could use this, for
+   example, to set the `Content-Length` header if uploading to S3.
+   If the part had a `Content-Length` header then that value is used
+   here instead.
+
+#### 'aborted'
+
+Emitted when the request is aborted. This event will be followed shortly
+by an `error` event. In practice you do not need to handle this event.
+
+#### 'progress' (bytesReceived, bytesExpected)
+
+#### 'close'
+
+Emitted after all parts have been parsed and emitted. Not emitted if an `error`
+event is emitted. This is typically when you would send your response.
+
+#### 'file' (name, file)
+
+**By default multiparty will not touch your hard drive.** But if you add this
+listener, multiparty automatically sets `form.autoFiles` to `true` and will
+stream uploads to disk for you. 
+
+ * `name` - the field name for this file
+ * `file` - an object with these properties:
+   - `fieldName` - same as `name` - the field name for this file
+   - `originalFilename` - the filename that the user reports for the file
+   - `path` - the absolute path of the uploaded file on disk
+   - `headers` - the HTTP headers that were sent along with this file
+   - `size` - size of the file in bytes
+
+If you set the `form.hash` option, then `file` will also contain a `hash`
+property which is the checksum of the file.
+
+#### 'field' (name, value)
+
+ * `name` - field name
+ * `value` - string field value
+
diff --git a/examples/azureblobstorage.js b/examples/azureblobstorage.js
new file mode 100644
index 0000000..273c332
--- /dev/null
+++ b/examples/azureblobstorage.js
@@ -0,0 +1,41 @@
+var http = require('http')
+  , util = require('util')
+  , multiparty = require('../')
+  , azure = require('azure')
+  , PORT = process.env.PORT || 27372
+
+var 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"><br>'+
+      '<input type="submit" value="Upload">'+
+      '</form>'
+    );
+  } else if (req.url === '/upload') {
+    
+	var blobService = azure.createBlobService();
+	var form = new multiparty.Form();
+    form.on('part', function(part) {
+	    if (!part.filename) return;
+		
+		var size = part.byteCount - part.byteOffset;
+		var name = part.filename;
+		var container = 'blobContainerName';
+		
+		blobService.createBlockBlobFromStream(container, name, part, size, function(error) {
+			if (error) {
+				// error handling
+			}
+		});
+	});
+	form.parse(req);
+	
+	res.send('File uploaded successfully');
+   }
+});
+server.listen(PORT, function() {
+  console.info('listening on http://0.0.0.0:'+PORT+'/');
+});
diff --git a/examples/s3.js b/examples/s3.js
new file mode 100644
index 0000000..60617ba
--- /dev/null
+++ b/examples/s3.js
@@ -0,0 +1,74 @@
+var http = require('http')
+  , util = require('util')
+  , multiparty = require('../')
+  , knox = require('knox')
+  , Batch = require('batch')
+  , PORT = process.env.PORT || 27372
+
+var s3Client = knox.createClient({
+  secure: false,
+  key: process.env.S3_KEY,
+  secret: process.env.S3_SECRET,
+  bucket: process.env.S3_BUCKET,
+});
+
+var 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="path"><br>'+
+      '<input type="file" name="upload"><br>'+
+      '<input type="submit" value="Upload">'+
+      '</form>'
+    );
+  } else if (req.url === '/upload') {
+    var headers = {
+      'x-amz-acl': 'public-read',
+    };
+    var form = new multiparty.Form();
+    var batch = new Batch();
+    batch.push(function(cb) {
+      form.on('field', function(name, value) {
+        if (name === 'path') {
+          var destPath = value;
+          if (destPath[0] !== '/') destPath = '/' + destPath;
+          cb(null, destPath);
+        }
+      });
+    });
+    batch.push(function(cb) {
+      form.on('part', function(part) {
+        if (! part.filename) return;
+        cb(null, part);
+      });
+    });
+    batch.end(function(err, results) {
+      if (err) throw err;
+      form.removeListener('close', onEnd);
+      var destPath = results[0]
+        , part = results[1];
+
+      headers['Content-Length'] = part.byteCount;
+      s3Client.putStream(part, destPath, headers, function(err, s3Response) {
+        if (err) throw err;
+        res.statusCode = s3Response.statusCode;
+        s3Response.pipe(res);
+        console.log("https://s3.amazonaws.com/" + process.env.S3_BUCKET + destPath);
+      });
+    });
+    form.on('close', onEnd);
+    form.parse(req);
+    
+  } else {
+    res.writeHead(404, {'content-type': 'text/plain'});
+    res.end('404');
+  }
+
+  function onEnd() {
+    throw new Error("no uploaded file");
+  }
+});
+server.listen(PORT, function() {
+  console.info('listening on http://0.0.0.0:'+PORT+'/');
+});
diff --git a/examples/upload.js b/examples/upload.js
new file mode 100644
index 0000000..5dd3926
--- /dev/null
+++ b/examples/upload.js
@@ -0,0 +1,37 @@
+var http = require('http')
+  , util = require('util')
+  , multiparty = require('../')
+  , PORT = process.env.PORT || 27372
+
+var 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>'
+    );
+  } else if (req.url === '/upload') {
+    var form = new multiparty.Form();
+
+    form.parse(req, function(err, fields, files) {
+      if (err) {
+        res.writeHead(400, {'content-type': 'text/plain'});
+        res.end("invalid request: " + err.message);
+        return;
+      }
+      res.writeHead(200, {'content-type': 'text/plain'});
+      res.write('received fields:\n\n '+util.inspect(fields));
+      res.write('\n\n');
+      res.end('received files:\n\n '+util.inspect(files));
+    });
+  } else {
+    res.writeHead(404, {'content-type': 'text/plain'});
+    res.end('404');
+  }
+});
+server.listen(PORT, function() {
+  console.info('listening on http://0.0.0.0:'+PORT+'/');
+});
diff --git a/index.js b/index.js
new file mode 100755
index 0000000..71c88ae
--- /dev/null
+++ b/index.js
@@ -0,0 +1,618 @@
+exports.Form = Form;
+
+var stream = require('readable-stream')
+  , util = require('util')
+  , fs = require('fs')
+  , crypto = require('crypto')
+  , path = require('path')
+  , os = require('os')
+  , StringDecoder = require('string_decoder').StringDecoder
+  , StreamCounter = require('stream-counter')
+
+var START = 0
+  , START_BOUNDARY = 1
+  , HEADER_FIELD_START = 2
+  , HEADER_FIELD = 3
+  , HEADER_VALUE_START = 4
+  , HEADER_VALUE = 5
+  , HEADER_VALUE_ALMOST_DONE = 6
+  , HEADERS_ALMOST_DONE = 7
+  , PART_DATA_START = 8
+  , PART_DATA = 9
+  , PART_END = 10
+  , END = 11
+
+  , LF = 10
+  , CR = 13
+  , SPACE = 32
+  , HYPHEN = 45
+  , COLON = 58
+  , A = 97
+  , Z = 122
+
+var CONTENT_TYPE_RE = /^multipart\/(form-data|related);\s*boundary=(?:"([^"]+)"|([^;]+))$/i;
+var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16}).*/;
+var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
+
+util.inherits(Form, stream.Writable);
+function Form(options) {
+  var self = this;
+  stream.Writable.call(self);
+
+  options = options || {};
+
+  self.error = null;
+  self.finished = false;
+
+  self.autoFields = !!options.autoFields;
+  self.autoFiles = !!options.autoFields;
+
+  self.maxFields = options.maxFields || 1000;
+  self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024;
+  self.uploadDir = options.uploadDir || os.tmpDir();
+  self.encoding = options.encoding || 'utf8';
+  self.hash = options.hash || false;
+
+  self.bytesReceived = 0;
+  self.bytesExpected = null;
+
+  self.openedFiles = [];
+  self.totalFieldSize = 0;
+  self.totalFieldCount = 0;
+  self.flushing = 0;
+
+  self.backpressure = false;
+  self.writeCbs = [];
+
+  if (options.boundary) setUpParser(self, options.boundary);
+
+  self.on('newListener', function(eventName) {
+    if (eventName === 'file') {
+      self.autoFiles = true;
+    } else if (eventName === 'field') {
+      self.autoFields = true;
+    }
+  });
+}
+
+Form.prototype.parse = function(req, cb) {
+  var self = this;
+
+  // if the user supplies a callback, this implies autoFields and autoFiles
+  if (cb) {
+    self.autoFields = true;
+    self.autoFiles = true;
+  }
+
+  self.handleError = handleError;
+  self.bytesExpected = getBytesExpected(req.headers);
+
+  req.on('error', handleError);
+  req.on('aborted', onReqAborted);
+
+  var contentType = req.headers['content-type'];
+  if (!contentType) {
+    handleError(new Error('missing content-type header'));
+    return;
+  }
+
+  var m = contentType.match(CONTENT_TYPE_RE);
+  if (!m) {
+    handleError(new Error('unrecognized content-type: ' + contentType));
+    return;
+  }
+  var boundary = m[2] || m[3];
+  setUpParser(self, boundary);
+  req.pipe(self);
+
+  if (cb) {
+    var fieldsTable = {};
+    var filesTable = {};
+    var fieldsList = [];
+    var filesList = [];
+    self.on('error', function(err) {
+      cb(err);
+    });
+    self.on('field', function(name, value) {
+      fieldsTable[name] = value;
+      fieldsList.push({name: name, value: value});
+    });
+    self.on('file', function(name, file) {
+      filesTable[name] = file;
+      filesList.push(file);
+    });
+    self.on('close', function() {
+      cb(null, fieldsTable, filesTable, fieldsList, filesList);
+    });
+  }
+
+  function onReqAborted() {
+    self.emit('aborted');
+    handleError(new Error("Request aborted"));
+  }
+
+  function handleError(err) {
+    var first = !self.error;
+    if (first) {
+      self.error = err;
+      req.removeListener('aborted', onReqAborted);
+
+      // welp. 0.8 doesn't support unpipe, too bad so sad.
+      // let's drop support for 0.8 soon.
+      if (req.unpipe) {
+        req.unpipe(self);
+      }
+    }
+
+    self.openedFiles.forEach(function(file) {
+      file.ws.destroy();
+      fs.unlink(file.path, function(err) {
+        // this is already an error condition, ignore 2nd error
+      });
+    });
+    self.openedFiles = [];
+
+    if (first) {
+      self.emit('error', err);
+    }
+  }
+
+};
+
+Form.prototype._write = function(buffer, encoding, cb) {
+  var self = this
+    , i = 0
+    , len = buffer.length
+    , prevIndex = self.index
+    , index = self.index
+    , state = self.state
+    , lookbehind = self.lookbehind
+    , boundary = self.boundary
+    , boundaryChars = self.boundaryChars
+    , boundaryLength = self.boundary.length
+    , boundaryEnd = boundaryLength - 1
+    , bufferLength = buffer.length
+    , c
+    , cl
+
+  for (i = 0; i < len; i++) {
+    c = buffer[i];
+    switch (state) {
+      case START:
+        index = 0;
+        state = START_BOUNDARY;
+        /* falls through */
+      case START_BOUNDARY:
+        if (index === boundaryLength - 2) {
+          if (c !== CR) return self.handleError(new Error("Expected CR Received " + c));
+          index++;
+          break;
+        } else if (index === boundaryLength - 1) {
+          if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
+          index = 0;
+          self.onParsePartBegin();
+          state = HEADER_FIELD_START;
+          break;
+        }
+
+        if (c !== boundary[index+2]) index = -2;
+        if (c === boundary[index+2]) index++;
+        break;
+      case HEADER_FIELD_START:
+        state = HEADER_FIELD;
+        self.headerFieldMark = i;
+        index = 0;
+        /* falls through */
+      case HEADER_FIELD:
+        if (c === CR) {
+          self.headerFieldMark = null;
+          state = HEADERS_ALMOST_DONE;
+          break;
+        }
+
+        index++;
+        if (c === HYPHEN) break;
+
+        if (c === COLON) {
+          if (index === 1) {
+            // empty header field
+            self.handleError(new Error("Empty header field"));
+            return;
+          }
+          self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
+          self.headerFieldMark = null;
+          state = HEADER_VALUE_START;
+          break;
+        }
+
+        cl = lower(c);
+        if (cl < A || cl > Z) {
+          self.handleError(new Error("Expected alphabetic character, received " + c));
+          return;
+        }
+        break;
+      case HEADER_VALUE_START:
+        if (c === SPACE) break;
+
+        self.headerValueMark = i;
+        state = HEADER_VALUE;
+        /* falls through */
+      case HEADER_VALUE:
+        if (c === CR) {
+          self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
+          self.headerValueMark = null;
+          self.onParseHeaderEnd();
+          state = HEADER_VALUE_ALMOST_DONE;
+        }
+        break;
+      case HEADER_VALUE_ALMOST_DONE:
+        if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
+        state = HEADER_FIELD_START;
+        break;
+      case HEADERS_ALMOST_DONE:
+        if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
+        var err = self.onParseHeadersEnd(i + 1);
+        if (err) return self.handleError(err);
+        state = PART_DATA_START;
+        break;
+      case PART_DATA_START:
+        state = PART_DATA;
+        self.partDataMark = i;
+        /* falls through */
+      case PART_DATA:
+        prevIndex = index;
+
+        if (index === 0) {
+          // boyer-moore derrived algorithm to safely skip non-boundary data
+          i += boundaryEnd;
+          while (i < bufferLength && !(buffer[i] in boundaryChars)) {
+            i += boundaryLength;
+          }
+          i -= boundaryEnd;
+          c = buffer[i];
+        }
+
+        if (index < boundaryLength) {
+          if (boundary[index] === c) {
+            if (index === 0) {
+              self.onParsePartData(buffer.slice(self.partDataMark, i));
+              self.partDataMark = null;
+            }
+            index++;
+          } else {
+            index = 0;
+          }
+        } else if (index === boundaryLength) {
+          index++;
+          if (c === CR) {
+            // CR = part boundary
+            self.partBoundaryFlag = true;
+          } else if (c === HYPHEN) {
+            // HYPHEN = end boundary
+            self.lastBoundaryFlag = true;
+          } else {
+            index = 0;
+          }
+        } else if (index - 1 === boundaryLength)  {
+          if (self.partBoundaryFlag) {
+            index = 0;
+            if (c === LF) {
+              self.partBoundaryFlag = false;
+              self.onParsePartEnd();
+              self.onParsePartBegin();
+              state = HEADER_FIELD_START;
+              break;
+            }
+          } else if (self.lastBoundaryFlag) {
+            if (c === HYPHEN) {
+              self.onParsePartEnd();
+              self.end();
+              state = END;
+            } else {
+              index = 0;
+            }
+          } else {
+            index = 0;
+          }
+        }
+
+        if (index > 0) {
+          // when matching a possible boundary, keep a lookbehind reference
+          // in case it turns out to be a false lead
+          lookbehind[index-1] = c;
+        } else if (prevIndex > 0) {
+          // if our boundary turned out to be rubbish, the captured lookbehind
+          // belongs to partData
+          self.onParsePartData(lookbehind.slice(0, prevIndex));
+          prevIndex = 0;
+          self.partDataMark = i;
+
+          // reconsider the current character even so it interrupted the sequence
+          // it could be the beginning of a new sequence
+          i--;
+        }
+
+        break;
+      case END:
+        break;
+      default:
+        self.handleError(new Error("Parser has invalid state."));
+        return;
+    }
+  }
+
+  if (self.headerFieldMark != null) {
+    self.onParseHeaderField(buffer.slice(self.headerFieldMark));
+    self.headerFieldMark = 0;
+  }
+  if (self.headerValueMark != null) {
+    self.onParseHeaderValue(buffer.slice(self.headerValueMark));
+    self.headerValueMark = 0;
+  }
+  if (self.partDataMark != null) {
+    self.onParsePartData(buffer.slice(self.partDataMark));
+    self.partDataMark = 0;
+  }
+
+  self.index = index;
+  self.state = state;
+
+  self.bytesReceived += buffer.length;
+  self.emit('progress', self.bytesReceived, self.bytesExpected);
+
+  if (self.backpressure) {
+    self.writeCbs.push(cb);
+  } else {
+    cb();
+  }
+};
+
+Form.prototype.onParsePartBegin = function() {
+  clearPartVars(this);
+}
+
+Form.prototype.onParseHeaderField = function(b) {
+  this.headerField += this.headerFieldDecoder.write(b);
+}
+
+Form.prototype.onParseHeaderValue = function(b) {
+  this.headerValue += this.headerValueDecoder.write(b);
+}
+
+Form.prototype.onParseHeaderEnd = function() {
+  this.headerField = this.headerField.toLowerCase();
+  this.partHeaders[this.headerField] = this.headerValue;
+
+  var m;
+  if (this.headerField === 'content-disposition') {
+    if (m = this.headerValue.match(/\bname="([^"]+)"/i)) {
+      this.partName = m[1];
+    }
+    this.partFilename = parseFilename(this.headerValue);
+  } else if (this.headerField === 'content-transfer-encoding') {
+    this.partTransferEncoding = this.headerValue.toLowerCase();
+  }
+
+  this.headerFieldDecoder = new StringDecoder(this.encoding);
+  this.headerField = '';
+  this.headerValueDecoder = new StringDecoder(this.encoding);
+  this.headerValue = '';
+}
+
+Form.prototype.onParsePartData = function(b) {
+  if (this.partTransferEncoding === 'base64') {
+    this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64');
+  } else {
+    this.backpressure = ! this.destStream.write(b);
+  }
+}
+
+Form.prototype.onParsePartEnd = function() {
+  if (this.destStream) {
+    flushWriteCbs(this);
+    var s = this.destStream;
+    process.nextTick(function() {
+      s.end();
+    });
+  }
+  clearPartVars(this);
+}
+
+Form.prototype.onParseHeadersEnd = function(offset) {
+  var self = this;
+  switch(self.partTransferEncoding){
+    case 'binary':
+    case '7bit':
+    case '8bit':
+    self.partTransferEncoding = 'binary';
+    break;
+
+    case 'base64': break;
+    default:
+    return new Error("unknown transfer-encoding: " + self.partTransferEncoding);
+  }
+
+  self.totalFieldCount += 1;
+  if (self.totalFieldCount >= self.maxFields) {
+    return new Error("maxFields " + self.maxFields + " exceeded.");
+  }
+
+  self.destStream = new stream.PassThrough();
+  self.destStream.on('drain', function() {
+    flushWriteCbs(self);
+  });
+  self.destStream.headers = self.partHeaders;
+  self.destStream.name = self.partName;
+  self.destStream.filename = self.partFilename;
+  self.destStream.byteOffset = self.bytesReceived + offset;
+  var partContentLength = self.destStream.headers['content-length'];
+  self.destStream.byteCount = partContentLength ?
+    parseInt(partContentLength, 10) :
+    (self.bytesExpected - self.destStream.byteOffset -
+     self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN);
+
+  self.emit('part', self.destStream);
+  if (self.destStream.filename == null && self.autoFields) {
+    handleField(self, self.destStream);
+  } else if (self.destStream.filename != null && self.autoFiles) {
+    handleFile(self, self.destStream);
+  }
+}
+
+function flushWriteCbs(self) {
+  self.writeCbs.forEach(function(cb) {
+    process.nextTick(cb);
+  });
+  self.writeCbs = [];
+  self.backpressure = false;
+}
+
+function getBytesExpected(headers) {
+  var contentLength = headers['content-length'];
+  if (contentLength) {
+    return parseInt(contentLength, 10);
+  } else if (headers['transfer-encoding'] == null) {
+    return 0;
+  } else {
+    return null;
+  }
+}
+
+function beginFlush(self) {
+  self.flushing += 1;
+}
+
+function endFlush(self) {
+  self.flushing -= 1;
+  maybeClose(self);
+}
+
+function maybeClose(self) {
+  if (!self.flushing && self.finished && !self.error) {
+    self.emit('close');
+  }
+}
+
+function handleFile(self, fileStream) {
+  beginFlush(self);
+  var file = {
+    fieldName: fileStream.name,
+    originalFilename: fileStream.filename,
+    path: uploadPath(self.uploadDir, fileStream.filename),
+    headers: fileStream.headers,
+  };
+  file.ws = fs.createWriteStream(file.path);
+  self.openedFiles.push(file);
+  fileStream.pipe(file.ws);
+  var counter = new StreamCounter();
+  fileStream.pipe(counter);
+  var hashWorkaroundStream
+    , hash = null;
+  if (self.hash) {
+    // workaround stream because https://github.com/joyent/node/issues/5216
+    hashWorkaroundStream = stream.Writable();
+    hash = crypto.createHash(self.hash);
+    hashWorkaroundStream._write = function(buffer, encoding, callback) {
+      hash.update(buffer);
+      callback();
+    };
+    fileStream.pipe(hashWorkaroundStream);
+  }
+  file.ws.on('error', function(err) {
+    if (!self.error) self.handleError(err);
+  });
+  file.ws.on('close', function() {
+    if (hash) file.hash = hash.digest('hex');
+    file.size = counter.bytes;
+    self.emit('file', fileStream.name, file);
+    endFlush(self);
+  });
+}
+
+function handleField(self, fieldStream) {
+  var value = '';
+  var decoder = new StringDecoder(self.encoding);
+
+  beginFlush(self);
+  fieldStream.on('readable', function() {
+    var buffer = fieldStream.read();
+    if (!buffer) return;
+
+    self.totalFieldSize += buffer.length;
+    if (self.totalFieldSize > self.maxFieldsSize) {
+      self.handleError(new Error("maxFieldsSize " + self.maxFieldsSize + " exceeded"));
+      return;
+    }
+    value += decoder.write(buffer);
+  });
+
+  fieldStream.on('end', function() {
+    self.emit('field', fieldStream.name, value);
+    endFlush(self);
+  });
+}
+
+function clearPartVars(self) {
+  self.partHeaders = {};
+  self.partName = null;
+  self.partFilename = null;
+  self.partTransferEncoding = 'binary';
+  self.destStream = null;
+
+  self.headerFieldDecoder = new StringDecoder(self.encoding);
+  self.headerField = "";
+  self.headerValueDecoder = new StringDecoder(self.encoding);
+  self.headerValue = "";
+}
+
+function setUpParser(self, boundary) {
+  self.boundary = new Buffer(boundary.length + 4);
+  self.boundary.write('\r\n--', 0, boundary.length + 4, 'ascii');
+  self.boundary.write(boundary, 4, boundary.length, 'ascii');
+  self.lookbehind = new Buffer(self.boundary.length + 8);
+  self.state = START;
+  self.boundaryChars = {};
+  for (var i = 0; i < self.boundary.length; i++) {
+    self.boundaryChars[self.boundary[i]] = true;
+  }
+
+  self.index = null;
+  self.partBoundaryFlag = false;
+  self.lastBoundaryFlag = false;
+
+  self.on('finish', function() {
+    if ((self.state === HEADER_FIELD_START && self.index === 0) ||
+        (self.state === PART_DATA && self.index === self.boundary.length))
+    {
+      self.onParsePartEnd();
+    } else if (self.state !== END) {
+      self.handleError(new Error('stream ended unexpectedly'));
+    }
+    self.finished = true;
+    maybeClose(self);
+  });
+}
+
+function uploadPath(baseDir, filename) {
+  var ext = path.extname(filename).replace(FILE_EXT_RE, '$1');
+  var name = process.pid + '-' +
+    (Math.random() * 0x100000000 + 1).toString(36) + ext;
+  return path.join(baseDir, name);
+}
+
+function parseFilename(headerValue) {
+  var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
+  if (!m) return;
+
+  var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
+  filename = filename.replace(/%22/g, '"');
+  filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
+    return String.fromCharCode(code);
+  });
+  return filename;
+}
+
+function lower(c) {
+  return c | 0x20;
+}
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..65e5757
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "multiparty",
+  "version": "2.2.0",
+  "description": "multipart/form-data parser which supports streaming",
+  "repository": {
+    "type": "git",
+    "url": "git at github.com:superjoe30/node-multiparty.git"
+  },
+  "keywords": [
+    "file",
+    "upload",
+    "formidable",
+    "stream",
+    "s3"
+  ],
+  "devDependencies": {
+    "findit": "0.1.1",
+    "hashish": "0.0.4",
+    "mocha": "~1.8.2",
+    "request": "~2.16.6",
+    "mkdirp": "~0.3.5",
+    "superagent": "~0.14.1"
+  },
+  "scripts": {
+    "test": "ulimit -n 500 && mocha --timeout 4000 --reporter spec --recursive test/test.js"
+  },
+  "engines": {
+    "node": ">=0.8.0"
+  },
+  "license": "MIT",
+  "dependencies": {
+    "readable-stream": "~1.1.9",
+    "stream-counter": "~0.2.0"
+  }
+}
diff --git a/test/bench-multipart-parser.js b/test/bench-multipart-parser.js
new file mode 100644
index 0000000..ee5dbad
--- /dev/null
+++ b/test/bench-multipart-parser.js
@@ -0,0 +1,76 @@
+var assert = require('assert')
+  , Form = require('../').Form
+  , boundary = '-----------------------------168072824752491622650073'
+  , mb = 100
+  , buffer = createMultipartBuffer(boundary, mb * 1024 * 1024)
+
+var callbacks = {
+  partBegin: -1,
+  partEnd: -1,
+  headerField: -1,
+  headerValue: -1,
+  partData: -1,
+  end: -1,
+};
+
+var form = new Form({ boundary: boundary });
+
+hijack('onParseHeaderField', function() {
+  callbacks.headerField++;
+});
+
+hijack('onParseHeaderValue', function() {
+  callbacks.headerValue++;
+});
+
+hijack('onParsePartBegin', function() {
+  callbacks.partBegin++;
+});
+
+hijack('onParsePartData', function() {
+  callbacks.partData++;
+});
+
+hijack('onParsePartEnd', function() {
+  callbacks.partEnd++;
+});
+
+form.on('finish', function() {
+  callbacks.end++;
+});
+
+var start = new Date();
+form.write(buffer, function(err) {
+  var duration = new Date() - start;
+  assert.ifError(err);
+  var mbPerSec = (mb / (duration / 1000)).toFixed(2);
+  console.log(mbPerSec+' mb/sec');
+});
+
+process.on('exit', function() {
+  for (var k in callbacks) {
+    assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
+  }
+});
+
+function createMultipartBuffer(boundary, size) {
+  var head =
+        '--'+boundary+'\r\n' +
+        'content-disposition: form-data; name="field1"\r\n' +
+        '\r\n'
+    , tail = '\r\n--'+boundary+'--\r\n'
+    , buffer = new Buffer(size);
+
+  buffer.write(head, 'ascii', 0);
+  buffer.write(tail, 'ascii', buffer.length - tail.length);
+  return buffer;
+}
+
+function hijack(name, fn) {
+  var oldFn = form[name];
+  form[name] = function() {
+    fn();
+    return oldFn.apply(this, arguments);
+  };
+}
+
diff --git a/test/fixture/file/beta-sticker-1.png b/test/fixture/file/beta-sticker-1.png
new file mode 100644
index 0000000..20b1a7f
Binary files /dev/null and b/test/fixture/file/beta-sticker-1.png differ
diff --git a/test/fixture/file/binaryfile.tar.gz b/test/fixture/file/binaryfile.tar.gz
new file mode 100644
index 0000000..4a85af7
Binary files /dev/null and b/test/fixture/file/binaryfile.tar.gz differ
diff --git a/test/fixture/file/blank.gif b/test/fixture/file/blank.gif
new file mode 100755
index 0000000..75b945d
Binary files /dev/null and b/test/fixture/file/blank.gif differ
diff --git a/test/fixture/file/funkyfilename.txt b/test/fixture/file/funkyfilename.txt
new file mode 100644
index 0000000..e7a4785
--- /dev/null
+++ b/test/fixture/file/funkyfilename.txt
@@ -0,0 +1 @@
+I am a text file with a funky name!
diff --git a/test/fixture/file/menu_separator.png b/test/fixture/file/menu_separator.png
new file mode 100644
index 0000000..1c16a71
Binary files /dev/null and b/test/fixture/file/menu_separator.png differ
diff --git a/test/fixture/file/pf1y5.png b/test/fixture/file/pf1y5.png
new file mode 100644
index 0000000..44d6017
Binary files /dev/null and b/test/fixture/file/pf1y5.png differ
diff --git a/test/fixture/file/plain.txt b/test/fixture/file/plain.txt
new file mode 100644
index 0000000..9b6903e
--- /dev/null
+++ b/test/fixture/file/plain.txt
@@ -0,0 +1 @@
+I am a plain text file
diff --git a/test/fixture/http/encoding/beta-sticker-1.png.http b/test/fixture/http/encoding/beta-sticker-1.png.http
new file mode 100644
index 0000000..833b83c
--- /dev/null
+++ b/test/fixture/http/encoding/beta-sticker-1.png.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Length: 2483
+
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Disposition: form-data; name="sticker"; filename="beta-sticker-1.png"
+Content-Type: image/png
+Content-Transfer-Encoding: base64
+
+iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABh5JREFUeNrMmHtIHEcYwGfv5SNwaovxEanEiJKqlYCCTRo1f0SvDeof1legEcE/YttQaNOiaQjYFFtpKaJILZU8SCRUWqlJGpoWepGLTXqUEnzFxCrnK9DEelbvvPOe/WacuY7r7HmGFjrwsbNzt7u//V7zfYvQ/2xI/9K1/NyvMP9PgCTuGmmL6/0ckD9UOGmbIExUsqMkAPHJjv5QwKRtgKioqDlh5+w/7IFeCuLlxCeA2zQ0IcCwh2qoaLH09fUdTElJ2e/1elU+n0/y+9fvPz4+fvfYsWN3YOoBcXPiocLghD4mBYHhQTCErqWlZU9FRcXJqKiowyqVSk/uSEH4o8fjWVlYWDB2d3e3d3R0WGB5jYqLg/NyGgsKxMNgkDB4451NTU3v [...]
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/--
diff --git a/test/fixture/http/encoding/binaryfile.tar.gz.http b/test/fixture/http/encoding/binaryfile.tar.gz.http
new file mode 100644
index 0000000..4f4fadb
--- /dev/null
+++ b/test/fixture/http/encoding/binaryfile.tar.gz.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Length: 676
+
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Disposition: form-data; name="file"; filename="binaryfile.tar.gz"
+Content-Type: application/x-gzip
+Content-Transfer-Encoding: base64
+
+H4sIAGiNIU8AA+3R0W6CMBQGYK59iobLZantRDG73osUOGqnFNJWM2N897UghG1ZdmWWLf93U/jP4bRAq8q92hJ/dY1J7kQEqyyLq8yXYrp2ltkqkTKXYiEykYc++ZTLVcLEvQ40dXReWcYSV1pdnL/v+6n+R11mjKVG1ZQ+s3TT2FpXqjhQ+hjzE1mnGxNLkgu+7tOKWjIVmVKTC6XL9ZaeXj4VQhwKWzL+cI4zwgQuuhkh3mhTad/Hkssh3im3027X54JnQ360R/M19OT8kC7SEN7Ooi2VvrEfznHQRWzl83gxttZKmzGehzPRW/+W8X+3fvL8sFet9sS6m3EIma02071MU3Uf9KHrmV1/+y8DAAAAAAAAAAAAAAAAAAAAAMB/9A6txIuJACgAAA==
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/--
diff --git a/test/fixture/http/encoding/blank.gif.http b/test/fixture/http/encoding/blank.gif.http
new file mode 100644
index 0000000..7426f5b
--- /dev/null
+++ b/test/fixture/http/encoding/blank.gif.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Length: 323
+
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Disposition: form-data; name="file"; filename="blank.gif"
+Content-Type: image/gif
+Content-Transfer-Encoding: base64
+
+R0lGODlhAQABAJH/AP///wAAAMDAwAAAACH5BAEAAAIALAAAAAABAAEAAAICVAEAOw==
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/--
diff --git a/test/fixture/http/encoding/menu_seperator.png.http b/test/fixture/http/encoding/menu_seperator.png.http
new file mode 100644
index 0000000..d08fd37
--- /dev/null
+++ b/test/fixture/http/encoding/menu_seperator.png.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Length: 1509
+
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
+Content-Disposition: form-data; name="image"; filename="menu_separator.png"
+Content-Type: image/png
+Content-Transfer-Encoding: base64
+
+iVBORw0KGgoAAAANSUhEUgAAAAIAAAAYCAIAAABfmbuOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1w [...]
+--\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/--
diff --git a/test/fixture/http/encoding/pf1y5.png.http b/test/fixture/http/encoding/pf1y5.png.http
new file mode 100644
index 0000000..20c2c2d
Binary files /dev/null and b/test/fixture/http/encoding/pf1y5.png.http differ
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/encoding/plain.txt.http
new file mode 100644
index 0000000..5e85ad6
--- /dev/null
+++ b/test/fixture/http/encoding/plain.txt.http
@@ -0,0 +1,13 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 221
+
+------TLV0SrKD4z1TRxRhAPUvZ
+Content-Disposition: form-data; name="file"; filename="plain.txt"
+Content-Type: text/plain
+Content-Transfer-Encoding: 7bit
+
+I am a plain text file
+
+------TLV0SrKD4z1TRxRhAPUvZ--
diff --git a/test/fixture/http/no-filename/filename-name.http b/test/fixture/http/no-filename/filename-name.http
new file mode 100644
index 0000000..43672a3
--- /dev/null
+++ b/test/fixture/http/no-filename/filename-name.http
@@ -0,0 +1,13 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Length: 1000
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Disposition: form-data; filename="plain.txt"; name="upload"
+Content-Type: text/plain
+
+I am a plain text file
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG--
+
diff --git a/test/fixture/http/no-filename/generic.http b/test/fixture/http/no-filename/generic.http
new file mode 100644
index 0000000..e0dee27
--- /dev/null
+++ b/test/fixture/http/no-filename/generic.http
@@ -0,0 +1,13 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Length: 1000
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Disposition: form-data; name="upload"; filename=""
+Content-Type: text/plain
+
+I am a plain text file
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG--
+
diff --git a/test/fixture/http/preamble/crlf.http b/test/fixture/http/preamble/crlf.http
new file mode 100644
index 0000000..1d5f709
--- /dev/null
+++ b/test/fixture/http/preamble/crlf.http
@@ -0,0 +1,13 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 184
+
+
+------TLV0SrKD4z1TRxRhAPUvZ
+Content-Disposition: form-data; name="upload"; filename="plain.txt"
+Content-Type: text/plain
+
+I am a plain text file
+
+------TLV0SrKD4z1TRxRhAPUvZ--
diff --git a/test/fixture/http/preamble/preamble.http b/test/fixture/http/preamble/preamble.http
new file mode 100644
index 0000000..d14d433
--- /dev/null
+++ b/test/fixture/http/preamble/preamble.http
@@ -0,0 +1,13 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 226
+
+This is a preamble which should be ignored
+------TLV0SrKD4z1TRxRhAPUvZ
+Content-Disposition: form-data; name="upload"; filename="plain.txt"
+Content-Type: text/plain
+
+I am a plain text file
+
+------TLV0SrKD4z1TRxRhAPUvZ--
diff --git a/test/fixture/http/special-chars-in-filename/info.md b/test/fixture/http/special-chars-in-filename/info.md
new file mode 100644
index 0000000..3c9dbe3
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/info.md
@@ -0,0 +1,3 @@
+* Opera does not allow submitting this file, it shows a warning to the
+  user that the file could not be found instead. Tested in 9.8, 11.51 on OSX.
+  Reported to Opera on 08.09.2011 (tracking email DSK-346009 at bugs.opera.com).
diff --git a/test/fixture/http/special-chars-in-filename/osx-chrome-13.http b/test/fixture/http/special-chars-in-filename/osx-chrome-13.http
new file mode 100644
index 0000000..4ef3917
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/osx-chrome-13.http
@@ -0,0 +1,26 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Connection: keep-alive
+Referer: http://localhost:8080/
+Content-Length: 383
+Cache-Control: max-age=0
+Origin: http://localhost:8080
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: en-US,en;q=0.8
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
+Cookie: jqCookieJar_tablesorter=%7B%22showListTable%22%3A%5B%5B5%2C1%5D%2C%5B1%2C0%5D%5D%7D
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Disposition: form-data; name="title"
+
+Weird filename
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG
+Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: text/plain
+
+I am a text file with a funky name!
+
+------WebKitFormBoundarytyE4wkKlZ5CQJVTG--
diff --git a/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http b/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http
new file mode 100644
index 0000000..bf49f85
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http
@@ -0,0 +1,24 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://localhost:8080/
+Content-Type: multipart/form-data; boundary=---------------------------9849436581144108930470211272
+Content-Length: 438
+
+-----------------------------9849436581144108930470211272
+Content-Disposition: form-data; name="title"
+
+Weird filename
+-----------------------------9849436581144108930470211272
+Content-Disposition: form-data; name="upload"; filename=": \ ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: text/plain
+
+I am a text file with a funky name!
+
+-----------------------------9849436581144108930470211272--
diff --git a/test/fixture/http/special-chars-in-filename/osx-safari-5.http b/test/fixture/http/special-chars-in-filename/osx-safari-5.http
new file mode 100644
index 0000000..ff158a4
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/osx-safari-5.http
@@ -0,0 +1,23 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Origin: http://localhost:8080
+Content-Length: 383
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQJZ1gvhvdgfisJPJ
+Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
+Referer: http://localhost:8080/
+Accept-Language: en-us
+Accept-Encoding: gzip, deflate
+Connection: keep-alive
+
+------WebKitFormBoundaryQJZ1gvhvdgfisJPJ
+Content-Disposition: form-data; name="title"
+
+Weird filename
+------WebKitFormBoundaryQJZ1gvhvdgfisJPJ
+Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: text/plain
+
+I am a text file with a funky name!
+
+------WebKitFormBoundaryQJZ1gvhvdgfisJPJ--
diff --git a/test/fixture/http/special-chars-in-filename/xp-chrome-12.http b/test/fixture/http/special-chars-in-filename/xp-chrome-12.http
new file mode 100644
index 0000000..f0fc533
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/xp-chrome-12.http
@@ -0,0 +1,24 @@
+POST /upload HTTP/1.1
+Host: 192.168.56.1:8080
+Connection: keep-alive
+Referer: http://192.168.56.1:8080/
+Content-Length: 344
+Cache-Control: max-age=0
+Origin: http://192.168.56.1:8080
+User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEvqBNplR3ByrwQPa
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
+
+------WebKitFormBoundaryEvqBNplR3ByrwQPa
+Content-Disposition: form-data; name="title"
+
+Weird filename
+------WebKitFormBoundaryEvqBNplR3ByrwQPa
+Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: text/plain
+
+
+------WebKitFormBoundaryEvqBNplR3ByrwQPa--
diff --git a/test/fixture/http/special-chars-in-filename/xp-ie-7.http b/test/fixture/http/special-chars-in-filename/xp-ie-7.http
new file mode 100644
index 0000000..2e2c61c
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/xp-ie-7.http
@@ -0,0 +1,22 @@
+POST /upload HTTP/1.1
+Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */*
+Referer: http://192.168.56.1:8080/
+Accept-Language: de
+User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
+Content-Type: multipart/form-data; boundary=---------------------------7db1fe232017c
+Accept-Encoding: gzip, deflate
+Host: 192.168.56.1:8080
+Content-Length: 368
+Connection: Keep-Alive
+Cache-Control: no-cache
+
+-----------------------------7db1fe232017c
+Content-Disposition: form-data; name="title"
+
+Weird filename
+-----------------------------7db1fe232017c
+Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: application/octet-stream
+
+
+-----------------------------7db1fe232017c--
diff --git a/test/fixture/http/special-chars-in-filename/xp-ie-8.http b/test/fixture/http/special-chars-in-filename/xp-ie-8.http
new file mode 100644
index 0000000..e2b94fa
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/xp-ie-8.http
@@ -0,0 +1,22 @@
+POST /upload HTTP/1.1
+Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */*
+Referer: http://192.168.56.1:8080/
+Accept-Language: de
+User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
+Content-Type: multipart/form-data; boundary=---------------------------7db3a8372017c
+Accept-Encoding: gzip, deflate
+Host: 192.168.56.1:8080
+Content-Length: 368
+Connection: Keep-Alive
+Cache-Control: no-cache
+
+-----------------------------7db3a8372017c
+Content-Disposition: form-data; name="title"
+
+Weird filename
+-----------------------------7db3a8372017c
+Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: application/octet-stream
+
+
+-----------------------------7db3a8372017c--
diff --git a/test/fixture/http/special-chars-in-filename/xp-safari-5.http b/test/fixture/http/special-chars-in-filename/xp-safari-5.http
new file mode 100644
index 0000000..6379ac0
--- /dev/null
+++ b/test/fixture/http/special-chars-in-filename/xp-safari-5.http
@@ -0,0 +1,22 @@
+POST /upload HTTP/1.1
+Host: 192.168.56.1:8080
+Referer: http://192.168.56.1:8080/
+Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
+Accept-Language: en-US
+Origin: http://192.168.56.1:8080
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4
+Accept-Encoding: gzip, deflate
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykmaWSUbu697WN9TM
+Content-Length: 344
+Connection: keep-alive
+
+------WebKitFormBoundarykmaWSUbu697WN9TM
+Content-Disposition: form-data; name="title"
+
+Weird filename
+------WebKitFormBoundarykmaWSUbu697WN9TM
+Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
+Content-Type: text/plain
+
+
+------WebKitFormBoundarykmaWSUbu697WN9TM--
diff --git a/test/fixture/http/workarounds/missing-hyphens1.http b/test/fixture/http/workarounds/missing-hyphens1.http
new file mode 100644
index 0000000..2826890
--- /dev/null
+++ b/test/fixture/http/workarounds/missing-hyphens1.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 178
+
+------TLV0SrKD4z1TRxRhAPUvZ
+Content-Disposition: form-data; name="upload"; filename="plain.txt"
+Content-Type: text/plain
+
+I am a plain text file
+
+------TLV0SrKD4z1TRxRhAPUvZ
diff --git a/test/fixture/http/workarounds/missing-hyphens2.http b/test/fixture/http/workarounds/missing-hyphens2.http
new file mode 100644
index 0000000..8e18194
--- /dev/null
+++ b/test/fixture/http/workarounds/missing-hyphens2.http
@@ -0,0 +1,12 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 180
+
+------TLV0SrKD4z1TRxRhAPUvZ
+Content-Disposition: form-data; name="upload"; filename="plain.txt"
+Content-Type: text/plain
+
+I am a plain text file
+
+------TLV0SrKD4z1TRxRhAPUvZ
diff --git a/test/fixture/js/encoding.js b/test/fixture/js/encoding.js
new file mode 100644
index 0000000..1ade965
--- /dev/null
+++ b/test/fixture/js/encoding.js
@@ -0,0 +1,69 @@
+module.exports['menu_seperator.png.http'] = [
+  {
+    type: 'file',
+    name: 'image',
+    filename: 'menu_separator.png',
+    fixture: 'menu_separator.png',
+    sha1: 'c845ca3ea794be298f2a1b79769b71939eaf4e54',
+    size: 931,
+  }
+];
+
+module.exports['beta-sticker-1.png.http'] = [
+  {
+    type: 'file',
+    name: 'sticker',
+    filename: 'beta-sticker-1.png',
+    fixture: 'beta-sticker-1.png',
+    sha1: '6abbcffd12b4ada5a6a084fe9e4584f846331bc4',
+    size: 1660,
+  }
+];
+
+module.exports['blank.gif.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'blank.gif',
+    fixture: 'blank.gif',
+    sha1: 'a1fdee122b95748d81cee426d717c05b5174fe96',
+    size: 49,
+  }
+];
+
+module.exports['binaryfile.tar.gz.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'binaryfile.tar.gz',
+    fixture: 'binaryfile.tar.gz',
+    sha1: 'cfabe13b348e5e69287d677860880c52a69d2155',
+    size: 301,
+  }
+];
+
+module.exports['plain.txt.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'plain.txt',
+    fixture: 'plain.txt',
+    sha1: 'b31d07bac24ac32734de88b3687dddb10e976872',
+    size: 23,
+  }
+];
+
+module.exports['pf1y5.png.http'] = [
+  {
+    type: 'field',
+    name: 'path',
+  },
+  {
+    type: 'file',
+    name: 'upload',
+    filename: 'pf1y5.png',
+    fixture: 'pf1y5.png',
+    sha1: '805cc640c5b182e86f2b5c8ebf34ecf063cd34fd',
+    size: 768323,
+  }
+];
diff --git a/test/fixture/js/no-filename.js b/test/fixture/js/no-filename.js
new file mode 100644
index 0000000..f03b4f0
--- /dev/null
+++ b/test/fixture/js/no-filename.js
@@ -0,0 +1,9 @@
+module.exports['generic.http'] = [
+  {type: 'file', name: 'upload', filename: '', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
+
+module.exports['filename-name.http'] = [
+  {type: 'file', name: 'upload', filename: 'plain.txt', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
diff --git a/test/fixture/js/preamble.js b/test/fixture/js/preamble.js
new file mode 100644
index 0000000..d2e4cfd
--- /dev/null
+++ b/test/fixture/js/preamble.js
@@ -0,0 +1,9 @@
+module.exports['crlf.http'] = [
+  {type: 'file', name: 'upload', filename: 'plain.txt', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
+
+module.exports['preamble.http'] = [
+  {type: 'file', name: 'upload', filename: 'plain.txt', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
diff --git a/test/fixture/js/special-chars-in-filename.js b/test/fixture/js/special-chars-in-filename.js
new file mode 100644
index 0000000..aa0b79f
--- /dev/null
+++ b/test/fixture/js/special-chars-in-filename.js
@@ -0,0 +1,30 @@
+var properFilename = 'funkyfilename.txt';
+
+function expect(filename) {
+  return [
+    {
+      type: 'field',
+      name: 'title',
+      value: 'Weird filename',
+    },
+    {
+      type: 'file',
+      name: 'upload',
+      filename: filename,
+      fixture: properFilename,
+    },
+  ];
+}
+
+var webkit = " ? % * | \" < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt";
+var ffOrIe = " ? % * | \" < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt";
+
+module.exports = {
+  'osx-chrome-13.http'   : expect(webkit),
+  'osx-firefox-3.6.http' : expect(ffOrIe),
+  'osx-safari-5.http'    : expect(webkit),
+  'xp-chrome-12.http'    : expect(webkit),
+  'xp-ie-7.http'         : expect(ffOrIe),
+  'xp-ie-8.http'         : expect(ffOrIe),
+  'xp-safari-5.http'     : expect(webkit),
+};
diff --git a/test/fixture/js/workarounds.js b/test/fixture/js/workarounds.js
new file mode 100644
index 0000000..e59c5b2
--- /dev/null
+++ b/test/fixture/js/workarounds.js
@@ -0,0 +1,8 @@
+module.exports['missing-hyphens1.http'] = [
+  {type: 'file', name: 'upload', filename: 'plain.txt', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
+module.exports['missing-hyphens2.http'] = [
+  {type: 'file', name: 'upload', filename: 'plain.txt', fixture: 'plain.txt',
+  sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
+];
diff --git a/test/fixture/multi_video.upload b/test/fixture/multi_video.upload
new file mode 100644
index 0000000..9c82ba3
Binary files /dev/null and b/test/fixture/multi_video.upload differ
diff --git a/test/fixture/multipart.js b/test/fixture/multipart.js
new file mode 100644
index 0000000..a476169
--- /dev/null
+++ b/test/fixture/multipart.js
@@ -0,0 +1,72 @@
+exports['rfc1867'] =
+  { 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--\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',
+      }
+    ]
+  };
+
+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:
+      '--AaB03x\r\n'+
+      'content-disposition: form-data; name="field1"\r\n'+
+      ': foo\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--\r\n',
+    expectError: true,
+  };
diff --git a/test/record.js b/test/record.js
new file mode 100644
index 0000000..9f1cef8
--- /dev/null
+++ b/test/record.js
@@ -0,0 +1,47 @@
+var http = require('http');
+var fs = require('fs');
+var connections = 0;
+
+var server = http.createServer(function(req, res) {
+  var socket = req.socket;
+  console.log('Request: %s %s -> %s', req.method, req.url, socket.filename);
+
+  req.on('end', function() {
+    if (req.url !== '/') {
+      res.end(JSON.stringify({
+        method: req.method,
+        url: req.url,
+        filename: socket.filename,
+      }));
+      return;
+    }
+
+    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>'
+    );
+  });
+});
+
+server.on('connection', function(socket) {
+  connections++;
+
+  socket.id = connections;
+  socket.filename = 'connection-' + socket.id + '.http';
+  socket.file = fs.createWriteStream(socket.filename);
+  socket.pipe(socket.file);
+
+  console.log('--> %s', socket.filename);
+  socket.on('close', function() {
+    console.log('<-- %s', socket.filename);
+  });
+});
+
+var port = process.env.PORT || 8080;
+server.listen(port, function() {
+  console.log('Recording connections on port %s', port);
+});
diff --git a/test/standalone/test-connection-aborted.js b/test/standalone/test-connection-aborted.js
new file mode 100644
index 0000000..bd83e1d
--- /dev/null
+++ b/test/standalone/test-connection-aborted.js
@@ -0,0 +1,27 @@
+var assert = require('assert');
+var http = require('http');
+var net = require('net');
+var multiparty = require('../../');
+
+var server = http.createServer(function (req, res) {
+  var form = new multiparty.Form();
+  var aborted_received = false;
+  form.on('aborted', function () {
+    aborted_received = true;
+  });
+  form.on('error', function () {
+    assert(aborted_received, 'Error event should follow aborted');
+    server.close();
+  });
+  form.on('end', function () {
+    throw new Error('Unexpected "end" event');
+  });
+  form.parse(req);
+}).listen(0, 'localhost', function () {
+  var client = net.connect(server.address().port);
+  client.write(
+    "POST / HTTP/1.1\r\n" +
+    "Content-Length: 70\r\n" +
+    "Content-Type: multipart/form-data; boundary=foo\r\n\r\n");
+  client.end();
+});
diff --git a/test/standalone/test-content-transfer-encoding.js b/test/standalone/test-content-transfer-encoding.js
new file mode 100644
index 0000000..35e5a1f
--- /dev/null
+++ b/test/standalone/test-content-transfer-encoding.js
@@ -0,0 +1,52 @@
+var assert = require('assert')
+  , multiparty = require('../../')
+  , http = require('http')
+  , path = require('path')
+  , TMP_PATH = path.join(__dirname, '..', 'tmp')
+
+var server = http.createServer(function(req, res) {
+  var form = new multiparty.Form();
+  form.uploadDir = TMP_PATH;
+  form.on('close', function () {
+    throw new Error('Unexpected "close" event');
+  });
+  form.on('end', function () {
+    throw new Error('Unexpected "end" event');
+  });
+  form.on('error', function (e) {
+    res.writeHead(500);
+    res.end(e.message);
+  });
+  form.parse(req);
+});
+
+server.listen(0, function() {
+  var body =
+    '--foo\r\n' +
+    'Content-Disposition: form-data; name="file1"; filename="file1"\r\n' +
+    'Content-Type: application/octet-stream\r\n' +
+    '\r\nThis is the first file\r\n' +
+    '--foo\r\n' +
+    'Content-Type: application/octet-stream\r\n' +
+    'Content-Disposition: form-data; name="file2"; filename="file2"\r\n' +
+    'Content-Transfer-Encoding: unknown\r\n' +
+    '\r\nThis is the second file\r\n' +
+    '--foo--\r\n';
+
+  var req = http.request({
+    method: 'POST',
+    port: server.address().port,
+    headers: {
+      'Content-Length': body.length,
+      'Content-Type': 'multipart/form-data; boundary=foo'
+    }
+  });
+  req.on('response', function (res) {
+    assert.equal(res.statusCode, 500);
+    res.on('data', function () {});
+    res.on('end', function () {
+      server.close();
+    });
+  });
+  req.end(body);
+});
diff --git a/test/standalone/test-invalid.js b/test/standalone/test-invalid.js
new file mode 100644
index 0000000..ede541d
--- /dev/null
+++ b/test/standalone/test-invalid.js
@@ -0,0 +1,35 @@
+var superagent = require('superagent')
+  , multiparty = require('../../')
+  , http = require('http')
+
+var server = http.createServer(function(req, resp) {
+  var form = new multiparty.Form();
+
+  var errCount = 0;
+  form.on('error', function(err) {
+    errCount += 1;
+    resp.end();
+  });
+  form.on('file', function(name, file) {
+  });
+  form.on('field', function(name, file) {
+  });
+
+  form.parse(req);
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/'
+  var req = superagent.post(url)
+  req.set('Content-Type', 'multipart/form-data; boundary=foo')
+  req.write('--foo\r\n')
+  req.write('Content-filename="foo.txt"\r\n')
+  req.write('\r\n')
+  req.write('some text here')
+  req.write('Content-Disposition: form-data; name="text"; filename="bar.txt"\r\n')
+  req.write('\r\n')
+  req.write('some more text stuff')
+  req.write('\r\n--foo--')
+  req.end(function(err, resp) {
+    server.close();
+  });
+});
diff --git a/test/standalone/test-issue-15.js b/test/standalone/test-issue-15.js
new file mode 100644
index 0000000..43982fa
--- /dev/null
+++ b/test/standalone/test-issue-15.js
@@ -0,0 +1,88 @@
+var http = require('http')
+  , multiparty = require('../../')
+  , assert = require('assert')
+  , superagent = require('superagent')
+  , path = require('path')
+
+var server = http.createServer(function(req, res) {
+  assert.strictEqual(req.url, '/upload');
+  assert.strictEqual(req.method, 'POST');
+
+  var form = new multiparty.Form({autoFields:true,autoFiles:true});
+
+  form.on('error', function(err) {
+    console.log(err);
+  });
+
+  form.on('close', function() {
+  });
+
+  var fileCount = 0;
+  form.on('file', function(name, file) {
+    fileCount += 1;
+  });
+
+  form.parse(req, function(err, fields, files) {
+    var objFileCount = 0;
+    for (var file in files) {
+      objFileCount += 1;
+    }
+    // multiparty does NOT try to do intelligent things based on
+    // the part name.
+    assert.strictEqual(fileCount, 2);
+    assert.strictEqual(objFileCount, 1);
+    res.end();
+  });
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  req.attach('files[]', fixture('pf1y5.png'), 'SOG2.JPG');
+  req.attach('files[]', fixture('binaryfile.tar.gz'), 'BenF364_LIB353.zip');
+
+  // Get the existing boundary.
+  var contentType = req.get('content-type');
+  var split = contentType.split(' ');
+
+  // Set the content-type.
+  req.set('content-type', split.join(''));
+
+  req.end(function(err, resp) {
+    assert.ifError(err);
+    resp.on('end', function() {
+      server.close();
+    });
+  });
+
+  // No space.
+  createRequest('');
+
+  // Single space.
+  createRequest(' ');
+
+  // Multiple spaces.
+  createRequest('    ');
+});
+
+function createRequest(separator) {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  req.attach('files[]', fixture('pf1y5.png'), 'SOG2.JPG');
+  req.attach('files[]', fixture('binaryfile.tar.gz'), 'BenF364_LIB353.zip');
+
+  // Get the existing boundary.
+  var contentType = req.get('content-type');
+  var split = contentType.split(' ');
+
+  // Set the content-type.
+  req.set('content-type', split.join(separator));
+
+  req.end(function(err, resp) {
+    assert.ifError(err);
+    // We don't close the server, to allow other requests to pass.
+  });
+}
+
+function fixture(name) {
+  return path.join(__dirname, '..', 'fixture', 'file', name)
+}
diff --git a/test/standalone/test-issue-19.js b/test/standalone/test-issue-19.js
new file mode 100644
index 0000000..d7da0cf
--- /dev/null
+++ b/test/standalone/test-issue-19.js
@@ -0,0 +1,44 @@
+var assert = require('assert');
+var http = require('http');
+var net = require('net');
+var multiparty = require('../../');
+
+var client;
+var server = http.createServer(function (req, res) {
+  var form = new multiparty.Form({maxFields: 1});
+  form.on('aborted', function () {
+    throw new Error("did not expect aborted");
+  });
+  var first = true;
+  form.on('error', function (err) {
+    assert.ok(first);
+    first = false;
+    client.end();
+    assert.ok(/maxFields/.test(err.message));
+    server.close();
+  });
+  form.on('end', function () {
+    throw new Error('Unexpected "end" event');
+  });
+  form.parse(req);
+});
+server.listen(function() {
+  client = net.connect(server.address().port);
+
+  client.write("POST /upload HTTP/1.1\r\n" +
+    "Content-Length: 728\r\n" +
+    "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"title\"\r\n" +
+    "\r\n" +
+    "foofoo" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah1.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi1\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n");
+});
diff --git a/test/standalone/test-issue-21.js b/test/standalone/test-issue-21.js
new file mode 100644
index 0000000..155fba0
--- /dev/null
+++ b/test/standalone/test-issue-21.js
@@ -0,0 +1,66 @@
+var assert = require('assert');
+var http = require('http');
+var net = require('net');
+var multiparty = require('../../');
+
+var client;
+var server = http.createServer(function(req, res) {
+  var form = new multiparty.Form();
+
+  form.parse(req, function(err, fieldsTable, filesTable, fieldsList, filesList) {
+    if (err) {
+      console.error(err.stack);
+      return;
+    }
+    assert.strictEqual(fieldsList.length, 1);
+    assert.strictEqual(fieldsList[0].name, "title");
+    assert.strictEqual(fieldsList[0].value, "foofoo");
+    assert.strictEqual(filesList.length, 4);
+    assert.strictEqual(filesList[0].fieldName, "upload");
+    assert.strictEqual(filesList[1].fieldName, "upload");
+    assert.strictEqual(filesList[2].fieldName, "upload");
+    assert.strictEqual(filesList[3].fieldName, "upload");
+    res.end();
+    client.end();
+    server.close();
+  });
+});
+server.listen(function() {
+  client = net.connect(server.address().port);
+
+  client.write("POST /upload HTTP/1.1\r\n" +
+    "Content-Length: 728\r\n" +
+    "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"title\"\r\n" +
+    "\r\n" +
+    "foofoo" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah1.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi1\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah2.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi2\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah3.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi3\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah4.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi4\r\n" +
+    "\r\n" +
+    "------WebKitFormBoundaryvfUZhxgsZDO7FXLF--\r\n"
+  );
+});
diff --git a/test/standalone/test-issue-4.js b/test/standalone/test-issue-4.js
new file mode 100644
index 0000000..66b2a69
--- /dev/null
+++ b/test/standalone/test-issue-4.js
@@ -0,0 +1,51 @@
+var http = require('http')
+  , multiparty = require('../../')
+  , assert = require('assert')
+  , superagent = require('superagent')
+  , path = require('path')
+
+var server = http.createServer(function(req, res) {
+  assert.strictEqual(req.url, '/upload');
+  assert.strictEqual(req.method, 'POST');
+
+  var form = new multiparty.Form({autoFields:true,autoFiles:true});
+
+  form.on('error', function(err) {
+    console.log(err);
+  });
+
+  form.on('close', function() {
+  });
+
+  var fileCount = 0;
+  form.on('file', function(name, file) {
+    fileCount += 1;
+  });
+
+  form.parse(req, function(err, fields, files) {
+    var objFileCount = 0;
+    for (var file in files) {
+      objFileCount += 1;
+    }
+    // multiparty does NOT try to do intelligent things based on
+    // the part name.
+    assert.strictEqual(fileCount, 2);
+    assert.strictEqual(objFileCount, 1);
+    res.end();
+  });
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  req.attach('files[]', fixture('pf1y5.png'), 'SOG2.JPG');
+  req.attach('files[]', fixture('binaryfile.tar.gz'), 'BenF364_LIB353.zip');
+  req.end(function(err, resp) {
+    assert.ifError(err);
+    resp.on('end', function() {
+      server.close();
+    });
+  });
+});
+function fixture(name) {
+  return path.join(__dirname, '..', 'fixture', 'file', name)
+}
diff --git a/test/standalone/test-issue-46.js b/test/standalone/test-issue-46.js
new file mode 100644
index 0000000..676b870
--- /dev/null
+++ b/test/standalone/test-issue-46.js
@@ -0,0 +1,49 @@
+var http       = require('http'),
+    multiparty = require('../../'),
+    request    = require('request'),
+    assert     = require('assert');
+
+var host = 'localhost';
+
+var index = [
+  '<form action="/" method="post" enctype="multipart/form-data">',
+  '  <input type="text" name="foo" />',
+  '  <input type="submit" />',
+  '</form>'
+].join("\n");
+
+var server = http.createServer(function(req, res) {
+
+  // Show a form for testing purposes.
+  if (req.method === 'GET') {
+    res.writeHead(200, {'content-type': 'text/html'});
+    res.end(index);
+    return;
+  }
+
+  // Parse form and write results to response.
+  var form = new multiparty.Form();
+  form.parse(req, function(err, fields, files) {
+    res.writeHead(200, {'content-type': 'text/plain'});
+    res.write(JSON.stringify({err: err, fields: fields, files: files}));
+    res.end();
+  });
+
+}).listen(0, host, function() {
+
+  //console.log("Server up and running...");
+
+  var server = this,
+      url    = 'http://' + host + ':' + server.address().port;
+
+  var parts  = [
+    {'Content-Disposition': 'form-data; name="foo"', 'body': 'bar'}
+  ]
+
+  var req = request({method: 'POST', url: url, multipart: parts}, function(e, res, body) {
+    var obj = JSON.parse(body);
+    assert.equal("bar", obj.fields.foo);
+    server.close();
+  });
+
+});
diff --git a/test/standalone/test-issue-5.js b/test/standalone/test-issue-5.js
new file mode 100644
index 0000000..80eadf2
--- /dev/null
+++ b/test/standalone/test-issue-5.js
@@ -0,0 +1,39 @@
+var assert = require('assert');
+var http = require('http');
+var net = require('net');
+var multiparty = require('../../');
+
+var client;
+var attachmentCount = 510;
+var server = http.createServer(function(req, res) {
+  var form = new multiparty.Form({maxFields: 10000});
+
+  form.parse(req, function(err, fieldsTable, filesTable, fieldsList, filesList) {
+    assert.strictEqual(err.code, "EMFILE");
+    res.end();
+    client.end();
+    server.close();
+  });
+});
+server.listen(function() {
+  client = net.connect(server.address().port);
+
+  var boundary = "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n";
+  var oneAttachment = boundary +
+    "Content-Disposition: form-data; name=\"upload\"; filename=\"blah1.txt\"\r\n" +
+    "Content-Type: text/plain\r\n" +
+    "\r\n" +
+    "hi1\r\n" +
+    "\r\n";
+  var payloadSize = oneAttachment.length * attachmentCount + boundary.length;
+
+  client.write("POST /upload HTTP/1.1\r\n" +
+    "Content-Length: " + payloadSize + "\r\n" +
+    "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
+    "\r\n");
+
+  for (var i = 0; i < attachmentCount; i += 1) {
+    client.write(oneAttachment);
+  }
+  client.write(boundary);
+});
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..199d5cd
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,117 @@
+var spawn = require('child_process').spawn
+  , findit = require('findit')
+  , path = require('path')
+  , hashish = require('hashish')
+  , fs = require('fs')
+  , http = require('http')
+  , net = require('net')
+  , assert = require('assert')
+  , multiparty = require('../')
+  , mkdirp = require('mkdirp')
+  , STANDALONE_PATH = path.join(__dirname, 'standalone')
+  , server = http.createServer()
+  , PORT = 13532
+  , FIXTURE_PATH = path.join(__dirname, 'fixture')
+  , TMP_PATH = path.join(__dirname, 'tmp')
+
+mkdirp.sync(TMP_PATH);
+
+describe("fixtures", function() {
+  before(function(done) {
+    server.listen(PORT, done);
+  });
+  var fixtures = [];
+  findit
+    .sync(path.join(FIXTURE_PATH, 'js'))
+    .forEach(function(jsPath) {
+      if (!/\.js$/.test(jsPath)) return;
+      var group = path.basename(jsPath, '.js');
+      hashish.forEach(require(jsPath), function(fixture, name) {
+        it(group + '/' + name, createTest({
+          name    : group + '/' + name,
+          fixture : fixture,
+        }));
+      });
+    });
+});
+
+describe("standalone", function() {
+  findit
+    .sync(STANDALONE_PATH)
+    .forEach(function(jsPath) {
+      if (!/\.js$/.test(jsPath)) return;
+      it(path.basename(jsPath, '.js'), function(done) {
+        var child = spawn(process.execPath, [jsPath], { stdio: 'inherit' });
+        child.on('error', function(err) {
+          done(err);
+        });
+        child.on('exit', function(code) {
+          if (code) return done(new Error("exited with code " + code));
+          done();
+        });
+      });
+    });
+});
+
+function createTest(fixture) {
+  var name = fixture.name;
+  fixture = fixture.fixture;
+  return function(done) {
+    uploadFixture(name, function(err, parts) {
+      if (err) return done(err);
+      fixture.forEach(function(expectedPart, i) {
+        var parsedPart = parts[i];
+        assert.equal(parsedPart.type, expectedPart.type);
+        assert.equal(parsedPart.name, expectedPart.name);
+
+        if (parsedPart.type === 'file') {
+          var file = parsedPart.value;
+          assert.equal(file.originalFilename, expectedPart.filename);
+          if(expectedPart.sha1) assert.strictEqual(file.hash, expectedPart.sha1);
+          if(expectedPart.size) assert.strictEqual(file.size, expectedPart.size);
+        }
+      });
+      done();
+    });
+  };
+
+}
+
+function uploadFixture(name, cb) {
+  server.once('request', function(req, res) {
+    var parts = [];
+    var form = new multiparty.Form({
+      autoFields: true,
+      autoFiles: true,
+    });
+    form.uploadDir = TMP_PATH;
+    form.hash = "sha1";
+
+    form.on('error', callback);
+    form.on('file', function(name, value) {
+      parts.push({type: 'file', name: name, value: value});
+    });
+    form.on('field', function(name, value) {
+      parts.push({type: 'field', name: name, value: value});
+    });
+    form.on('close', function() {
+      res.end('OK');
+      callback(null, parts);
+    });
+    form.parse(req);
+
+    function callback() {
+      var realCallback = cb;
+      cb = function() {};
+      realCallback.apply(null, arguments);
+    }
+  });
+
+  var socket = net.createConnection(PORT);
+  var file = fs.createReadStream(FIXTURE_PATH + '/http/' + name);
+
+  file.pipe(socket, {end: false});
+  socket.on('data', function () {
+    socket.end();
+  });
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-multiparty.git



More information about the Pkg-javascript-commits mailing list