diff --git a/README.md b/README.md
index 5b1a098..e87fbbe 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,7 @@ Pass in non-Busboy options directly to the middleware. These are express-fileupl
Option | Acceptable Values | Details
--- | --- | ---
safeFileNames |
false
**(default)**true
- regex
| 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.
**Example #1 (strip slashes from file names):** `app.use(fileUpload({ safeFileNames: /\\/g }))`
**Example #2:** `app.use(fileUpload({ safeFileNames: true }))`
+preserveExtension | false
**(default)**true
*Number*
| Preserves filename extension when using safeFileNames
option. If set to true
, will default to an extension length of 3. If set to *Number*
, this will be the max allowable extension length. If an extension is smaller than the extension length, it remains untouched. If the extension is longer, it is shifted.
**Example #1 (true):**
app.use(fileUpload({ safeFileNames: true, preserveExtension: true }));
*myFileName.ext* --> *myFileName.ext*
**Example #2 (max extension length 2, extension shifted):**
app.use(fileUpload({ safeFileNames: true, preserveExtension: 2 }));
*myFileName.ext* --> *myFileNamee.xt*
# Help Wanted
Pull Requests are welcomed!
diff --git a/lib/index.js b/lib/index.js
index c34d443..851a8f4 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -15,7 +15,7 @@ module.exports = function(options) {
return function(req, res, next) {
if (!hasBody(req) || !hasAcceptableMethod(req) || !hasAcceptableMime(req))
- return next();
+ return next();
processMultipart(options, req, res, next);
};
@@ -85,14 +85,40 @@ function processMultipart(options, req, res, next) {
// 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)
+ if (!buf.length)
return;
if (options.safeFileNames) {
+ let maxExtensionLength = 3;
+ let extension = '';
+
if (typeof options.safeFileNames === 'object')
safeFileNameRegex = options.safeFileNames;
- filename = filename.replace(safeFileNameRegex, '');
+ maxExtensionLength = parseInt(options.preserveExtension);
+ if (options.preserveExtension || maxExtensionLength === 0) {
+ if (isNaN(maxExtensionLength))
+ maxExtensionLength = 3;
+ else
+ maxExtensionLength = Math.abs(maxExtensionLength);
+
+ let filenameParts = filename.split('.');
+ let filenamePartsLen = filenameParts.length;
+ if (filenamePartsLen > 1) {
+ extension = filenameParts.pop();
+
+ if (extension.length > maxExtensionLength && maxExtensionLength > 0) {
+ filenameParts[filenameParts.length - 1] +=
+ '.' + extension.substr(0, extension.length - maxExtensionLength);
+ extension = extension.substr(-maxExtensionLength);
+ }
+
+ extension = maxExtensionLength ? '.' + extension.replace(safeFileNameRegex, '') : '';
+ filename = filenameParts.join('.');
+ }
+ }
+
+ filename = filename.replace(safeFileNameRegex, '').concat(extension);
}
let newFile = {
@@ -123,9 +149,9 @@ function processMultipart(options, req, res, next) {
} else {
// Array fields
if (req.files[fieldname] instanceof Array)
- req.files[fieldname].push(newFile);
+ req.files[fieldname].push(newFile);
else
- req.files[fieldname] = [req.files[fieldname], newFile];
+ req.files[fieldname] = [req.files[fieldname], newFile];
}
});
});
diff --git a/package.json b/package.json
index cb8731a..a620ca3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "express-fileupload",
- "version": "0.1.2",
+ "version": "0.1.3",
"author": "Richard Girges ",
"description": "Simple express file upload middleware that wraps around Busboy",
"main": "./lib/index",
diff --git a/test/files/basket.ball.bp b/test/files/basket.ball.bp
new file mode 100644
index 0000000..a2a1571
Binary files /dev/null and b/test/files/basket.ball.bp differ
diff --git a/test/files/my$Invalid#fileName.png123 b/test/files/my$Invalid#fileName.png123
new file mode 100644
index 0000000..510b859
Binary files /dev/null and b/test/files/my$Invalid#fileName.png123 differ
diff --git a/test/multipartFields.spec.js b/test/multipartFields.spec.js
index 034164e..bbddfe6 100644
--- a/test/multipartFields.spec.js
+++ b/test/multipartFields.spec.js
@@ -2,7 +2,7 @@
const request = require('supertest');
const server = require('./server');
-const app = server.app;
+const app = server.setup();
let mockUser = {
firstName: 'Joe',
diff --git a/test/multipartUploads.spec.js b/test/multipartUploads.spec.js
index 0dc1e1c..f4be574 100644
--- a/test/multipartUploads.spec.js
+++ b/test/multipartUploads.spec.js
@@ -4,7 +4,7 @@ const fs = require('fs');
const path = require('path');
const request = require('supertest');
const server = require('./server');
-const app = server.app;
+const app = server.setup();
const clearUploadsDir = server.clearUploadsDir;
const fileDir = server.fileDir;
const uploadDir = server.uploadDir;
diff --git a/test/options.spec.js b/test/options.spec.js
new file mode 100644
index 0000000..b1af76c
--- /dev/null
+++ b/test/options.spec.js
@@ -0,0 +1,187 @@
+const fs = require('fs');
+const path = require('path');
+const request = require('supertest');
+const server = require('./server');
+const clearUploadsDir = server.clearUploadsDir;
+const fileDir = server.fileDir;
+const uploadDir = server.uploadDir;
+
+describe('File Upload Options Tests', function() {
+ afterEach(function(done) {
+ clearUploadsDir();
+ done();
+ });
+
+ /**
+ * Upload the file for testing and verify the expected filename.
+ * @param {object} options The expressFileUpload options.
+ * @param {string} actualFileNameToUpload The name of the file to upload.
+ * @param {string} expectedFileNameOnFileSystem The name of the file after upload.
+ * @param {function} done The mocha continuation function.
+ */
+ function executeFileUploadTestWalk(options,
+ actualFileNameToUpload,
+ expectedFileNameOnFileSystem,
+ done) {
+ request(server.setup(options))
+ .post('/upload/single')
+ .attach('testFile', path.join(fileDir, actualFileNameToUpload))
+ .expect(200)
+ .end(function(err) {
+ if (err)
+ return done(err);
+
+ const uploadedFilePath = path.join(uploadDir, expectedFileNameOnFileSystem);
+
+ fs.stat(uploadedFilePath, done);
+ });
+ }
+
+ describe('Testing [safeFileNames] option to ensure:', function() {
+ it('Does nothing to your filename when disabled.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: false};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'my$Invalid#fileName.png123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Is disabled by default.',
+ function(done) {
+ const fileUploadOptions = null;
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'my$Invalid#fileName.png123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Strips away all non-alphanumeric characters (excluding hyphens/underscores) when enabled.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Accepts a regex for stripping (decidedly) "invalid" characters from filename.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: /[\$#]/g};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileName.png123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+ });
+
+ describe('Testing [preserveExtension] option to ensure:', function() {
+ it('Does not preserve the extension of your filename when disabled.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: false};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Is disabled by default.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Shortens your extension to the default(3) when enabled, if the extension found is larger.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: true};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng.123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Leaves your extension alone when enabled, if the extension found is <= default(3) length',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: true};
+ const actualFileName = 'car.png';
+ const expectedFileName = 'car.png';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Can be configured for an extension length > default(3).',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: 7};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileName.png123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Can be configured for an extension length < default(3).',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: 2};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng1.23';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Will use the absolute value of your extension length when negative.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: -5};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamep.ng123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Will leave no extension when the extension length == 0.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: 0};
+ const actualFileName = 'car.png';
+ const expectedFileName = 'car';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Will accept numbers as strings, if they can be resolved with parseInt.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: '3'};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng.123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Will be evaluated for truthy-ness if it cannot be parsed as an int.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: 'not-a-#-but-truthy'};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepng.123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Will ignore any decimal amount when evaluating for extension length.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: 4.98};
+ const actualFileName = 'my$Invalid#fileName.png123';
+ const expectedFileName = 'myInvalidfileNamepn.g123';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+
+ it('Only considers the last dotted part as the extension.',
+ function(done) {
+ const fileUploadOptions = {safeFileNames: true, preserveExtension: true};
+ const actualFileName = 'basket.ball.bp';
+ const expectedFileName = 'basketball.bp';
+
+ executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done);
+ });
+ });
+});
diff --git a/test/server.js b/test/server.js
index 41a80b7..3f4bdeb 100644
--- a/test/server.js
+++ b/test/server.js
@@ -1,14 +1,8 @@
'use strict';
-
-const fs = require('fs-extra');
const path = require('path');
-const express = require('express');
-const expressFileupload = require('../lib/index');
-
const fileDir = path.join(__dirname, 'files');
const uploadDir = path.join(__dirname, 'uploads');
-
-const app = express();
+const fs = require('fs-extra');
const clearUploadsDir = function() {
if (!fs.existsSync(uploadDir)) {
@@ -18,157 +12,166 @@ const clearUploadsDir = function() {
}
};
-app.use(expressFileupload());
+const setup = function(fileUploadOptions) {
+ const express = require('express');
+ const expressFileupload = require('../lib/index');
-app.all('/upload/single', function(req, res) {
- if (!req.files)
- return res.status(400).send('No files were uploaded.');
+ const app = express();
- let testFile = req.files.testFile;
- let uploadPath = path.join(uploadDir, testFile.name);
+ fileUploadOptions = fileUploadOptions || {};
+ app.use(expressFileupload(fileUploadOptions));
- testFile.mv(uploadPath, function(err) {
- if (err)
- return res.status(500).send(err);
+ app.all('/upload/single', function(req, res) {
+ if (!req.files)
+ return res.status(400).send('No files were uploaded.');
+
+ let testFile = req.files.testFile;
+ let uploadPath = path.join(uploadDir, testFile.name);
+
+ testFile.mv(uploadPath, function(err) {
+ if (err)
+ return res.status(500).send(err);
- res.send('File uploaded to ' + uploadPath);
+ res.send('File uploaded to ' + uploadPath);
+ });
});
-});
-app.all('/upload/single/withfields', function(req, res) {
- if (!req.files)
- return res.status(400).send('No files were uploaded.');
+ app.all('/upload/single/withfields', function(req, res) {
+ if (!req.files)
+ return res.status(400).send('No files were uploaded.');
- if (!req.body)
- return res.status(400).send('No request body found');
+ if (!req.body)
+ return res.status(400).send('No request body found');
- if (!req.body.firstName || !req.body.firstName.trim())
- return res.status(400).send('Invalid first name');
+ if (!req.body.firstName || !req.body.firstName.trim())
+ return res.status(400).send('Invalid first name');
- if (!req.body.lastName || !req.body.lastName.trim())
- return res.status(400).send('Invalid last name');
+ if (!req.body.lastName || !req.body.lastName.trim())
+ return res.status(400).send('Invalid last name');
- if (!req.body.email || !req.body.email.trim())
- return res.status(400).send('Invalid email');
+ if (!req.body.email || !req.body.email.trim())
+ return res.status(400).send('Invalid email');
- let testFile = req.files.testFile;
- let uploadPath = path.join(uploadDir, testFile.name);
+ let testFile = req.files.testFile;
+ let uploadPath = path.join(uploadDir, testFile.name);
- testFile.mv(uploadPath, function(err) {
- if (err)
- return res.status(500).send(err);
+ testFile.mv(uploadPath, function(err) {
+ if (err)
+ return res.status(500).send(err);
- res.json({
- firstName: req.body.firstName,
- lastName: req.body.lastName,
- email: req.body.email
+ res.json({
+ firstName: req.body.firstName,
+ lastName: req.body.lastName,
+ email: req.body.email
+ });
});
});
-});
-app.all('/upload/multiple', function(req, res) {
- if (!req.files)
- return res.status(400).send('No files were uploaded.');
+ app.all('/upload/multiple', function(req, res) {
+ if (!req.files)
+ return res.status(400).send('No files were uploaded.');
- let testFile1 = req.files.testFile1;
- let testFile2 = req.files.testFile2;
- let testFile3 = req.files.testFile3;
- let uploadPath1 = path.join(uploadDir, testFile1.name);
- let uploadPath2 = path.join(uploadDir, testFile2.name);
- let uploadPath3 = path.join(uploadDir, testFile3.name);
+ let testFile1 = req.files.testFile1;
+ let testFile2 = req.files.testFile2;
+ let testFile3 = req.files.testFile3;
+ let uploadPath1 = path.join(uploadDir, testFile1.name);
+ let uploadPath2 = path.join(uploadDir, testFile2.name);
+ let uploadPath3 = path.join(uploadDir, testFile3.name);
- if (!testFile1)
- return res.status(400).send('testFile1 was not uploaded');
+ if (!testFile1)
+ return res.status(400).send('testFile1 was not uploaded');
- if (!testFile2)
- return res.status(400).send('testFile2 was not uploaded');
+ if (!testFile2)
+ return res.status(400).send('testFile2 was not uploaded');
- if (!testFile3)
- return res.status(400).send('testFile3 was not uploaded');
+ if (!testFile3)
+ return res.status(400).send('testFile3 was not uploaded');
- testFile1.mv(uploadPath1, function(err) {
- if (err)
- return res.status(500).send(err);
-
- testFile2.mv(uploadPath2, function(err) {
+ testFile1.mv(uploadPath1, function(err) {
if (err)
return res.status(500).send(err);
- testFile3.mv(uploadPath3, function(err) {
+ testFile2.mv(uploadPath2, function(err) {
if (err)
return res.status(500).send(err);
- res.send('Files uploaded to ' + uploadDir);
+ testFile3.mv(uploadPath3, function(err) {
+ if (err)
+ return res.status(500).send(err);
+
+ res.send('Files uploaded to ' + uploadDir);
+ });
});
});
});
-});
-app.all('/upload/array', function(req, res) {
- if (!req.files)
- return res.status(400).send('No files were uploaded.');
+ app.all('/upload/array', function(req, res) {
+ if (!req.files)
+ return res.status(400).send('No files were uploaded.');
- let testFiles = req.files.testFiles;
+ let testFiles = req.files.testFiles;
- if (!testFiles)
- return res.status(400).send('No files were uploaded');
+ if (!testFiles)
+ return res.status(400).send('No files were uploaded');
- if (!Array.isArray(testFiles))
- return res.status(400).send('Files were not uploaded as an array');
+ if (!Array.isArray(testFiles))
+ return res.status(400).send('Files were not uploaded as an array');
- if (!testFiles.length)
- return res.status(400).send('Files array is empty');
+ if (!testFiles.length)
+ return res.status(400).send('Files array is empty');
- let filesUploaded = 0;
- for (let i = 0; i < testFiles.length; i++) {
- let uploadPath = path.join(uploadDir, testFiles[i].name);
+ let filesUploaded = 0;
+ for (let i = 0; i < testFiles.length; i++) {
+ let uploadPath = path.join(uploadDir, testFiles[i].name);
- testFiles[i].mv(uploadPath, function(err) {
- if (err)
- return res.status(500).send(err);
+ testFiles[i].mv(uploadPath, function(err) {
+ if (err)
+ return res.status(500).send(err);
- if (++filesUploaded === testFiles.length)
- res.send('File uploaded to ' + uploadPath);
- });
- }
-});
+ if (++filesUploaded === testFiles.length)
+ res.send('File uploaded to ' + uploadPath);
+ });
+ }
+ });
-app.all('/fields/user', function(req, res) {
- if (!req.body)
- return res.status(400).send('No request body found');
+ app.all('/fields/user', function(req, res) {
+ if (!req.body)
+ return res.status(400).send('No request body found');
- if (!req.body.firstName || !req.body.firstName.trim())
- return res.status(400).send('Invalid first name');
+ if (!req.body.firstName || !req.body.firstName.trim())
+ return res.status(400).send('Invalid first name');
- if (!req.body.lastName || !req.body.lastName.trim())
- return res.status(400).send('Invalid last name');
+ if (!req.body.lastName || !req.body.lastName.trim())
+ return res.status(400).send('Invalid last name');
- if (!req.body.email || !req.body.email.trim())
- return res.status(400).send('Invalid email');
+ if (!req.body.email || !req.body.email.trim())
+ return res.status(400).send('Invalid email');
- res.json({
- firstName: req.body.firstName,
- lastName: req.body.lastName,
- email: req.body.email
+ res.json({
+ firstName: req.body.firstName,
+ lastName: req.body.lastName,
+ email: req.body.email
+ });
});
-});
-app.all('/fields/array', function(req, res) {
- if (!req.body)
- return res.status(400).send('No request body found');
+ app.all('/fields/array', function(req, res) {
+ if (!req.body)
+ return res.status(400).send('No request body found');
- if (!req.body.testField)
- return res.status(400).send('Invalid field');
+ if (!req.body.testField)
+ return res.status(400).send('Invalid field');
- if (!Array.isArray(req.body.testField))
- return res.status(400).send('Field is not an array');
+ if (!Array.isArray(req.body.testField))
+ return res.status(400).send('Field is not an array');
- res.json(req.body.testField);
-});
+ res.json(req.body.testField);
+ });
+ return app;
+};
module.exports = {
- app,
+ setup,
fileDir,
uploadDir,
clearUploadsDir