Skip to content

Commit

Permalink
Merge pull request hapijs#854 from spumko/multipart
Browse files Browse the repository at this point in the history
Move to use multiparty
  • Loading branch information
Eran Hammer committed May 14, 2013
2 parents bafc11d + a7411e5 commit 2140e91
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
63 changes: 48 additions & 15 deletions lib/payload.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Load modules

var Stream = require('stream');
var Zlib = require('zlib');
var Querystring = require('querystring');
var Formidable = require('formidable');
var Multiparty = require('multiparty');
var Boom = require('boom');
var Utils = require('./utils');

Expand All @@ -27,7 +28,7 @@ exports.read = function (request, next) {

// Levels are: 'stream', 'raw', 'parse', 'try'

var level = request.route.payload.mode || (request.route.validate.payload || request.method === 'post' || request.method === 'put'|| request.method === 'patch' ? 'parse' : 'stream');
var level = request.route.payload.mode || (request.route.validate.payload || request.method === 'post' || request.method === 'put' || request.method === 'patch' ? 'parse' : 'stream');
if (level === 'stream') {
return next();
}
Expand Down Expand Up @@ -223,38 +224,70 @@ internals.parse = function (payload, mime, headers, callback) {

if (mime === 'multipart/form-data') {

var stream = new internals.Replay(headers, payload);

var form = new Multiparty.Form();
var data = {};
var form = new Formidable.IncomingForm();

var processData = function (name, val) {
form.once('error', function (err) {

return callback(Boom.badRequest('Invalid request multipart payload format'));
});

var set = function (name, value) {

if (data[name]) {
data[name] = [data[name], val];
if (!data.hasOwnProperty(name)) {
data[name] = value;
}
else if (data[name] instanceof Array) {
data[name].push(value);
}
else {
data[name] = val;
data[name] = [data[name], value];
}
};

form.on('field', processData);
form.on('file', processData);
form.on('file', function (name, value) {

form.once('error', function () {
value.name = value.originalFilename; // For backwards compatibility with formidable
value.type = value.headers && value.headers['content-type'];
delete value.ws;
set(name, value);
});

form.removeAllListeners();
return callback(Boom.badRequest('Invalid request multipart payload format'));
form.on('field', function (name, value) {

set(name, value);
});

form.once('end', function () {
form.once('close', function () {

stream.removeAllListeners();
form.removeAllListeners();
return callback(null, data);
});

form.writeHeaders(headers);
form.write(payload);
form.parse(stream);

return;
}

return callback(new Boom(415)); // Unsupported Media Type
};


internals.Replay = function (headers, payload) {

Stream.Readable.call(this);
this.headers = headers;
this.__payload = payload;
};

Utils.inherits(internals.Replay, Stream.Readable);


internals.Replay.prototype._read = function (size) {

this.push(this.__payload);
this.push(null);
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"router"
],
"engines": {
"node": "0.10.x"
"node": ">=0.10.5"
},
"dependencies": {
"hoek": "0.8.x",
Expand All @@ -38,7 +38,7 @@
"cryptiles": "0.2.x",
"iron": "0.3.x",
"async": "0.2.x",
"formidable": "1.0.13",
"multiparty": "2.1.x",
"mime": "1.2.x",
"lru-cache": "2.3.x",
"optimist": "0.4.x",
Expand Down
26 changes: 19 additions & 7 deletions test/integration/payload.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,23 @@ describe('Payload', function () {
request.reply(request.payload);
};

var _server = new Hapi.Server('0.0.0.0', 0);
_server.route({ method: 'POST', path: '/invalid', handler: invalidHandler });
_server.route({ method: 'POST', path: '/echo', handler: echo });
var server = new Hapi.Server('0.0.0.0', 0);
server.route({ method: 'POST', path: '/invalid', handler: invalidHandler });
server.route({ method: 'POST', path: '/echo', handler: echo });

var multipartPayload =
'--AaB03x\r\n' +
'content-disposition: form-data; name="x"\r\n' +
'\r\n' +
'First\r\n' +
'--AaB03x\r\n' +
'content-disposition: form-data; name="x"\r\n' +
'\r\n' +
'Second\r\n' +
'--AaB03x\r\n' +
'content-disposition: form-data; name="x"\r\n' +
'\r\n' +
'Third\r\n' +
'--AaB03x\r\n' +
'content-disposition: form-data; name="field1"\r\n' +
'\r\n' +
Expand All @@ -576,7 +588,7 @@ describe('Payload', function () {

it('returns an error on missing boundary in content-type header', function (done) {

_server.inject({ method: 'POST', url: '/invalid', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data' } }, function (res) {
server.inject({ method: 'POST', url: '/invalid', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data' } }, function (res) {

expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
Expand All @@ -586,7 +598,7 @@ describe('Payload', function () {

it('returns an error on empty separator in content-type header', function (done) {

_server.inject({ method: 'POST', url: '/invalid', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=' } }, function (res) {
server.inject({ method: 'POST', url: '/invalid', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=' } }, function (res) {

expect(res.result).to.exist;
expect(res.result.code).to.equal(400);
Expand All @@ -596,9 +608,9 @@ describe('Payload', function () {

it('returns parsed multipart data', function (done) {

_server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' } }, function (res) {
server.inject({ method: 'POST', url: '/echo', payload: multipartPayload, headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' } }, function (res) {

expect(Object.keys(res.result).length).to.equal(2);
expect(Object.keys(res.result).length).to.equal(3);
expect(res.result.field1).to.exist;
expect(res.result.field1.length).to.equal(2);
expect(res.result.field1[1]).to.equal('Repeated name segment');
Expand Down

0 comments on commit 2140e91

Please sign in to comment.