123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- 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 = 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, info) => {
- // Parse file name(cutting huge names, decoding, etc..).
- const {filename:name, encoding, mimeType: mime} = info;
- 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);
- };
|