|
|
const Busboy = require('busboy'); const UploadTimer = require('./uploadtimer'); const fileFactory = require('./fileFactory'); const memHandler = require('./memHandler'); const tempFileHandler = require('./tempFileHandler'); const processNested = require('./processNested'); const { isFunc, debugLog, buildFields, buildOptions, parseFileName } = require('./utilities');
const waitFlushProperty = Symbol('wait flush property symbol');
/** * 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} */ module.exports = (options, req, res, next) => { req.files = null;
// 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.
const closeConnection = (code, reason) => { req.unpipe(busboy); res.writeHead(code || 400, { Connection: 'close' }); res.end(reason || 'Bad Request'); };
// Express proxies sometimes attach multipart data to a buffer
if (req.body instanceof Buffer) { req.body = Object.create(null); } // Build multipart req.body fields
busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val));
// Build req.files fields
busboy.on('file', (field, file, name, encoding, mime) => { // Parse file name(cutting huge names, decoding, etc..).
const filename = parseFileName(options, name); // Define methods and handlers for upload process.
const { dataHandler, getFilePath, getFileSize, getHash, complete, cleanup, getWritePromise } = options.useTempFiles ? tempFileHandler(options, field, filename) // Upload into temporary file.
: memHandler(options, field, filename); // Upload into RAM.
const writePromise = options.useTempFiles ? getWritePromise().catch(err => { req.unpipe(busboy); req.resume(); cleanup(); next(err); }) : getWritePromise();
// Define upload timer.
const uploadTimer = new UploadTimer(options.uploadTimeout, () => { file.removeAllListeners('data'); file.resume(); // After destroy an error event will be emitted and file clean up will be done.
file.destroy(new Error(`Upload timeout ${field}->${filename}, bytes:${getFileSize()}`)); });
file.on('limit', () => { debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`); // Reset upload timer in case of file limit reached.
uploadTimer.clear(); // 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 ${field}->${filename}.`); !isFunc(options.limitHandler) ? closeConnection(413, options.responseOnLimit) : ''; cleanup(); } });
file.on('data', (data) => { uploadTimer.set(); // Refresh upload timer each time new data chunk came.
dataHandler(data); // Handle new piece of data.
});
file.on('end', () => { const size = getFileSize(); // Debug logging for file upload ending.
debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`); // Reset upload timer in case of end event.
uploadTimer.clear(); // See https://github.com/richardgirges/express-fileupload/issues/191
// Do not add file instance to the req.files if original name and size are empty.
// Empty name and zero size indicates empty file field in the posted form.
if (!name && size === 0) { if (options.useTempFiles) { cleanup(); debugLog(options, `Removing the empty file ${field}->${filename}`); } return debugLog(options, `Don't add file instance if original name and size are empty`); } req.files = buildFields(req.files, field, fileFactory({ buffer: complete(), name: filename, tempFilePath: getFilePath(), hash: getHash(), size, encoding, truncated: file.truncated, mimetype: mime }, options));
if (!req[waitFlushProperty]) { req[waitFlushProperty] = []; } req[waitFlushProperty].push(writePromise); });
file.on('error', (err) => { uploadTimer.clear(); // Reset upload timer in case of errors.
debugLog(options, err); cleanup(); next(); });
// Debug logging for a new file upload.
debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`); // Set new upload timeout for a new file.
uploadTimer.set(); });
busboy.on('finish', () => { debugLog(options, `Busboy finished parsing request.`); if (options.parseNested) { req.body = processNested(req.body); req.files = processNested(req.files); }
if (!req[waitFlushProperty]) return next(); Promise.all(req[waitFlushProperty]) .then(() => { delete req[waitFlushProperty]; next(); }); });
busboy.on('error', (err) => { debugLog(options, `Busboy error`); next(err); });
req.pipe(busboy); };
|