express-fileupload
Advanced tools
Comparing version 1.1.3-alpha.1 to 1.1.3-alpha.2
@@ -7,2 +7,3 @@ 'use strict'; | ||
const processNested = require('./processNested'); | ||
const {buildOptions} = require('./utilities'); | ||
@@ -13,2 +14,4 @@ const fileUploadOptionsDefaults = { | ||
abortOnLimit: false, | ||
responseOnLimit: 'File size limit has been reached', | ||
limitHandler: false, | ||
createParentPath: false, | ||
@@ -23,6 +26,6 @@ parseNested: false, | ||
*/ | ||
module.exports = function(fileUploadOptions) { | ||
fileUploadOptions = Object.assign({}, fileUploadOptionsDefaults, fileUploadOptions || {}); | ||
module.exports = (fileUploadOptions) => { | ||
fileUploadOptions = buildOptions(fileUploadOptionsDefaults, fileUploadOptions); | ||
return function(req, res, next) { | ||
return function(req, res, next){ | ||
if (!isEligibleRequest(req)) { | ||
@@ -29,0 +32,0 @@ return next(); |
const crypto = require('crypto'); | ||
const {debugLog} = require('./utilities'); | ||
@@ -14,2 +15,5 @@ /** | ||
const getBuffer = () => Buffer.concat(buffers); | ||
const emptyFunc = () => ''; | ||
return { | ||
@@ -20,12 +24,6 @@ dataHandler: function(data) { | ||
fileSize += data.length; | ||
if (options.debug) { | ||
return console.log('Uploading %s -> %s, bytes: %d', fieldname, filename, fileSize); // eslint-disable-line | ||
} | ||
debugLog(options, `Uploading ${fieldname} -> ${filename}, bytes: ${fileSize}`); | ||
}, | ||
getBuffer: function(){ | ||
return Buffer.concat(buffers); | ||
}, | ||
getFilePath: function(){ | ||
return ''; | ||
}, | ||
getBuffer: getBuffer, | ||
getFilePath: emptyFunc, | ||
getFileSize: function(){ | ||
@@ -37,8 +35,5 @@ return fileSize; | ||
}, | ||
complete: function(){ | ||
return Buffer.concat(buffers); | ||
}, | ||
cleanup: function(){ | ||
} | ||
complete: getBuffer, | ||
cleanup: emptyFunc | ||
}; | ||
}; |
@@ -6,2 +6,6 @@ const Busboy = require('busboy'); | ||
const processNested = require('./processNested'); | ||
const { | ||
buildOptions, | ||
parseFileName | ||
} = require('./utilities'); | ||
@@ -19,20 +23,8 @@ /** | ||
module.exports = function processMultipart(options, req, res, next) { | ||
let busboyOptions = {}; | ||
let busboy; | ||
req.files = null; | ||
// Build busboy options and init busboy instance. | ||
let busboyOptions = buildOptions(options, {headers: req.headers}); | ||
let busboy = new Busboy(busboyOptions); | ||
// Build busboy config | ||
for (let k in options) { | ||
if (Object.prototype.hasOwnProperty.call(options, k)) { | ||
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 | ||
@@ -65,3 +57,3 @@ busboy.on('field', function(fieldname, val) { | ||
res.writeHead(413, { Connection: 'close' }); | ||
res.end('File size limit has been reached'); | ||
res.end(options.responseOnLimit); | ||
} | ||
@@ -85,47 +77,5 @@ }); | ||
if (options.safeFileNames) { | ||
let safeFileNameRegex = /[^\w-]/g; | ||
let maxExtensionLength = 3; | ||
let extension = ''; | ||
if (typeof options.safeFileNames === 'object') { | ||
safeFileNameRegex = options.safeFileNames; | ||
} | ||
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); | ||
} | ||
const newFile = fileFactory( | ||
{ | ||
name: filename, | ||
name: parseFileName(options, filename), | ||
buffer, | ||
@@ -132,0 +82,0 @@ tempFilePath: getFilePath(), |
const fs = require('fs'); | ||
const path = require('path'); | ||
const crypto = require('crypto'); | ||
const {checkAndMakeDir} = require('./utilities.js'); | ||
const { | ||
debugLog, | ||
checkAndMakeDir, | ||
getTempFilename | ||
} = require('./utilities'); | ||
module.exports = function(options, fieldname, filename) { | ||
const dir = path.normalize(options.tempFileDir || process.cwd() + '/tmp/'); | ||
const tempFilePath = path.join(dir, 'tmp' + Date.now()); | ||
const tempFilePath = path.join(dir, getTempFilename()); | ||
@@ -21,9 +25,3 @@ checkAndMakeDir({createParentPath: true}, tempFilePath); | ||
fileSize += data.length; | ||
if (options.debug) { | ||
return console.log( // eslint-disable-line | ||
`Uploaded ${data.length} bytes for `, | ||
fieldname, | ||
filename | ||
); | ||
} | ||
debugLog(options, `Uploading ${fieldname} -> ${filename}, bytes: ${fileSize}`); | ||
}, | ||
@@ -30,0 +28,0 @@ getFilePath: function(){ |
@@ -7,3 +7,36 @@ 'use strict'; | ||
//Parameters for safe file name parsing | ||
const SAFE_FILE_NAME_REGEX = /[^\w-]/g; | ||
const MAX_EXTENSION_LENGTH = 3; | ||
//Parameters which used to generate unique temporary file names: | ||
const TEMP_COUNTER_MAX = 65536; | ||
const TEMP_PREFIX = 'tmp'; | ||
let tempCounter = 0; | ||
/** | ||
* Logs message to console if debug option set to true. | ||
* @param {Object} options - options object. | ||
* @param {String} msg - message to log. | ||
* @return {Boolean} | ||
*/ | ||
const debugLog = (options, msg) => { | ||
options = options || {}; | ||
if (!options.debug) return false; | ||
console.log(msg); // eslint-disable-line | ||
return true; | ||
}; | ||
/** | ||
* Generates unique temporary file name like: tmp-5000-156788789789. | ||
* @param prefix {String} - a prefix for generated unique file name. | ||
*/ | ||
const getTempFilename = (prefix) => { | ||
prefix = prefix || TEMP_PREFIX; | ||
tempCounter ++; | ||
if (tempCounter > TEMP_COUNTER_MAX) tempCounter = 1; | ||
return `${prefix}-${tempCounter}-${Date.now()}`; | ||
}; | ||
/** | ||
* Returns true if argument is function. | ||
@@ -14,2 +47,15 @@ */ | ||
/** | ||
* Builds instance options from arguments objects. | ||
* @returns {Object} - result options. | ||
*/ | ||
const buildOptions = function(){ | ||
let result = {}; | ||
[...arguments].forEach(options => { | ||
if (!options || typeof options !== 'object') return; | ||
Object.keys(options).forEach(key => result[key] = options[key]); | ||
}); | ||
return result; | ||
}; | ||
/** | ||
* Creates a folder for file specified in the path variable | ||
@@ -83,7 +129,70 @@ * @param {Object} fileUploadOptions | ||
/** | ||
* Parses filename and extension and returns object {name, extension}. | ||
* @param preserveExtension {Boolean, Integer} - true/false or number of characters for extension. | ||
* @param fileName {String} - file name to parse. | ||
* @returns {Object} - {name, extension}. | ||
*/ | ||
const parseFileNameExtension = (preserveExtension, fileName) => { | ||
let preserveExtensionLengh = parseInt(preserveExtension); | ||
let result = {name: fileName, extension: ''}; | ||
if (!preserveExtension && preserveExtensionLengh !== 0){ | ||
return result; | ||
} | ||
// Define maximum extension length | ||
let maxExtLength = isNaN(preserveExtensionLengh) | ||
? MAX_EXTENSION_LENGTH | ||
: Math.abs(preserveExtensionLengh); | ||
let nameParts = fileName.split('.'); | ||
if (nameParts.length < 2) { | ||
return result; | ||
} | ||
let extension = nameParts.pop(); | ||
if ( | ||
extension.length > maxExtLength && | ||
maxExtLength > 0 | ||
) { | ||
nameParts[nameParts.length - 1] += | ||
'.' + | ||
extension.substr(0, extension.length - maxExtLength); | ||
extension = extension.substr(-maxExtLength); | ||
} | ||
result.extension = maxExtLength ? extension : ''; | ||
result.name = nameParts.join('.'); | ||
return result; | ||
}; | ||
/** | ||
* Parse file name and extension. | ||
* @param opts {Object} - middleware options. | ||
* @param fileName {String} - Uploaded file name. | ||
* @returns {String} | ||
*/ | ||
const parseFileName = (opts, fileName) => { | ||
if (!opts.safeFileNames) { | ||
return fileName; | ||
} | ||
// Set regular expression for the file name. | ||
let safeNameRegex = typeof opts.safeFileNames === 'object' && opts.safeFileNames instanceof RegExp | ||
? opts.safeFileNames | ||
: SAFE_FILE_NAME_REGEX; | ||
// Parse file name extension. | ||
let {name, extension} = parseFileNameExtension(opts.preserveExtension, fileName); | ||
if (extension.length) extension = '.' + extension.replace(safeNameRegex, ''); | ||
return name.replace(safeNameRegex, '').concat(extension); | ||
}; | ||
module.exports = { | ||
debugLog, | ||
isFunc, | ||
buildOptions, | ||
checkAndMakeDir, | ||
copyFile, | ||
saveBufferToFile | ||
saveBufferToFile, | ||
parseFileName, | ||
getTempFilename | ||
}; |
{ | ||
"name": "express-fileupload", | ||
"version": "1.1.3-alpha.1", | ||
"version": "1.1.3-alpha.2", | ||
"author": "Richard Girges <richardgirges@gmail.com>", | ||
@@ -5,0 +5,0 @@ "description": "Simple express file upload middleware that wraps around Busboy", |
@@ -10,5 +10,5 @@ # express-fileupload | ||
# Version 1.1.1 Breaking Changes | ||
Breaking change to `md5` handling. | ||
md5 again returns a hash value instead of function which compute the hash. | ||
md5 hashes now can be used with tempFiles. | ||
Breaking change to `md5` handling: | ||
* `md5` value contains md5 hash instead of a function to compute it. | ||
* `md5` now can be used with `useTempFiles: true`. | ||
@@ -45,2 +45,3 @@ # Version 1.0.0 Breaking Changes | ||
* `req.files.foo.data`: A buffer representation of your file | ||
* `req.files.foo.tempFilePath`: A path to the temporary file in case useTempFiles option was set to true. | ||
* `req.files.foo.truncated`: A boolean that represents if the file is over the size limit | ||
@@ -83,2 +84,3 @@ * `req.files.foo.size`: Uploaded size in bytes | ||
abortOnLimit | <ul><li><code>false</code> **(default)**</li><li><code>true</code></ul> | Returns a HTTP 413 when the file is bigger than the size limit if true. Otherwise, it will add a <code>truncate = true</code> to the resulting file structure. | ||
responseOnLimit | <ul><li><code>'File size limit has been reached'</code> **(default)**</li><li><code>*String*</code></ul> | Response which will be send to client if file size limit exceeded when abortOnLimit set to true. | ||
useTempFiles | <ul><li><code>false</code> **(default)**</li><li><code>true</code></ul> | Will use temporary files at the specified tempDir for managing uploads rather than using buffers in memory. This avoids memory issues when uploading large files. | ||
@@ -85,0 +87,0 @@ tempFileDir | <ul><li><code>String</code> **(path)**</li></ul> | Used with the <code>useTempFiles</code> option. Path to the directory where temp files will be stored during the upload process. Feel free to add trailing slash, but it is not necessary. |
@@ -12,6 +12,10 @@ 'use strict'; | ||
const { | ||
debugLog, | ||
isFunc, | ||
getTempFilename, | ||
buildOptions, | ||
checkAndMakeDir, | ||
copyFile, | ||
saveBufferToFile | ||
saveBufferToFile, | ||
parseFileName | ||
} = require('../lib/utilities.js'); | ||
@@ -29,2 +33,20 @@ | ||
}); | ||
//debugLog tests | ||
describe('Test debugLog function', () => { | ||
let testMessage = 'Test message'; | ||
it('debugLog returns false if no options passed', () => { | ||
assert.equal(debugLog(null, testMessage), false); | ||
}); | ||
it('debugLog returns false if option debug is false', () => { | ||
assert.equal(debugLog({debug: false}, testMessage), false); | ||
}); | ||
it('debugLog returns true if option debug is true', () => { | ||
assert.equal(debugLog({debug: true}, testMessage), true); | ||
}); | ||
}); | ||
//isFunc tests | ||
@@ -51,2 +73,108 @@ describe('Test isFunc function', () => { | ||
}); | ||
//getTempFilename tests | ||
describe('Test getTempFilename function', () => { | ||
const nameRegexp = /tmp-\d{1,5}-\d{1,}/; | ||
it('getTempFilename result matches regexp /tmp-d{1,5}-d{1,}/', () => { | ||
let errCounter = 0; | ||
let tempName = ''; | ||
for (var i = 0; i < 65537; i++) { | ||
tempName = getTempFilename(); | ||
if (!nameRegexp.test(tempName)) errCounter ++; | ||
} | ||
assert.equal(errCounter, 0); | ||
}); | ||
it('getTempFilename current and previous results are not equal', () => { | ||
let errCounter = 0; | ||
let tempName = ''; | ||
let previousName = ''; | ||
for (var i = 0; i < 65537; i++) { | ||
previousName = tempName; | ||
tempName = getTempFilename(); | ||
if (previousName === tempName) errCounter ++; | ||
} | ||
assert.equal(errCounter, 0); | ||
}); | ||
}); | ||
//parseFileName | ||
describe('Test parseFileName function', () => { | ||
it('Does nothing to your filename when disabled.', () => { | ||
const opts = {safeFileNames: false}; | ||
const name = 'my$Invalid#fileName.png123'; | ||
const expected = 'my$Invalid#fileName.png123'; | ||
let result = parseFileName(opts, name); | ||
assert.equal(result, expected); | ||
}); | ||
it( | ||
'Strips away all non-alphanumeric characters (excluding hyphens/underscores) when enabled.', | ||
() => { | ||
const opts = {safeFileNames: true}; | ||
const name = 'my$Invalid#fileName.png123'; | ||
const expected = 'myInvalidfileNamepng123'; | ||
let result = parseFileName(opts, name); | ||
assert.equal(result, expected); | ||
}); | ||
it( | ||
'Strips away all non-alphanumeric chars when preserveExtension: true for a name without dots', | ||
() => { | ||
const opts = {safeFileNames: true, preserveExtension: true}; | ||
const name = 'my$Invalid#fileName'; | ||
const expected = 'myInvalidfileName'; | ||
let result = parseFileName(opts, name); | ||
assert.equal(result, expected); | ||
}); | ||
it('Accepts a regex for stripping (decidedly) "invalid" characters from filename.', () => { | ||
const opts = {safeFileNames: /[$#]/g}; | ||
const name = 'my$Invalid#fileName.png123'; | ||
const expected = 'myInvalidfileName.png123'; | ||
let result = parseFileName(opts, name); | ||
assert.equal(result, expected); | ||
}); | ||
it( | ||
'Returns correct filename if name contains dots characters and preserveExtension: true.', | ||
() => { | ||
const opts = {safeFileNames: true, preserveExtension: true}; | ||
const name = 'basket.ball.png'; | ||
const expected = 'basketball.png'; | ||
let result = parseFileName(opts, name); | ||
assert.equal(result, expected); | ||
}); | ||
}); | ||
//buildOptions tests | ||
describe('Test buildOptions function', () => { | ||
const source = { option1: '1', option2: '2' }; | ||
const sourceAddon = { option3: '3'}; | ||
const expected = { option1: '1', option2: '2' }; | ||
const expectedAddon = { option1: '1', option2: '2', option3: '3'}; | ||
it('buildOptions returns and equal object to the object which was paased', () => { | ||
let result = buildOptions(source); | ||
assert.deepStrictEqual(result, source); | ||
}); | ||
it('buildOptions doesnt add non object or null arguments to the result', () => { | ||
let result = buildOptions(source, 2, '3', null); | ||
assert.deepStrictEqual(result, expected); | ||
}); | ||
it('buildOptions adds value to the result from the several source argumets', () => { | ||
let result = buildOptions(source, sourceAddon); | ||
assert.deepStrictEqual(result, expectedAddon); | ||
}); | ||
}); | ||
//checkAndMakeDir tests | ||
@@ -53,0 +181,0 @@ describe('Test checkAndMakeDir function', () => { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1203516
2244
92