Skip to content

Commit

Permalink
v0.1.0 Beta: No more urlencoded support. Multipart only. Removed conn…
Browse files Browse the repository at this point in the history
…ect-busboy dependency in exchange for directly using Busboy. Updated README with breaking changes. This commit also fixes #11
  • Loading branch information
richardgirges committed Feb 17, 2017
1 parent 3d72084 commit 92d7ad5
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 99 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Description
Simple express middleware for uploading files.

# Version 0.1.0 Breaking Change
As of `v0.1.0`, there is NO MORE `application/x-www-form-urlencoded` SUPPORT!

If you want to parse `urlencoded` requests, [use body-parser](https://github.com/expressjs/body-parser#bodyparserurlencodedoptions).

Moving forward, express-fileupload is considered a "multipart" solution only.

# Install
```bash
# With NPM
Expand Down Expand Up @@ -113,10 +120,6 @@ Option | Acceptable Values | Details
--- | --- | ---
safeFileNames | <ul><li><code>false</code>&nbsp;**(default)**</li><li><code>true</code></li><li>regex</li></ul> | Strips characters from the upload's filename. You can use custom regex to determine what to strip. If set to `true`, non-alphanumeric characters _except_ dashes and underscores will be stripped. This option is off by default.<br /><br />**Example #1 (strip slashes from file names):** `app.use(fileUpload({ safeFileNames: /\\/g }))`<br />**Example #2:** `app.use(fileUpload({ safeFileNames: true }))`

# Known Bugs
##### If you're using bodyParser middleware
Add `app.use(fileUpload())` *AFTER* `app.use(bodyParser.json)` and any other `bodyParser` middlewares! This limitation will be investigated in an upcoming release.

# Help Wanted
Pull Requests are welcomed!

Expand Down
242 changes: 158 additions & 84 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,169 @@
var busboy = require('connect-busboy');
var Busboy = require('busboy');
var fs = require('fs-extra');
var streamifier = require('streamifier');

var ACCEPTABLE_MIME = /^(?:multipart\/.+)$/i;
var UNACCEPTABLE_METHODS = [
'GET',
'HEAD'
];

module.exports = function(options) {
options = options || {};

return function(req, res, next) {
return busboy(options)(req, res, function() {

// If no busboy req obj, then no uploads are taking place
if (!req.busboy)
if (!hasBody(req) || !hasAcceptableMethod(req) || !hasAcceptableMime(req))
return next();

req.files = null;

req.busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
req.body = req.body || {};

var prev = req.body[fieldname];

if (!prev)
return req.body[fieldname] = val;

if (Array.isArray(prev))
return prev.push(val);

req.body[fieldname] = [prev, val];
});

req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
var buf = new Buffer(0);
var safeFileNameRegex = /[^\w-]/g;

file.on('data', function(data) {
buf = Buffer.concat([buf, data]);

if (options.debug)
return console.log('Uploading %s -> %s', fieldname, filename);
});

file.on('end', function() {
if (!req.files)
req.files = {};

// see: https://github.com/richardgirges/express-fileupload/issues/14
// firefox uploads empty file in case of cache miss when f5ing page.
// resulting in unexpected behavior. if there is no file data, the file is invalid.
if(!buf.length)
return;

if (options.safeFileNames) {
if (typeof options.safeFileNames === 'object')
safeFileNameRegex = options.safeFileNames;

filename = filename.replace(safeFileNameRegex, '');
}

var newFile = {
name: filename,
data: buf,
encoding: encoding,
mimetype: mimetype,
mv: function(path, callback) {
var fstream;
fstream = fs.createWriteStream(path);
streamifier.createReadStream(buf).pipe(fstream);
fstream.on('error', function(error) {
if (callback)
callback(error);
});
fstream.on('close', function() {
if (callback)
callback(null);
});
}
};

if (!req.files.hasOwnProperty(fieldname)) {
req.files[fieldname] = newFile;
} else {
if (req.files[fieldname] instanceof Array)
req.files[fieldname].push(newFile);
else
req.files[fieldname] = [req.files[fieldname], newFile];
}
});
});

req.busboy.on('finish', next);

req.pipe(req.busboy);
});
processMultipart(options, req, res, next);
};
};


/**
* Processes multipart request
* Builds a req.body object for fields
* Builds a req.files object for files
* @param {Object} options expressFileupload and Busboy options
* @param {Object} req Express request object
* @param {Object} res Express response object
* @param {Function} next Express next method
* @return {void}
*/
function processMultipart(options, req, res, next) {
var busboyOptions = {};
var busboy;

req.files = null;

// Build busboy config
for (var k in options) {
busboyOptions[k] = options[k];
}

// Attach request headers to busboy config
busboyOptions.headers = req.headers;

// Init busboy instance
busboy = new Busboy(busboyOptions);

// Build multipart req.body fields
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mime) {
req.body = req.body || {};

var prev = req.body[fieldname];

if (!prev)
return req.body[fieldname] = val;

if (Array.isArray(prev))
return prev.push(val);

req.body[fieldname] = [prev, val];
});

// Build req.files fields
busboy.on('file', function(fieldname, file, filename, encoding, mime) {
var buf = new Buffer(0);
var safeFileNameRegex = /[^\w-]/g;

file.on('data', function(data) {
buf = Buffer.concat([buf, data]);

if (options.debug)
return console.log('Uploading %s -> %s', fieldname, filename);
});

file.on('end', function() {
if (!req.files)
req.files = {};

// see: https://github.com/richardgirges/express-fileupload/issues/14
// firefox uploads empty file in case of cache miss when f5ing page.
// resulting in unexpected behavior. if there is no file data, the file is invalid.
if(!buf.length)
return;

if (options.safeFileNames) {
if (typeof options.safeFileNames === 'object')
safeFileNameRegex = options.safeFileNames;

filename = filename.replace(safeFileNameRegex, '');
}

var newFile = {
name: filename,
data: buf,
encoding: encoding,
mimetype: mime,
mv: function(path, callback) {
var fstream = fs.createWriteStream(path);

streamifier.createReadStream(buf).pipe(fstream);

fstream.on('error', function(error) {
if (callback)
callback(error);
});

fstream.on('close', function() {
if (callback)
callback(null);
});
}
};

// Non-array fields
if (!req.files.hasOwnProperty(fieldname)) {
req.files[fieldname] = newFile;
} else {
// Array fields
if (req.files[fieldname] instanceof Array)
req.files[fieldname].push(newFile);
else
req.files[fieldname] = [req.files[fieldname], newFile];
}
});
});

busboy.on('finish', next);

req.pipe(busboy);
}


/**************************************************************
* Methods below were copied from, or heavily inspired by
* the Connect and connect-busboy packages
**************************************************************/

/**
* Ensures the request is not using a non-compliant multipart method
* such as GET or HEAD
* @param {Object} req Express req object
* @return {Boolean}
*/
function hasAcceptableMethod(req) {
return (UNACCEPTABLE_METHODS.indexOf(req.method) < 0);
}

/**
* Ensures that only multipart requests are processed by express-fileupload
* @param {Object} req Express req object
* @return {Boolean}
*/
function hasAcceptableMime(req) {
var str = (req.headers['content-type'] || '').split(';')[0];

return ACCEPTABLE_MIME.test(str);
}

/**
* Ensures the request contains a content body
* @param {Object} req Express req object
* @return {Boolean}
*/
function hasBody(req) {
return ('transfer-encoding' in req.headers) ||
('content-length' in req.headers && req.headers['content-length'] !== '0');
}
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
{
"name": "express-fileupload",
"version": "0.0.7",
"version": "0.1.0-beta",
"author": "Richard Girges <richardgirges@gmail.com>",
"description": "Simple express file upload middleware that wraps around connect-busboy",
"description": "Simple express file upload middleware that wraps around Busboy",
"main": "./lib/index",
"dependencies": {
"busboy": "^0.2.14",
"connect-busboy": "0.0.2",
"fs-extra": "^0.22.1",
"streamifier": "^0.1.1"
},
Expand All @@ -20,7 +19,7 @@
"forms",
"multipart",
"files",
"connect-busboy",
"busboy",
"middleware"
],
"licenses": [
Expand Down
8 changes: 1 addition & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ brace-expansion@^1.0.0:
balanced-match "^0.4.1"
concat-map "0.0.1"

busboy@*, busboy@^0.2.14:
busboy@^0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
dependencies:
Expand All @@ -35,12 +35,6 @@ concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"

connect-busboy@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/connect-busboy/-/connect-busboy-0.0.2.tgz#ac5c9c96672171885e576c66b2bfd95d3bb11097"
dependencies:
busboy "*"

content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
Expand Down

0 comments on commit 92d7ad5

Please sign in to comment.