express-fileupload
Advanced tools
Comparing version 1.1.6-alpha.4 to 1.1.6-alpha.5
@@ -14,3 +14,3 @@ # express-fileupload Examples | ||
app.post('/upload', function(req, res) { | ||
if (Object.keys(req.files).length == 0) { | ||
if (!req.files || Object.keys(req.files).length === 0) { | ||
return res.status(400).send('No files were uploaded.'); | ||
@@ -17,0 +17,0 @@ } |
@@ -19,3 +19,3 @@ const express = require('express'); | ||
if (Object.keys(req.files).length == 0) { | ||
if (!req.files || Object.keys(req.files).length === 0) { | ||
res.status(400).send('No files were uploaded.'); | ||
@@ -22,0 +22,0 @@ return; |
@@ -5,5 +5,6 @@ 'use strict'; | ||
isFunc, | ||
debugLog, | ||
moveFile, | ||
promiseCallback, | ||
checkAndMakeDir, | ||
moveFile, | ||
saveBufferToFile | ||
@@ -19,6 +20,5 @@ } = require('./utilities'); | ||
*/ | ||
const moveFromTemp = (filePath, options) => { | ||
return (resolve, reject) => { | ||
moveFile(options.tempFilePath, filePath, promiseCallback(resolve, reject)); | ||
}; | ||
const moveFromTemp = (filePath, options, fileUploadOptions) => (resolve, reject) => { | ||
debugLog(fileUploadOptions, `Moving temporary file ${options.tempFilePath} to ${filePath}`); | ||
moveFile(options.tempFilePath, filePath, promiseCallback(resolve, reject)); | ||
}; | ||
@@ -33,6 +33,5 @@ | ||
*/ | ||
const moveFromBuffer = (filePath, options) => { | ||
return (resolve, reject) => { | ||
saveBufferToFile(options.buffer, filePath, promiseCallback(resolve, reject)); | ||
}; | ||
const moveFromBuffer = (filePath, options, fileUploadOptions) => (resolve, reject) => { | ||
debugLog(fileUploadOptions, `Moving uploaded buffer to ${filePath}`); | ||
saveBufferToFile(options.buffer, filePath, promiseCallback(resolve, reject)); | ||
}; | ||
@@ -58,3 +57,3 @@ | ||
// Determine a propper move function. | ||
let moveFunc = (options.buffer.length && !options.tempFilePath) | ||
const moveFunc = (options.buffer.length && !options.tempFilePath) | ||
? moveFromBuffer(filePath, options) | ||
@@ -65,7 +64,5 @@ : moveFromTemp(filePath, options); | ||
// If callback is passed in, use the callback API, otherwise return a promise. | ||
return isFunc(callback) | ||
? moveFunc(callback) | ||
: new Promise(moveFunc); | ||
return isFunc(callback) ? moveFunc(callback) : new Promise(moveFunc); | ||
} | ||
}; | ||
}; |
@@ -5,5 +5,5 @@ 'use strict'; | ||
const processNested = require('./processNested'); | ||
const {buildOptions} = require('./utilities'); | ||
const processMultipart = require('./processMultipart'); | ||
const isEligibleRequest = require('./isEligibleRequest'); | ||
const { buildOptions, debugLog } = require('./utilities'); | ||
@@ -35,3 +35,6 @@ const DEFAULT_OPTIONS = { | ||
return (req, res, next) => { | ||
if (!isEligibleRequest(req)) return next(); | ||
if (!isEligibleRequest(req)) { | ||
debugLog(fileUploadOptions, 'Request is not eligible for file upload!'); | ||
return next(); | ||
} | ||
processMultipart(fileUploadOptions, req, res, next); | ||
@@ -38,0 +41,0 @@ }; |
@@ -9,4 +9,4 @@ const Busboy = require('busboy'); | ||
debugLog, | ||
buildFields, | ||
buildOptions, | ||
buildFields, | ||
parseFileName, | ||
@@ -29,2 +29,6 @@ uriDecodeFileName | ||
// Build busboy options and init busboy instance. | ||
const busboyOptions = buildOptions(options, { headers: req.headers }); | ||
const busboy = new Busboy(busboyOptions); | ||
// Close connection with specified reason and http code, default: 400 Bad Request. | ||
@@ -37,27 +41,26 @@ const closeConnection = (code, reason) => { | ||
// Build busboy options and init busboy instance. | ||
let busboyOptions = buildOptions(options, {headers: req.headers}); | ||
let busboy = new Busboy(busboyOptions); | ||
// Build multipart req.body fields | ||
busboy.on('field', (fieldname, val) => req.body = buildFields(req.body, fieldname, val)); | ||
busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val)); | ||
// Build req.files fields | ||
busboy.on('file', (fieldname, file, name, encoding, mime) => { | ||
busboy.on('file', (field, file, name, encoding, mime) => { | ||
// Define upload timer settings | ||
let uploadTimer = null; | ||
const timeout = options.uploadTimeout; | ||
// Decode file name if uriDecodeFileNames option set true. | ||
const filename = uriDecodeFileName(options, name); | ||
// Define methods and handlers for upload process. | ||
const {dataHandler, getFilePath, getFileSize, getHash, complete, cleanup} = options.useTempFiles | ||
? tempFileHandler(options, fieldname, filename) | ||
: memHandler(options, fieldname, filename); | ||
? tempFileHandler(options, field, filename) // Upload into temporary file. | ||
: memHandler(options, field, filename); // Upload into RAM. | ||
file.on('limit', () => { | ||
debugLog(options, `Size limit reached for ${fieldname}->${filename}, bytes:${getFileSize()}`); | ||
// Run user defined limit handler if it has been set. | ||
if (isFunc(options.limitHandler)){ | ||
return options.limitHandler(req, res, next); | ||
} | ||
// Close connection with 413 code if abortOnLimit set(default: false). | ||
debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`); | ||
// Reset upload timer in case of file limit reached. | ||
clearTimeout(uploadTimer); | ||
// Run a user defined limit handler if it has been set. | ||
if (isFunc(options.limitHandler)) return options.limitHandler(req, res, next); | ||
// Close connection with 413 code and do cleanup if abortOnLimit set(default: false). | ||
if (options.abortOnLimit) { | ||
debugLog(options, `Aborting upload because of size limit ${fieldname}->${filename}!`); | ||
debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`); | ||
closeConnection(413, options.responseOnLimit); | ||
@@ -68,27 +71,41 @@ cleanup(); | ||
file.on('data', dataHandler); | ||
file.on('data', (data) => { | ||
// Reset and set new upload timer each time when new data came. | ||
clearTimeout(uploadTimer); | ||
uploadTimer = setTimeout(() => { | ||
debugLog(options, `Upload timeout ${field}->${filename}, bytes:${getFileSize()}`); | ||
cleanup(); | ||
}, timeout); | ||
// Handle new piece of data. | ||
dataHandler(data); | ||
}); | ||
file.on('end', () => { | ||
// Debug logging for a new file upload. | ||
debugLog(options, `Upload finished ${fieldname}->${filename}, bytes:${getFileSize()}`); | ||
debugLog(options, `Upload finished ${field}->${filename}, bytes:${getFileSize()}`); | ||
// Reset upload timer in case of end event. | ||
clearTimeout(uploadTimer); | ||
// Add file instance to the req.files | ||
req.files = buildFields(req.files, fieldname, fileFactory( | ||
{ | ||
buffer: complete(), | ||
name: parseFileName(options, filename), | ||
tempFilePath: getFilePath(), | ||
size: getFileSize(), | ||
hash: getHash(), | ||
encoding, | ||
truncated: file.truncated, | ||
mimetype: mime | ||
}, | ||
options | ||
)); | ||
req.files = buildFields(req.files, field, fileFactory({ | ||
buffer: complete(), | ||
name: parseFileName(options, filename), | ||
tempFilePath: getFilePath(), | ||
size: getFileSize(), | ||
hash: getHash(), | ||
encoding, | ||
truncated: file.truncated, | ||
mimetype: mime | ||
}, options)); | ||
}); | ||
file.on('error', cleanup, next); | ||
file.on('error', (err) => { | ||
// Reset upload timer in case of errors. | ||
clearTimeout(uploadTimer); | ||
debugLog(options, `Error ${field}->${filename}, bytes:${getFileSize()}, error:${err}`); | ||
cleanup(); | ||
next(); | ||
}); | ||
// Debug logging for a new file upload. | ||
debugLog(options, `New upload started ${fieldname}->${filename}, bytes:${getFileSize()}`); | ||
debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`); | ||
}); | ||
@@ -95,0 +112,0 @@ |
@@ -7,3 +7,4 @@ const fs = require('fs'); | ||
checkAndMakeDir, | ||
getTempFilename | ||
getTempFilename, | ||
deleteFile | ||
} = require('./utilities'); | ||
@@ -14,7 +15,8 @@ | ||
const tempFilePath = path.join(dir, getTempFilename()); | ||
checkAndMakeDir({createParentPath: true}, tempFilePath); | ||
let hash = crypto.createHash('md5'); | ||
let writeStream = fs.createWriteStream(tempFilePath); | ||
debugLog(options, `Temporary file path is ${tempFilePath}`); | ||
const hash = crypto.createHash('md5'); | ||
const writeStream = fs.createWriteStream(tempFilePath); | ||
let fileSize = 0; // eslint-disable-line | ||
@@ -27,3 +29,3 @@ | ||
fileSize += data.length; | ||
debugLog(options, `Uploading ${fieldname} -> ${filename}, bytes: ${fileSize}`); | ||
debugLog(options, `Uploading ${fieldname}->${filename}, bytes:${fileSize}...`); | ||
}, | ||
@@ -35,12 +37,11 @@ getFilePath: () => tempFilePath, | ||
writeStream.end(); | ||
//return empty buffer since data uploaded to the temporary file. | ||
// Return empty buff since data was uploaded into a temp file. | ||
return Buffer.concat([]); | ||
}, | ||
cleanup: () => { | ||
debugLog(options, `Cleaning up temporary file ${tempFilePath}...`); | ||
writeStream.end(); | ||
fs.unlink(tempFilePath, (err) => { | ||
if (err) throw err; | ||
}); | ||
deleteFile(tempFilePath, (err) => { if (err) throw err; }); | ||
} | ||
}; | ||
}; |
{ | ||
"name": "express-fileupload", | ||
"version": "1.1.6-alpha.4", | ||
"version": "1.1.6-alpha.5", | ||
"author": "Richard Girges <richardgirges@gmail.com>", | ||
@@ -5,0 +5,0 @@ "description": "Simple express file upload middleware that wraps around Busboy", |
@@ -12,2 +12,3 @@ 'use strict'; | ||
const uploadDir = server.uploadDir; | ||
const clearTempDir = server.clearTempDir; | ||
const clearUploadsDir = server.clearUploadsDir; | ||
@@ -276,3 +277,3 @@ | ||
it('fail when no files were attached', function(done) { | ||
it('fail when no files were attached', (done) => { | ||
request(app) | ||
@@ -284,3 +285,3 @@ .post('/upload/single') | ||
it('fail when using GET', function(done) { | ||
it('fail when using GET', (done) => { | ||
request(app) | ||
@@ -293,3 +294,3 @@ .get('/upload/single') | ||
it('fail when using HEAD', function(done) { | ||
it('fail when using HEAD', (done) => { | ||
request(app) | ||
@@ -306,20 +307,13 @@ .head('/upload/single') | ||
it('upload multiple files with POST', function(done) { | ||
it('upload multiple files with POST', (done) => { | ||
clearUploadsDir(); | ||
const req = request(app).post('/upload/multiple'); | ||
let expectedResult = []; | ||
let expectedResultSorted = []; | ||
let uploadedFilesPath = []; | ||
const expectedResult = []; | ||
const expectedResultSorted = []; | ||
const uploadedFilesPath = []; | ||
mockFiles.forEach((fileName, index) => { | ||
let filePath = path.join(fileDir, fileName); | ||
let fileStat = fs.statSync(filePath); | ||
const filePath = path.join(fileDir, fileName); | ||
req.attach(`testFile${index + 1}`, filePath); | ||
uploadedFilesPath.push(path.join(uploadDir, fileName)); | ||
expectedResult.push({ | ||
name: fileName, | ||
md5: md5(fs.readFileSync(filePath)), | ||
size: fileStat.size, | ||
uploadDir: '', | ||
uploadPath: '' | ||
}); | ||
req.attach(`testFile${index+1}`, filePath); | ||
expectedResult.push(genUploadResult(fileName, filePath)); | ||
}); | ||
@@ -329,6 +323,6 @@ | ||
.expect((res) => { | ||
res.body.forEach(fileInfo => { | ||
res.body.forEach((fileInfo) => { | ||
fileInfo.uploadDir = ''; | ||
fileInfo.uploadPath = ''; | ||
let index = mockFiles.indexOf(fileInfo.name); | ||
const index = mockFiles.indexOf(fileInfo.name); | ||
expectedResultSorted.push(expectedResult[index]); | ||
@@ -342,3 +336,3 @@ }); | ||
if (err) return done(err); | ||
fs.stat(uploadedFilesPath[1], function(err) { | ||
fs.stat(uploadedFilesPath[1], (err) => { | ||
if (err) return done(err); | ||
@@ -355,19 +349,12 @@ fs.stat(uploadedFilesPath[2], done); | ||
it('upload array of files with POST', function(done) { | ||
it('upload array of files with POST', (done) => { | ||
clearUploadsDir(); | ||
const req = request(app).post('/upload/array'); | ||
let expectedResult = []; | ||
let expectedResultSorted = []; | ||
let uploadedFilesPath = []; | ||
const expectedResult = []; | ||
const expectedResultSorted = []; | ||
const uploadedFilesPath = []; | ||
mockFiles.forEach((fileName) => { | ||
let filePath = path.join(fileDir, fileName); | ||
let fileStat = fs.statSync(filePath); | ||
const filePath = path.join(fileDir, fileName); | ||
uploadedFilesPath.push(path.join(uploadDir, fileName)); | ||
expectedResult.push({ | ||
name:fileName, | ||
md5: md5(fs.readFileSync(filePath)), | ||
size: fileStat.size, | ||
uploadDir: '', | ||
uploadPath: '' | ||
}); | ||
expectedResult.push(genUploadResult(fileName, filePath)); | ||
req.attach('testFiles', filePath); | ||
@@ -378,6 +365,6 @@ }); | ||
.expect((res)=>{ | ||
res.body.forEach(fileInfo => { | ||
res.body.forEach((fileInfo) => { | ||
fileInfo.uploadDir = ''; | ||
fileInfo.uploadPath = ''; | ||
let index = mockFiles.indexOf(fileInfo.name); | ||
const index = mockFiles.indexOf(fileInfo.name); | ||
expectedResultSorted.push(expectedResult[index]); | ||
@@ -433,1 +420,42 @@ }); | ||
}); | ||
describe('Test Aborting/Canceling during upload', function() { | ||
this.timeout(4000); // Set timeout for async tests. | ||
const uploadTimeout = 1000; | ||
const app = server.setup({ | ||
useTempFiles: true, | ||
tempFileDir: tempDir, | ||
debug: true, | ||
uploadTimeout | ||
}); | ||
clearTempDir(); | ||
clearUploadsDir(); | ||
mockFiles.forEach((fileName) => { | ||
const filePath = path.join(fileDir, fileName); | ||
it(`Delete temp file if ${fileName} upload was aborted`, (done) => { | ||
const req = request(app) | ||
.post('/upload/single') | ||
.attach('testFile', filePath) | ||
.on('progress', (e) => { | ||
const progress = (e.loaded * 100) / e.total; | ||
// Aborting request, use req.req since it is original superagent request. | ||
if (progress > 50) req.req.abort(); | ||
}) | ||
.end((err) => { | ||
if (!err) return done(`Connection hasn't been aborted!`); | ||
if (err.code !== 'ECONNRESET') return done(err); | ||
// err.code === 'ECONNRESET' that means upload has been aborted. | ||
// Checking temp directory after upload timeout. | ||
setTimeout(() => { | ||
fs.readdir(tempDir, (err, files) => { | ||
if (err) return done(err); | ||
return files.length ? done(`Temporary directory contains files!`) : done(); | ||
}); | ||
}, uploadTimeout * 2); | ||
}); | ||
}); | ||
}); | ||
}); |
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
1206596
2161