processMultipart.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. const Busboy = require('busboy');
  2. const UploadTimer = require('./uploadtimer');
  3. const fileFactory = require('./fileFactory');
  4. const memHandler = require('./memHandler');
  5. const tempFileHandler = require('./tempFileHandler');
  6. const processNested = require('./processNested');
  7. const {
  8. isFunc,
  9. debugLog,
  10. buildFields,
  11. buildOptions,
  12. parseFileName
  13. } = require('./utilities');
  14. const waitFlushProperty = Symbol('wait flush property symbol');
  15. /**
  16. * Processes multipart request
  17. * Builds a req.body object for fields
  18. * Builds a req.files object for files
  19. * @param {Object} options expressFileupload and Busboy options
  20. * @param {Object} req Express request object
  21. * @param {Object} res Express response object
  22. * @param {Function} next Express next method
  23. * @return {void}
  24. */
  25. module.exports = (options, req, res, next) => {
  26. req.files = null;
  27. // Build busboy options and init busboy instance.
  28. const busboyOptions = buildOptions(options, { headers: req.headers });
  29. const busboy = new Busboy(busboyOptions);
  30. // Close connection with specified reason and http code, default: 400 Bad Request.
  31. const closeConnection = (code, reason) => {
  32. req.unpipe(busboy);
  33. res.writeHead(code || 400, { Connection: 'close' });
  34. res.end(reason || 'Bad Request');
  35. };
  36. // Build multipart req.body fields
  37. busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val));
  38. // Build req.files fields
  39. busboy.on('file', (field, file, name, encoding, mime) => {
  40. // Parse file name(cutting huge names, decoding, etc..).
  41. const filename = parseFileName(options, name);
  42. // Define methods and handlers for upload process.
  43. const {
  44. dataHandler,
  45. getFilePath,
  46. getFileSize,
  47. getHash,
  48. complete,
  49. cleanup,
  50. getWritePromise
  51. } = options.useTempFiles
  52. ? tempFileHandler(options, field, filename) // Upload into temporary file.
  53. : memHandler(options, field, filename); // Upload into RAM.
  54. const writePromise = options.useTempFiles
  55. ? getWritePromise().catch(err => {
  56. req.unpipe(busboy);
  57. req.resume();
  58. cleanup();
  59. next(err);
  60. }) : getWritePromise();
  61. // Define upload timer.
  62. const uploadTimer = new UploadTimer(options.uploadTimeout, () => {
  63. file.removeAllListeners('data');
  64. file.resume();
  65. // After destroy an error event will be emitted and file clean up will be done.
  66. file.destroy(new Error(`Upload timeout ${field}->${filename}, bytes:${getFileSize()}`));
  67. });
  68. file.on('limit', () => {
  69. debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`);
  70. // Reset upload timer in case of file limit reached.
  71. uploadTimer.clear();
  72. // Run a user defined limit handler if it has been set.
  73. if (isFunc(options.limitHandler)) return options.limitHandler(req, res, next);
  74. // Close connection with 413 code and do cleanup if abortOnLimit set(default: false).
  75. if (options.abortOnLimit) {
  76. debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`);
  77. !isFunc(options.limitHandler) ? closeConnection(413, options.responseOnLimit) : '';
  78. cleanup();
  79. }
  80. });
  81. file.on('data', (data) => {
  82. uploadTimer.set(); // Refresh upload timer each time new data chunk came.
  83. dataHandler(data); // Handle new piece of data.
  84. });
  85. file.on('end', () => {
  86. const size = getFileSize();
  87. // Debug logging for file upload ending.
  88. debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`);
  89. // Reset upload timer in case of end event.
  90. uploadTimer.clear();
  91. // See https://github.com/richardgirges/express-fileupload/issues/191
  92. // Do not add file instance to the req.files if original name and size are empty.
  93. // Empty name and zero size indicates empty file field in the posted form.
  94. if (!name && size === 0) {
  95. if (options.useTempFiles) {
  96. cleanup();
  97. debugLog(options, `Removing the empty file ${field}->${filename}`);
  98. }
  99. return debugLog(options, `Don't add file instance if original name and size are empty`);
  100. }
  101. req.files = buildFields(req.files, field, fileFactory({
  102. buffer: complete(),
  103. name: filename,
  104. tempFilePath: getFilePath(),
  105. hash: getHash(),
  106. size,
  107. encoding,
  108. truncated: file.truncated,
  109. mimetype: mime
  110. }, options));
  111. if (!req[waitFlushProperty]) {
  112. req[waitFlushProperty] = [];
  113. }
  114. req[waitFlushProperty].push(writePromise);
  115. });
  116. file.on('error', (err) => {
  117. uploadTimer.clear(); // Reset upload timer in case of errors.
  118. debugLog(options, err);
  119. cleanup();
  120. next();
  121. });
  122. // Debug logging for a new file upload.
  123. debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`);
  124. // Set new upload timeout for a new file.
  125. uploadTimer.set();
  126. });
  127. busboy.on('finish', () => {
  128. debugLog(options, `Busboy finished parsing request.`);
  129. if (options.parseNested) {
  130. req.body = processNested(req.body);
  131. req.files = processNested(req.files);
  132. }
  133. if (!req[waitFlushProperty]) return next();
  134. Promise.all(req[waitFlushProperty])
  135. .then(() => {
  136. delete req[waitFlushProperty];
  137. next();
  138. });
  139. });
  140. busboy.on('error', (err) => {
  141. debugLog(options, `Busboy error`);
  142. next(err);
  143. });
  144. req.pipe(busboy);
  145. };