[Pkg-javascript-commits] [node-multiparty] 04/13: Imported Upstream version 3.3.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 92d60f58e6f28047d15d2fa2af562910eddbf136
Author: Andrew Kelley <superjoe30 at gmail.com>
Date:   Fri Jul 4 22:32:46 2014 +0000

    Imported Upstream version 3.3.0
---
 .npmignore                                         |   2 +
 CHANGELOG.md                                       |  78 ++++++++
 README.md                                          |  97 ++++++++--
 index.js                                           | 211 +++++++++++++++------
 package.json                                       |  16 +-
 test/fixture/file/blank.gif                        | Bin
 .../charset-last.http}                             |   4 +-
 .../plain.txt.http => content-type/charset.http}   |   4 +-
 .../custom-equal-sign.http}                        |  10 +-
 .../plain.txt.http => content-type/custom.http}    |   4 +-
 test/fixture/http/encoding/plain.txt.http          |   2 +-
 test/fixture/http/no-filename/empty.http           |   6 +
 test/fixture/http/no-filename/filename-name.http   |   2 +-
 test/fixture/http/no-filename/generic.http         |   2 +-
 test/fixture/js/content-type.js                    |  44 +++++
 test/fixture/js/no-filename.js                     |   3 +
 test/standalone/test-chunked.js                    |  43 +++++
 test/standalone/test-epilogue-last-chunk.js        |  44 +++++
 test/standalone/test-error-listen-after-parse.js   |  20 ++
 test/standalone/test-error-unpipe.js               |  32 ++++
 test/standalone/test-issue-15.js                   |  16 +-
 test/standalone/test-issue-21.js                   |  39 +++-
 test/standalone/test-issue-32.js                   |  38 ++++
 .../{test-issue-5.js => test-issue-36.js}          |  22 ++-
 test/standalone/test-issue-4.js                    |   2 +
 test/standalone/test-issue-5.js                    |   3 +-
 test/standalone/test-max-fields.js                 |  52 +++++
 ...est-issue-4.js => test-max-files-size-exact.js} |  37 ++--
 .../{test-issue-4.js => test-max-files-size.js}    |  41 ++--
 test/standalone/test-parse-type-error.js           |  39 ++++
 test/standalone/test-req-encoding.js               |  39 ++++
 test/test.js                                       | 150 ++++++++++-----
 32 files changed, 874 insertions(+), 228 deletions(-)

diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..d457948
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,2 @@
+test/ 
+examples/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea54d9a..5663f4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,81 @@
+### 3.3.0
+
+ * Douglas Christopher Wilson (4):
+   - Expand form.parse in README
+   - Always emit close after all parts ended
+   - Remove execute bit from files
+   - Fix callback hang in node.js 0.8 on errors
+
+ * Andrew Kelley (1):
+   - tests refactor
+
+ * Thanasis Polychronakis (1):
+   - docs: fix code error in readme
+
+### 3.2.9
+
+ * Fix attaching error listeners directly after form.parse
+ * Fix to not synchronously invoke callback to form.parse on error
+
+### 3.2.8
+
+ * Fix developer accidentally corrupting data
+ * Fix handling epilogue in a separate chunk
+ * Fix initial check errors to use supplied callback
+
+### 3.2.7
+
+ * Fix errors hanging responses in callback-style
+
+### 3.2.6
+
+ * Fix maxFields to error on field after max
+
+### 3.2.5
+
+ * Support boundary containing equal sign (thanks [garel-a])
+
+### 3.2.4
+
+ * Keep part.byteCount undefined in chunked encoding (thanks [dougwilson])
+ * Fix temp files not always cleaned up (thanks [dougwilson])
+
+### 3.2.3
+
+ * improve parsing boundary attribute from Content-Type (thanks [dougwilson])
+
+### 3.2.2
+
+ * fix error on empty payloads (thanks [dougwilson])
+
+### 3.2.1
+
+ * fix maxFilesSize overcalculation bug (thanks [dougwilson] and
+   [timothysoehnlin])
+
+### 3.2.0
+
+ * add maxFilesSize for autoFiles (thanks [dougwilson])
+
+### 3.1.2
+
+ * exclude test files from npm package (thanks Dag Einar Monsen)
+ * fix incorrectly using autoFields value for autoFiles (thanks RG72)
+
+### 3.1.1
+
+ * fix not emitting 'close' after all part 'end' events
+
+### 3.1.0
+
+ * support UTF8 filename in Content-Disposition (thanks baoshan)
+
+### 3.0.0
+
+ * form.parse callback API changed in a compatibility-breaking manner.
+   sorry, I know it sucks but the way I had it before is misleading and
+   inconsistent.
+
 ### 2.2.0
 
  * additional callback API to support multiple files with same field name
diff --git a/README.md b/README.md
index f149ac0..120e5c4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-[![Build Status](https://travis-ci.org/superjoe30/node-multiparty.png?branch=master)](https://travis-ci.org/superjoe30/node-multiparty)
-# multiparty
+# multiparty [![Build Status](https://travis-ci.org/andrewrk/node-multiparty.svg?branch=master)](https://travis-ci.org/andrewrk/node-multiparty) [![NPM version](https://badge.fury.io/js/multiparty.svg)](http://badge.fury.io/js/multiparty)
 
 Parse http requests with content-type `multipart/form-data`, also known as file uploads.
 
@@ -71,12 +70,15 @@ 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
+ * `maxFieldsSize` - 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.
+ * `maxFilesSize` - Only relevant when `autoFiles` is `true`.  Limits the
+   total bytes accepted for all files combined. If this value is exceeded,
+   an `error` event is emitted. The default is `Infinity`.
  * `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`
@@ -90,25 +92,78 @@ Creates a new form. Options:
 
 #### 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:
+Parses an incoming node.js `request` containing form data.This will cause
+`form` to emit events based off the incoming request.
 
 ```js
-form.parse(req, function(err, fieldsObject, filesObject, fieldsList, filesList) {
-  // ...
+var count = 0;
+var form = new multiparty.Form();
+
+// Errors may be emitted
+form.on('error', function(err) {
+  console.log('Error parsing form: ' + err.stack);
+});
+
+// Parts are emitted when parsing the form
+form.on('part', function(part) {
+  // You *must* act on the part by reading it
+  // NOTE: if you want to ignore it, just call "part.resume()"
+
+  if (part.filename === null) {
+    // filename is "null" when this is a field and not a file
+    console.log('got field named ' + part.name);
+    // ignore field's content
+    part.resume();
+  }
+
+  if (part.filename !== null) {
+    // filename is not "null" when this is a file
+    count++;
+    console.log('got file named ' + part.name);
+    // ignore file's content here
+    part.resume();
+  }
+});
+
+// Close emitted after form parsed
+form.on('close', function() {
+  console.log('Upload completed!');
+  res.setHeader('text/plain');
+  res.end('Received ' + count + ' files');
 });
+
+// Parse req
+form.parse(req);
+
 ```
 
-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`.
+If `cb` is provided, `autoFields` and `autoFiles` are set to `true` and all
+fields and files are collected and passed to the callback, removing the need to
+listen to any events on `form`. This is for convenience when wanted to read
+everything, but be careful as this will write all uploaded files to the disk,
+even ones you may not be interested in.
 
-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]`.
+```js
+form.parse(req, function(err, fields, files) {
+  Object.keys(fields).forEach(function(name) {
+    console.log('got field named ' + name);
+  });
+
+  Object.keys(files).forEach(function(name) {
+    console.log('got file named ' + name);
+  });
+
+  console.log('Upload completed!');
+  res.setHeader('text/plain');
+  res.end('Received ' + files.length + ' files');
+});
+```
+
+`fields` is an object where the property names are field names and the values
+are arrays of field values.
+
+`files` is an object where the property names are field names and the values
+are arrays of file objects.
 
 #### form.bytesReceived
 
@@ -122,8 +177,12 @@ The expected number of bytes in this form.
 
 #### 'error' (err)
 
-You definitely want to handle this event. If not your server *will* crash when
-users submit bogus multipart requests!
+Unless you supply a callback to `form.parse`, you definitely want to handle
+this event. Otherwise your server *will* crash when users submit bogus
+multipart requests!
+
+Only one 'error' event can ever be emitted, and if an 'error' event is
+emitted, then 'close' will not be emitted.
 
 #### 'part' (part)
 
@@ -159,6 +218,8 @@ event is emitted. This is typically when you would send your response.
 listener, multiparty automatically sets `form.autoFiles` to `true` and will
 stream uploads to disk for you. 
 
+**The max bytes accepted per request can be specified with `maxFilesSize`.**
+
  * `name` - the field name for this file
  * `file` - an object with these properties:
    - `fieldName` - same as `name` - the field name for this file
diff --git a/index.js b/index.js
old mode 100755
new mode 100644
index 71c88ae..890dd37
--- a/index.js
+++ b/index.js
@@ -20,7 +20,8 @@ var START = 0
   , PART_DATA_START = 8
   , PART_DATA = 9
   , PART_END = 10
-  , END = 11
+  , CLOSE_BOUNDARY = 11
+  , END = 12
 
   , LF = 10
   , CR = 13
@@ -30,7 +31,8 @@ var START = 0
   , A = 97
   , Z = 122
 
-var CONTENT_TYPE_RE = /^multipart\/(form-data|related);\s*boundary=(?:"([^"]+)"|([^;]+))$/i;
+var CONTENT_TYPE_RE = /^multipart\/(?:form-data|related)(?:;|$)/i;
+var CONTENT_TYPE_PARAM_RE = /;\s*([^=]+)=(?:"([^"]+)"|([^;]+))/gi;
 var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16}).*/;
 var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
 
@@ -45,10 +47,11 @@ function Form(options) {
   self.finished = false;
 
   self.autoFields = !!options.autoFields;
-  self.autoFiles = !!options.autoFields;
+  self.autoFiles = !!options.autoFiles;
 
   self.maxFields = options.maxFields || 1000;
   self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024;
+  self.maxFilesSize = options.maxFilesSize || Infinity;
   self.uploadDir = options.uploadDir || os.tmpDir();
   self.encoding = options.encoding || 'utf8';
   self.hash = options.hash || false;
@@ -59,6 +62,7 @@ function Form(options) {
   self.openedFiles = [];
   self.totalFieldSize = 0;
   self.totalFieldCount = 0;
+  self.totalFileSize = 0;
   self.flushing = 0;
 
   self.backpressure = false;
@@ -76,79 +80,115 @@ function Form(options) {
 }
 
 Form.prototype.parse = function(req, cb) {
+  var called = false;
   var self = this;
+  var waitend = true;
 
-  // if the user supplies a callback, this implies autoFields and autoFiles
   if (cb) {
+    // if the user supplies a callback, this implies autoFields and autoFiles
     self.autoFields = true;
     self.autoFiles = true;
+
+    var fields = {};
+    var files = {};
+    self.on('error', function(err) {
+      if (called) return;
+
+      called = true;
+
+      // wait for req events to fire
+      process.nextTick(function() {
+        if (waitend && req.readable) {
+          // dump rest of request
+          req.resume();
+          req.once('end', function() {
+            cb(err);
+          });
+          return;
+        }
+
+        cb(err);
+      });
+    });
+    self.on('field', function(name, value) {
+      var fieldsArray = fields[name] || (fields[name] = []);
+      fieldsArray.push(value);
+    });
+    self.on('file', function(name, file) {
+      var filesArray = files[name] || (files[name] = []);
+      filesArray.push(file);
+    });
+    self.on('close', function() {
+      cb(null, fields, files);
+    });
   }
 
   self.handleError = handleError;
   self.bytesExpected = getBytesExpected(req.headers);
 
-  req.on('error', handleError);
+  req.on('end', onReqEnd);
+  req.on('error', function(err) {
+    waitend = false;
+    handleError(err);
+  });
   req.on('aborted', onReqAborted);
 
+  var state = req._readableState;
+  if (req._decoder || (state && (state.encoding || state.decoder))) {
+    // this is a binary protocol
+    // if an encoding is set, input is likely corrupted
+    validationError(new Error('request encoding must not be set'));
+    return;
+  }
+
   var contentType = req.headers['content-type'];
   if (!contentType) {
-    handleError(new Error('missing content-type header'));
+    validationError(new Error('missing content-type header'));
     return;
   }
 
-  var m = contentType.match(CONTENT_TYPE_RE);
+  var m = CONTENT_TYPE_RE.exec(contentType);
   if (!m) {
-    handleError(new Error('unrecognized content-type: ' + contentType));
+    validationError(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);
-    });
+  var boundary;
+  CONTENT_TYPE_PARAM_RE.lastIndex = m.index + m[0].length - 1;
+  while ((m = CONTENT_TYPE_PARAM_RE.exec(contentType))) {
+    if (m[1].toLowerCase() !== 'boundary') continue;
+    boundary = m[2] || m[3];
+    break;
+  }
+
+  if (!boundary) {
+    validationError(new Error('content-type missing boundary: ' + require('util').inspect(m)));
+    return;
   }
 
+  setUpParser(self, boundary);
+  req.pipe(self);
+
   function onReqAborted() {
+    waitend = false;
     self.emit('aborted');
     handleError(new Error("Request aborted"));
   }
 
+  function onReqEnd() {
+    waitend = false;
+  }
+
   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);
-      }
+      req.removeListener('end', onReqEnd);
     }
 
     self.openedFiles.forEach(function(file) {
-      file.ws.destroy();
-      fs.unlink(file.path, function(err) {
-        // this is already an error condition, ignore 2nd error
-      });
+      destroyFile(self, file);
     });
     self.openedFiles = [];
 
@@ -157,9 +197,15 @@ Form.prototype.parse = function(req, cb) {
     }
   }
 
+  function validationError(err) {
+    // handle error on next tick for event listeners to attach
+    process.nextTick(handleError.bind(null, err))
+  }
 };
 
 Form.prototype._write = function(buffer, encoding, cb) {
+  if (this.error) return;
+
   var self = this
     , i = 0
     , len = buffer.length
@@ -183,7 +229,11 @@ Form.prototype._write = function(buffer, encoding, cb) {
         state = START_BOUNDARY;
         /* falls through */
       case START_BOUNDARY:
-        if (index === boundaryLength - 2) {
+        if (index === boundaryLength - 2 && c === HYPHEN) {
+          index = 1;
+          state = CLOSE_BOUNDARY;
+          break;
+        } else if (index === boundaryLength - 2) {
           if (c !== CR) return self.handleError(new Error("Expected CR Received " + c));
           index++;
           break;
@@ -288,8 +338,9 @@ Form.prototype._write = function(buffer, encoding, cb) {
             // CR = part boundary
             self.partBoundaryFlag = true;
           } else if (c === HYPHEN) {
-            // HYPHEN = end boundary
-            self.lastBoundaryFlag = true;
+            index = 1;
+            state = CLOSE_BOUNDARY;
+            break;
           } else {
             index = 0;
           }
@@ -303,14 +354,6 @@ Form.prototype._write = function(buffer, encoding, cb) {
               state = HEADER_FIELD_START;
               break;
             }
-          } else if (self.lastBoundaryFlag) {
-            if (c === HYPHEN) {
-              self.onParsePartEnd();
-              self.end();
-              state = END;
-            } else {
-              index = 0;
-            }
           } else {
             index = 0;
           }
@@ -333,6 +376,16 @@ Form.prototype._write = function(buffer, encoding, cb) {
         }
 
         break;
+      case CLOSE_BOUNDARY:
+        if (c !== HYPHEN) return self.handleError(new Error("Expected HYPHEN Received " + c));
+        if (index === 1) {
+          self.onParsePartEnd();
+          state = END;
+        } else if (index > 1) {
+          return self.handleError(new Error("Parser has invalid state."));
+        }
+        index++;
+        break;
       case END:
         break;
       default:
@@ -433,7 +486,7 @@ Form.prototype.onParseHeadersEnd = function(offset) {
   }
 
   self.totalFieldCount += 1;
-  if (self.totalFieldCount >= self.maxFields) {
+  if (self.totalFieldCount > self.maxFields) {
     return new Error("maxFields " + self.maxFields + " exceeded.");
   }
 
@@ -446,16 +499,21 @@ Form.prototype.onParseHeadersEnd = function(offset) {
   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.destStream.byteCount = partContentLength ? parseInt(partContentLength, 10) :
+    self.bytesExpected ? (self.bytesExpected - self.destStream.byteOffset -
+      self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN) :
+    undefined;
 
   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);
+  } else {
+    beginFlush(self);
+    self.destStream.on('end', function(){
+      endFlush(self);
+    });
   }
 }
 
@@ -493,18 +551,31 @@ function maybeClose(self) {
   }
 }
 
+function destroyFile(self, file) {
+  if (!file.ws) return;
+  file.ws.removeAllListeners('close');
+  file.ws.on('close', function() {
+    fs.unlink(file.path, function(err) {
+      if (err && !self.error) self.handleError(err);
+    });
+  });
+  file.ws.destroy();
+}
+
 function handleFile(self, fileStream) {
-  beginFlush(self);
+  if (self.error) return;
   var file = {
     fieldName: fileStream.name,
     originalFilename: fileStream.filename,
     path: uploadPath(self.uploadDir, fileStream.filename),
     headers: fileStream.headers,
   };
+  beginFlush(self); // flush to write stream
   file.ws = fs.createWriteStream(file.path);
   self.openedFiles.push(file);
   fileStream.pipe(file.ws);
   var counter = new StreamCounter();
+  var seenBytes = 0;
   fileStream.pipe(counter);
   var hashWorkaroundStream
     , hash = null;
@@ -518,6 +589,17 @@ function handleFile(self, fileStream) {
     };
     fileStream.pipe(hashWorkaroundStream);
   }
+  counter.on('progress', function() {
+    var deltaBytes = counter.bytes - seenBytes;
+    seenBytes += deltaBytes;
+    self.totalFileSize += deltaBytes;
+    if (self.totalFileSize > self.maxFilesSize) {
+      if (hashWorkaroundStream) fileStream.unpipe(hashWorkaroundStream);
+      fileStream.unpipe(counter);
+      fileStream.unpipe(file.ws);
+      self.handleError(new Error("maxFilesSize " + self.maxFilesSize + " exceeded"));
+    }
+  });
   file.ws.on('error', function(err) {
     if (!self.error) self.handleError(err);
   });
@@ -527,6 +609,10 @@ function handleFile(self, fileStream) {
     self.emit('file', fileStream.name, file);
     endFlush(self);
   });
+  beginFlush(self); // flush from file stream
+  fileStream.on('end', function(){
+    endFlush(self);
+  });
 }
 
 function handleField(self, fieldStream) {
@@ -578,7 +664,6 @@ function setUpParser(self, boundary) {
 
   self.index = null;
   self.partBoundaryFlag = false;
-  self.lastBoundaryFlag = false;
 
   self.on('finish', function() {
     if ((self.state === HEADER_FIELD_START && self.index === 0) ||
@@ -602,7 +687,15 @@ function uploadPath(baseDir, filename) {
 
 function parseFilename(headerValue) {
   var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
-  if (!m) return;
+  if (!m) {
+    m = headerValue.match(/\bfilename\*=utf-8\'\'(.*?)($|; )/i);
+    if (m) {
+      m[1] = decodeURI(m[1]);
+    }
+    else {
+      return;
+    }
+  }
 
   var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
   filename = filename.replace(/%22/g, '"');
diff --git a/package.json b/package.json
index 65e5757..260ae68 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
 {
   "name": "multiparty",
-  "version": "2.2.0",
+  "version": "3.3.0",
   "description": "multipart/form-data parser which supports streaming",
   "repository": {
     "type": "git",
-    "url": "git at github.com:superjoe30/node-multiparty.git"
+    "url": "git at github.com:andrewrk/node-multiparty.git"
   },
   "keywords": [
     "file",
@@ -14,15 +14,15 @@
     "s3"
   ],
   "devDependencies": {
-    "findit": "0.1.1",
-    "hashish": "0.0.4",
-    "mocha": "~1.8.2",
+    "findit": "~2.0.0",
+    "mkdirp": "~0.5.0",
+    "pend": "~1.1.1",
     "request": "~2.16.6",
-    "mkdirp": "~0.3.5",
-    "superagent": "~0.14.1"
+    "rimraf": "~2.2.8",
+    "superagent": "~0.18.0"
   },
   "scripts": {
-    "test": "ulimit -n 500 && mocha --timeout 4000 --reporter spec --recursive test/test.js"
+    "test": "ulimit -n 500 && node test/test.js"
   },
   "engines": {
     "node": ">=0.8.0"
diff --git a/test/fixture/file/blank.gif b/test/fixture/file/blank.gif
old mode 100755
new mode 100644
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/content-type/charset-last.http
similarity index 85%
copy from test/fixture/http/encoding/plain.txt.http
copy to test/fixture/http/content-type/charset-last.http
index 5e85ad6..8ad9dd4 100644
--- a/test/fixture/http/encoding/plain.txt.http
+++ b/test/fixture/http/content-type/charset-last.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
-Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
-Content-Length: 221
+Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ; charset=utf-8
+Content-Length: 213
 
 ------TLV0SrKD4z1TRxRhAPUvZ
 Content-Disposition: form-data; name="file"; filename="plain.txt"
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/content-type/charset.http
similarity index 68%
copy from test/fixture/http/encoding/plain.txt.http
copy to test/fixture/http/content-type/charset.http
index 5e85ad6..18ebe4b 100644
--- a/test/fixture/http/encoding/plain.txt.http
+++ b/test/fixture/http/content-type/charset.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
-Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
-Content-Length: 221
+Content-Type: multipart/form-data; charset=utf-8; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 213
 
 ------TLV0SrKD4z1TRxRhAPUvZ
 Content-Disposition: form-data; name="file"; filename="plain.txt"
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/content-type/custom-equal-sign.http
similarity index 54%
copy from test/fixture/http/encoding/plain.txt.http
copy to test/fixture/http/content-type/custom-equal-sign.http
index 5e85ad6..f97c2e5 100644
--- a/test/fixture/http/encoding/plain.txt.http
+++ b/test/fixture/http/content-type/custom-equal-sign.http
@@ -1,13 +1,13 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
-Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
-Content-Length: 221
+Content-Type: multipart/form-data; boundary=----=test; charset=utf-8
+Content-Length: 182
 
-------TLV0SrKD4z1TRxRhAPUvZ
+------=test
 Content-Disposition: form-data; name="file"; filename="plain.txt"
 Content-Type: text/plain
 Content-Transfer-Encoding: 7bit
 
-I am a plain text file
+I am a plain text file
 
-------TLV0SrKD4z1TRxRhAPUvZ--
+------=test--
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/content-type/custom.http
similarity index 68%
copy from test/fixture/http/encoding/plain.txt.http
copy to test/fixture/http/content-type/custom.http
index 5e85ad6..0a046f1 100644
--- a/test/fixture/http/encoding/plain.txt.http
+++ b/test/fixture/http/content-type/custom.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
-Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
-Content-Length: 221
+Content-Type: multipart/form-data; custom=stuff; boundary=----TLV0SrKD4z1TRxRhAPUvZ
+Content-Length: 213
 
 ------TLV0SrKD4z1TRxRhAPUvZ
 Content-Disposition: form-data; name="file"; filename="plain.txt"
diff --git a/test/fixture/http/encoding/plain.txt.http b/test/fixture/http/encoding/plain.txt.http
index 5e85ad6..a230b3d 100644
--- a/test/fixture/http/encoding/plain.txt.http
+++ b/test/fixture/http/encoding/plain.txt.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
 Content-Type: multipart/form-data; boundary=----TLV0SrKD4z1TRxRhAPUvZ
-Content-Length: 221
+Content-Length: 213
 
 ------TLV0SrKD4z1TRxRhAPUvZ
 Content-Disposition: form-data; name="file"; filename="plain.txt"
diff --git a/test/fixture/http/no-filename/empty.http b/test/fixture/http/no-filename/empty.http
new file mode 100644
index 0000000..6b5448e
--- /dev/null
+++ b/test/fixture/http/no-filename/empty.http
@@ -0,0 +1,6 @@
+POST /upload HTTP/1.1
+Host: localhost:8080
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryi3Xz4TKrYpgIdIpf
+Content-Length: 43
+
+------WebKitFormBoundaryi3Xz4TKrYpgIdIpf--
diff --git a/test/fixture/http/no-filename/filename-name.http b/test/fixture/http/no-filename/filename-name.http
index 43672a3..65261bd 100644
--- a/test/fixture/http/no-filename/filename-name.http
+++ b/test/fixture/http/no-filename/filename-name.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG
-Content-Length: 1000
+Content-Length: 209
 
 ------WebKitFormBoundarytyE4wkKlZ5CQJVTG
 Content-Disposition: form-data; filename="plain.txt"; name="upload"
diff --git a/test/fixture/http/no-filename/generic.http b/test/fixture/http/no-filename/generic.http
index e0dee27..a3f22b4 100644
--- a/test/fixture/http/no-filename/generic.http
+++ b/test/fixture/http/no-filename/generic.http
@@ -1,7 +1,7 @@
 POST /upload HTTP/1.1
 Host: localhost:8080
 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG
-Content-Length: 1000
+Content-Length: 200
 
 ------WebKitFormBoundarytyE4wkKlZ5CQJVTG
 Content-Disposition: form-data; name="upload"; filename=""
diff --git a/test/fixture/js/content-type.js b/test/fixture/js/content-type.js
new file mode 100644
index 0000000..4a23899
--- /dev/null
+++ b/test/fixture/js/content-type.js
@@ -0,0 +1,44 @@
+module.exports['charset.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'plain.txt',
+    fixture: 'plain.txt',
+    sha1: 'b31d07bac24ac32734de88b3687dddb10e976872',
+    size: 23,
+  }
+];
+
+module.exports['charset-last.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'plain.txt',
+    fixture: 'plain.txt',
+    sha1: 'b31d07bac24ac32734de88b3687dddb10e976872',
+    size: 23,
+  }
+];
+
+module.exports['custom.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'plain.txt',
+    fixture: 'plain.txt',
+    sha1: 'b31d07bac24ac32734de88b3687dddb10e976872',
+    size: 23,
+  }
+];
+
+
+// to test the regexp modification : it should accepts equal sign in boundary
+module.exports['custom-equal-sign.http'] = [
+  {
+    type: 'file',
+    name: 'file',
+    filename: 'plain.txt',
+    fixture: 'plain.txt',
+    size: 24,
+  }
+];
\ No newline at end of file
diff --git a/test/fixture/js/no-filename.js b/test/fixture/js/no-filename.js
index f03b4f0..556710b 100644
--- a/test/fixture/js/no-filename.js
+++ b/test/fixture/js/no-filename.js
@@ -1,3 +1,6 @@
+module.exports['empty.http'] = [
+];
+
 module.exports['generic.http'] = [
   {type: 'file', name: 'upload', filename: '', fixture: 'plain.txt',
   sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'},
diff --git a/test/standalone/test-chunked.js b/test/standalone/test-chunked.js
new file mode 100644
index 0000000..b2eda3f
--- /dev/null
+++ b/test/standalone/test-chunked.js
@@ -0,0 +1,43 @@
+var multiparty = require('../../')
+  , assert = require('assert')
+  , http = require('http')
+  , net = require('net');
+
+var server = http.createServer(function(req, resp) {
+  var form = new multiparty.Form();
+
+  var partCount = 0;
+  form.on('part', function(part) {
+    part.resume();
+    partCount++;
+    assert.strictEqual(typeof part.byteCount, 'undefined');
+  });
+  form.on('close', function() {
+    assert.strictEqual(partCount, 1);
+    resp.end();
+  });
+
+  form.parse(req);
+});
+server.listen(function() {
+  var socket = net.connect(server.address().port, 'localhost', function () {
+    socket.write('POST / HTTP/1.1\r\n');
+    socket.write('Host: localhost\r\n');
+    socket.write('Connection: close\r\n');
+    socket.write('Content-Type: multipart/form-data; boundary=foo\r\n');
+    socket.write('Transfer-Encoding: chunked\r\n');
+    socket.write('\r\n');
+    socket.write('7\r\n');
+    socket.write('--foo\r\n\r\n');
+    socket.write('43\r\n');
+    socket.write('Content-Disposition: form-data; name="file"; filename="plain.txt"\r\n\r\n');
+    socket.write('12\r\n');
+    socket.write('\r\nsome text here\r\n\r\n');
+    socket.write('9\r\n');
+    socket.write('--foo--\r\n\r\n');
+    socket.write('0\r\n\r\n');
+    socket.on('close', function () {
+      server.close();
+    });
+  });
+});
diff --git a/test/standalone/test-epilogue-last-chunk.js b/test/standalone/test-epilogue-last-chunk.js
new file mode 100644
index 0000000..4e7320f
--- /dev/null
+++ b/test/standalone/test-epilogue-last-chunk.js
@@ -0,0 +1,44 @@
+var multiparty = require('../../')
+  , assert = require('assert')
+  , http = require('http')
+  , net = require('net');
+
+var server = http.createServer(function(req, res) {
+  var form = new multiparty.Form();
+
+  var partCount = 0;
+  form.on('part', function(part) {
+    part.resume();
+    partCount++;
+  });
+  form.on('close', function() {
+    assert.strictEqual(partCount, 1);
+    res.end();
+  });
+
+  form.parse(req);
+});
+server.listen(function() {
+  var socket = net.connect(server.address().port, 'localhost', function () {
+    socket.write('POST / HTTP/1.1\r\n');
+    socket.write('Host: localhost\r\n');
+    socket.write('Connection: close\r\n');
+    socket.write('Content-Type: multipart/form-data; boundary=foo\r\n');
+    socket.write('Transfer-Encoding: chunked\r\n');
+    socket.write('\r\n');
+    socket.write('7\r\n');
+    socket.write('--foo\r\n\r\n');
+    socket.write('43\r\n');
+    socket.write('Content-Disposition: form-data; name="file"; filename="plain.txt"\r\n\r\n');
+    socket.write('12\r\n');
+    socket.write('\r\nsome text here\r\n\r\n');
+    socket.write('7\r\n');
+    socket.write('--foo--\r\n');
+    socket.write('2\r\n');
+    socket.write('\r\n\r\n');
+    socket.write('0\r\n\r\n');
+    socket.on('close', function () {
+      server.close();
+    });
+  });
+});
diff --git a/test/standalone/test-error-listen-after-parse.js b/test/standalone/test-error-listen-after-parse.js
new file mode 100644
index 0000000..aad1d13
--- /dev/null
+++ b/test/standalone/test-error-listen-after-parse.js
@@ -0,0 +1,20 @@
+var multiparty = require('../../')
+  , assert = require('assert')
+  , http = require('http')
+  , net = require('net')
+  , stream = require('readable-stream');
+
+var form = new multiparty.Form();
+var req = new stream.Readable();
+
+req.headers = {};
+req._read = function(){
+  this.push(new Buffer('--foo!'));
+};
+
+form.parse(req);
+
+form.on('error', function(err){
+  // verification that error emitter when attached after form.parse
+  assert.ok(err);
+});
diff --git a/test/standalone/test-error-unpipe.js b/test/standalone/test-error-unpipe.js
new file mode 100644
index 0000000..2271230
--- /dev/null
+++ b/test/standalone/test-error-unpipe.js
@@ -0,0 +1,32 @@
+var multiparty = require('../../')
+  , assert = require('assert')
+  , http = require('http')
+  , net = require('net')
+  , stream = require('readable-stream');
+
+var form = new multiparty.Form();
+var req = new stream.Readable();
+var unpiped = false;
+
+req.headers = {
+  'content-type': 'multipart/form-data; boundary=foo'
+};
+req._read = function(){
+  this.push(new Buffer('--foo!'));
+};
+
+form.on('error', function(err){
+  // verification that error event implies unpipe call
+  assert.ok(err);
+  assert.ok(unpiped, 'req was unpiped');
+  assert.equal(req._readableState.flowing, false, 'req not flowing');
+  assert.equal(req._readableState.pipesCount, 0, 'req has 0 pipes');
+});
+
+form.on('unpipe', function(){
+  unpiped = true;
+});
+
+form.parse(req)
+assert.equal(req._readableState.flowing, true, 'req flowing');
+assert.equal(req._readableState.pipesCount, 1, 'req has 1 pipe');
diff --git a/test/standalone/test-issue-15.js b/test/standalone/test-issue-15.js
index 43982fa..fdec5f6 100644
--- a/test/standalone/test-issue-15.js
+++ b/test/standalone/test-issue-15.js
@@ -3,6 +3,7 @@ var http = require('http')
   , assert = require('assert')
   , superagent = require('superagent')
   , path = require('path')
+  , fs = require('fs')
 
 var server = http.createServer(function(req, res) {
   assert.strictEqual(req.url, '/upload');
@@ -20,6 +21,7 @@ var server = http.createServer(function(req, res) {
   var fileCount = 0;
   form.on('file', function(name, file) {
     fileCount += 1;
+    fs.unlink(file.path, function () {});
   });
 
   form.parse(req, function(err, fields, files) {
@@ -40,13 +42,6 @@ server.listen(function() {
   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() {
@@ -70,13 +65,6 @@ function createRequest(separator) {
   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.
diff --git a/test/standalone/test-issue-21.js b/test/standalone/test-issue-21.js
index 155fba0..ea0fddf 100644
--- a/test/standalone/test-issue-21.js
+++ b/test/standalone/test-issue-21.js
@@ -1,4 +1,5 @@
 var assert = require('assert');
+var fs = require('fs');
 var http = require('http');
 var net = require('net');
 var multiparty = require('../../');
@@ -7,19 +8,37 @@ var client;
 var server = http.createServer(function(req, res) {
   var form = new multiparty.Form();
 
-  form.parse(req, function(err, fieldsTable, filesTable, fieldsList, filesList) {
+  form.parse(req, function(err, fields, files) {
     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");
+    var nameCount = 0;
+    var name;
+    for (name in fields) {
+      assert.strictEqual(name, "title");
+      nameCount += 1;
+
+      var values = fields[name];
+      assert.strictEqual(values.length, 1);
+      assert.strictEqual(values[0], "foofoo");
+    }
+    assert.strictEqual(nameCount, 1);
+
+    nameCount = 0;
+    for (name in files) {
+      assert.strictEqual(name, "upload");
+      nameCount += 1;
+
+      var filesList = files[name];
+      assert.strictEqual(filesList.length, 4);
+      filesList.forEach(function(file){
+        assert.strictEqual(file.fieldName, "upload");
+        fs.unlinkSync(file.path);
+      });
+    }
+    assert.strictEqual(nameCount, 1);
+
     res.end();
     client.end();
     server.close();
@@ -29,7 +48,7 @@ server.listen(function() {
   client = net.connect(server.address().port);
 
   client.write("POST /upload HTTP/1.1\r\n" +
-    "Content-Length: 728\r\n" +
+    "Content-Length: 726\r\n" +
     "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
     "\r\n" +
     "------WebKitFormBoundaryvfUZhxgsZDO7FXLF\r\n" +
diff --git a/test/standalone/test-issue-32.js b/test/standalone/test-issue-32.js
new file mode 100644
index 0000000..6e70c71
--- /dev/null
+++ b/test/standalone/test-issue-32.js
@@ -0,0 +1,38 @@
+var assert = require('assert');
+var fs = require('fs');
+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, fields, files) {
+    if (err) {
+      console.error(err.stack);
+      return;
+    }
+    assert.strictEqual(files.image[0].originalFilename, "测试文档")
+    fs.unlinkSync(files.image[0].path);
+    res.end();
+    client.end();
+    server.close();
+  });
+});
+server.listen(function() {
+  client = net.connect(server.address().port);
+
+  client.write(
+    "POST /upload HTTP/1.1\r\n" +
+    "Accept: */*\r\n" +
+    "Content-Type: multipart/form-data; boundary=\"893e5556-f402-4fec-8180-c59333354c6f\"\r\n" +
+    "Content-Length: 187\r\n" +
+    "\r\n" +
+    "--893e5556-f402-4fec-8180-c59333354c6f\r\n" +
+    "Content-Disposition: form-data; name=\"image\"; filename*=utf-8''%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3\r\n" +
+    "\r\n" +
+    "\r\n" +
+    "--893e5556-f402-4fec-8180-c59333354c6f--\r\n"
+  );
+});
diff --git a/test/standalone/test-issue-5.js b/test/standalone/test-issue-36.js
similarity index 68%
copy from test/standalone/test-issue-5.js
copy to test/standalone/test-issue-36.js
index 80eadf2..6959e7c 100644
--- a/test/standalone/test-issue-5.js
+++ b/test/standalone/test-issue-36.js
@@ -4,16 +4,22 @@ 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");
+  var form = new multiparty.Form();
+  var endCalled = false;
+  form.on('part', function(part) {
+    part.on('end', function() {
+      endCalled = true;
+    });
+    part.resume();
+  });
+  form.on('close', function() {
+    assert.ok(endCalled);
     res.end();
     client.end();
     server.close();
   });
+  form.parse(req);
 });
 server.listen(function() {
   client = net.connect(server.address().port);
@@ -25,15 +31,13 @@ server.listen(function() {
     "\r\n" +
     "hi1\r\n" +
     "\r\n";
-  var payloadSize = oneAttachment.length * attachmentCount + boundary.length;
+  var payloadSize = oneAttachment.length + 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(oneAttachment);
   client.write(boundary);
 });
diff --git a/test/standalone/test-issue-4.js b/test/standalone/test-issue-4.js
index 66b2a69..de38125 100644
--- a/test/standalone/test-issue-4.js
+++ b/test/standalone/test-issue-4.js
@@ -3,6 +3,7 @@ var http = require('http')
   , assert = require('assert')
   , superagent = require('superagent')
   , path = require('path')
+  , fs = require('fs')
 
 var server = http.createServer(function(req, res) {
   assert.strictEqual(req.url, '/upload');
@@ -20,6 +21,7 @@ var server = http.createServer(function(req, res) {
   var fileCount = 0;
   form.on('file', function(name, file) {
     fileCount += 1;
+    fs.unlink(file.path, function () {});
   });
 
   form.parse(req, function(err, fields, files) {
diff --git a/test/standalone/test-issue-5.js b/test/standalone/test-issue-5.js
index 80eadf2..2525280 100644
--- a/test/standalone/test-issue-5.js
+++ b/test/standalone/test-issue-5.js
@@ -1,4 +1,5 @@
 var assert = require('assert');
+var fs = require('fs');
 var http = require('http');
 var net = require('net');
 var multiparty = require('../../');
@@ -8,7 +9,7 @@ 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) {
+  form.parse(req, function(err) {
     assert.strictEqual(err.code, "EMFILE");
     res.end();
     client.end();
diff --git a/test/standalone/test-max-fields.js b/test/standalone/test-max-fields.js
new file mode 100644
index 0000000..29f8b35
--- /dev/null
+++ b/test/standalone/test-max-fields.js
@@ -0,0 +1,52 @@
+var http = require('http')
+  , multiparty = require('../../')
+  , assert = require('assert')
+  , superagent = require('superagent')
+  , path = require('path')
+  , fs = require('fs')
+
+var server = http.createServer(function(req, res) {
+  assert.strictEqual(req.url, '/upload');
+  assert.strictEqual(req.method, 'POST');
+
+  var form = new multiparty.Form({autoFiles:true,maxFields:2});
+
+  var first = true;
+  form.on('error', function (err) {
+    assert.ok(first);
+    first = false;
+    assert.ok(/maxFields/.test(err.message));
+  });
+
+  var fieldCount = 0;
+  form.on('field', function() {
+    fieldCount += 1;
+  });
+
+  form.parse(req, function(err, fields, files) {
+    assert.ok(!first);
+    assert.ok(fieldCount <= 2);
+    res.statusCode = 413;
+    res.end('too many fields');
+  });
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  var val = new Buffer(10 * 1024);
+  req.field('a', val);
+  req.field('b', val);
+  req.field('c', val);
+  req.on('error', function(err) {
+    assert.ifError(err);
+  });
+  req.end();
+  req.on('response', function(res) {
+    assert.equal(res.statusCode, 413);
+    server.close();
+  });
+});
+
+function fixture(name) {
+  return path.join(__dirname, '..', 'fixture', 'file', name)
+}
diff --git a/test/standalone/test-issue-4.js b/test/standalone/test-max-files-size-exact.js
similarity index 51%
copy from test/standalone/test-issue-4.js
copy to test/standalone/test-max-files-size-exact.js
index 66b2a69..57892ec 100644
--- a/test/standalone/test-issue-4.js
+++ b/test/standalone/test-max-files-size-exact.js
@@ -3,49 +3,40 @@ var http = require('http')
   , assert = require('assert')
   , superagent = require('superagent')
   , path = require('path')
+  , fs = require('fs')
 
 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 form = new multiparty.Form({autoFiles:true,maxFilesSize:768323}); // exact size of pf1y5.png
 
   var fileCount = 0;
   form.on('file', function(name, file) {
     fileCount += 1;
+    fs.unlink(file.path, function() {});
   });
 
   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();
+    assert.ifError(err);
+    assert.ok(fileCount === 1);
+    res.end('OK');
   });
 });
 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) {
+  req.attach('file0', fixture('pf1y5.png'), 'SOG1.JPG');
+  req.on('error', function(err) {
     assert.ifError(err);
-    resp.on('end', function() {
-      server.close();
-    });
+  });
+  req.end();
+  req.on('response', function(res) {
+    assert.equal(res.statusCode, 200);
+    server.close();
   });
 });
+
 function fixture(name) {
   return path.join(__dirname, '..', 'fixture', 'file', name)
 }
diff --git a/test/standalone/test-issue-4.js b/test/standalone/test-max-files-size.js
similarity index 52%
copy from test/standalone/test-issue-4.js
copy to test/standalone/test-max-files-size.js
index 66b2a69..a8e1b51 100644
--- a/test/standalone/test-issue-4.js
+++ b/test/standalone/test-max-files-size.js
@@ -3,49 +3,48 @@ var http = require('http')
   , assert = require('assert')
   , superagent = require('superagent')
   , path = require('path')
+  , fs = require('fs')
 
 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});
+  var form = new multiparty.Form({autoFiles:true,maxFilesSize:800*1024});
 
-  form.on('error', function(err) {
-    console.log(err);
-  });
-
-  form.on('close', function() {
+  var first = true;
+  form.on('error', function (err) {
+    assert.ok(first);
+    first = false;
+    assert.ok(/maxFilesSize/.test(err.message));
   });
 
   var fileCount = 0;
   form.on('file', function(name, file) {
     fileCount += 1;
+    fs.unlinkSync(file.path);
   });
 
   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();
+    assert.ok(fileCount <= 1);
+    res.statusCode = 413;
+    res.end('files too large');
   });
 });
 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) {
+  req.attach('file0', fixture('pf1y5.png'), 'SOG1.JPG');
+  req.attach('file1', fixture('pf1y5.png'), 'SOG2.JPG');
+  req.on('error', function(err) {
     assert.ifError(err);
-    resp.on('end', function() {
-      server.close();
-    });
+  });
+  req.end();
+  req.on('response', function(res) {
+    assert.equal(res.statusCode, 413);
+    server.close();
   });
 });
+
 function fixture(name) {
   return path.join(__dirname, '..', 'fixture', 'file', name)
 }
diff --git a/test/standalone/test-parse-type-error.js b/test/standalone/test-parse-type-error.js
new file mode 100644
index 0000000..42437c8
--- /dev/null
+++ b/test/standalone/test-parse-type-error.js
@@ -0,0 +1,39 @@
+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();
+
+  // this is invalid
+  delete req.headers['content-type'];
+
+  form.parse(req, function(err, fields, files) {
+    assert.ok(err);
+    assert.equal(err.message, 'missing content-type header');
+    res.statusCode = 415;
+    res.end();
+  });
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  req.attach('file0', fixture('pf1y5.png'), 'SOG1.JPG');
+  req.on('error', function(err) {
+    assert.ifError(err);
+  });
+  req.end();
+  req.on('response', function(res) {
+    assert.equal(res.statusCode, 415);
+    server.close();
+  });
+});
+
+function fixture(name) {
+  return path.join(__dirname, '..', 'fixture', 'file', name)
+}
diff --git a/test/standalone/test-req-encoding.js b/test/standalone/test-req-encoding.js
new file mode 100644
index 0000000..aebd935
--- /dev/null
+++ b/test/standalone/test-req-encoding.js
@@ -0,0 +1,39 @@
+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();
+
+  // this is invalid
+  req.setEncoding('utf8');
+
+  form.parse(req, function(err, fields, files) {
+    assert.ok(err);
+    assert.equal(err.message, 'request encoding must not be set');
+    res.statusCode = 500;
+    res.end();
+  });
+});
+server.listen(function() {
+  var url = 'http://localhost:' + server.address().port + '/upload';
+  var req = superagent.post(url);
+  req.attach('file0', fixture('pf1y5.png'), 'SOG1.JPG');
+  req.on('error', function(err) {
+    assert.ifError(err);
+  });
+  req.end();
+  req.on('response', function(res) {
+    assert.equal(res.statusCode, 500);
+    server.close();
+  });
+});
+
+function fixture(name) {
+  return path.join(__dirname, '..', 'fixture', 'file', name)
+}
diff --git a/test/test.js b/test/test.js
index 199d5cd..83f65bc 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,64 +1,93 @@
-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')
+var spawn = require('child_process').spawn;
+var findit = require('findit');
+var path = require('path');
+var Pend = require('pend');
+var rimraf = require('rimraf');
+var fs = require('fs');
+var http = require('http');
+var net = require('net');
+var assert = require('assert');
+var multiparty = require('../');
+var mkdirp = require('mkdirp');
+var STANDALONE_PATH = path.join(__dirname, 'standalone');
+var server = http.createServer();
+var PORT = 13532;
+var FIXTURE_PATH = path.join(__dirname, 'fixture');
+var TMP_PATH = path.join(__dirname, 'tmp');
 
-mkdirp.sync(TMP_PATH);
+resetTempDir(startFixtureTests);
 
-describe("fixtures", function() {
-  before(function(done) {
-    server.listen(PORT, done);
+function startFixtureTests() {
+  console.log("Fixture tests:");
+  var walker = findit(path.join(FIXTURE_PATH, 'js'));
+  var pend = new Pend();
+  pend.max = 1;
+  pend.go(function(cb) {
+    server.listen(PORT, cb);
   });
-  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,
-        }));
-      });
+  walker.on('file', function(jsPath) {
+    if (!/\.js$/.test(jsPath)) return;
+    var group = path.basename(jsPath, '.js');
+    var testInfo = require(jsPath);
+    for (var name in testInfo) {
+      var fixture = testInfo[name];
+      pend.go(createFixtureTest(group + '/' + name, fixture));
+    }
+  });
+  walker.on('end', function() {
+    pend.wait(function(err) {
+      if (err) throw err;
+      server.close(startStandaloneTests);
     });
-});
+  });
+}
 
-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);
+function startStandaloneTests() {
+  console.log("\nStandalone tests:");
+  var walker = findit(STANDALONE_PATH);
+  var pend = new Pend();
+  pend.max = 1;
+  walker.on('file', function(jsPath) {
+    if (!/\.js$/.test(jsPath)) return;
+    pend.go(function(cb) {
+      var name = path.basename(jsPath, '.js');
+      process.stdout.write(name + "...");
+      var child = spawn(process.execPath, [jsPath], { env: { TMPDIR: TMP_PATH }, stdio: 'inherit' });
+      child.on('error', function(err) {
+        throw err;
+      });
+      child.on('exit', function(code) {
+        if (code) throw new Error("exited with code " + code);
+        var tmpWalker = findit(TMP_PATH);
+        var fileNames = [];
+        tmpWalker.on('file', function(file) {
+          fileNames.push(file);
         });
-        child.on('exit', function(code) {
-          if (code) return done(new Error("exited with code " + code));
-          done();
+        tmpWalker.on('end', function() {
+          if (fileNames.length) {
+            cleanFiles(fileNames);
+            throw new Error("failed to clean up auto files: " + fileNames.join(', '));
+          } else {
+            console.log("OK");
+            cb();
+          }
         });
       });
     });
-});
+  });
+  walker.on('end', function() {
+    pend.wait(function(err) {
+      if (err) throw err;
+      console.log("\nAll tests passed.");
+    });
+  });
+}
 
-function createTest(fixture) {
-  var name = fixture.name;
-  fixture = fixture.fixture;
-  return function(done) {
+function createFixtureTest(name, fixture) {
+  return function(cb) {
+    process.stdout.write(name + "...");
     uploadFixture(name, function(err, parts) {
-      if (err) return done(err);
+      if (err) throw err;
       fixture.forEach(function(expectedPart, i) {
         var parsedPart = parts[i];
         assert.equal(parsedPart.type, expectedPart.type);
@@ -71,15 +100,32 @@ function createTest(fixture) {
           if(expectedPart.size) assert.strictEqual(file.size, expectedPart.size);
         }
       });
-      done();
+      console.log("OK");
+      cb();
     });
   };
+}
+
+function cleanFiles(fileNames) {
+  fileNames.forEach(function(fileName) {
+    fs.unlinkSync(fileName);
+  });
+}
 
+function resetTempDir(cb) {
+  rimraf(TMP_PATH, function(err) {
+    if (err) throw err;
+    mkdirp(TMP_PATH, function(err) {
+      if (err) throw err;
+      cb();
+    });
+  });
 }
 
 function uploadFixture(name, cb) {
   server.once('request', function(req, res) {
     var parts = [];
+    var fileNames = [];
     var form = new multiparty.Form({
       autoFields: true,
       autoFiles: true,
@@ -90,6 +136,7 @@ function uploadFixture(name, cb) {
     form.on('error', callback);
     form.on('file', function(name, value) {
       parts.push({type: 'file', name: name, value: value});
+      fileNames.push(value.path);
     });
     form.on('field', function(name, value) {
       parts.push({type: 'field', name: name, value: value});
@@ -104,6 +151,7 @@ function uploadFixture(name, cb) {
       var realCallback = cb;
       cb = function() {};
       realCallback.apply(null, arguments);
+      cleanFiles(fileNames);
     }
   });
 

-- 
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