processMultipart.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 = 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. // Express proxies sometimes attach multipart data to a buffer
  37. if (req.body instanceof Buffer) {
  38. req.body = Object.create(null);
  39. }
  40. // Build multipart req.body fields
  41. busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val));
  42. // Build req.files fields
  43. busboy.on('file', (field, file, info) => {
  44. // Parse file name(cutting huge names, decoding, etc..).
  45. const {filename:name, encoding, mimeType: mime} = info;
  46. const filename = parseFileName(options, name);
  47. // Define methods and handlers for upload process.
  48. const {
  49. dataHandler,
  50. getFilePath,
  51. getFileSize,
  52. getHash,
  53. complete,
  54. cleanup,
  55. getWritePromise
  56. } = options.useTempFiles
  57. ? tempFileHandler(options, field, filename) // Upload into temporary file.
  58. : memHandler(options, field, filename); // Upload into RAM.
  59. const writePromise = options.useTempFiles
  60. ? getWritePromise().catch(err => {
  61. req.unpipe(busboy);
  62. req.resume();
  63. cleanup();
  64. next(err);
  65. }) : getWritePromise();
  66. // Define upload timer.
  67. const uploadTimer = new UploadTimer(options.uploadTimeout, () => {
  68. file.removeAllListeners('data');
  69. file.resume();
  70. // After destroy an error event will be emitted and file clean up will be done.
  71. file.destroy(new Error(`Upload timeout ${field}->${filename}, bytes:${getFileSize()}`));
  72. });
  73. file.on('limit', () => {
  74. debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`);
  75. // Reset upload timer in case of file limit reached.
  76. uploadTimer.clear();
  77. // Run a user defined limit handler if it has been set.
  78. if (isFunc(options.limitHandler)) return options.limitHandler(req, res, next);
  79. // Close connection with 413 code and do cleanup if abortOnLimit set(default: false).
  80. if (options.abortOnLimit) {
  81. debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`);
  82. !isFunc(options.limitHandler) ? closeConnection(413, options.responseOnLimit) : '';
  83. cleanup();
  84. }
  85. });
  86. file.on('data', (data) => {
  87. uploadTimer.set(); // Refresh upload timer each time new data chunk came.
  88. dataHandler(data); // Handle new piece of data.
  89. });
  90. file.on('end', () => {
  91. const size = getFileSize();
  92. // Debug logging for file upload ending.
  93. debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`);
  94. // Reset upload timer in case of end event.
  95. uploadTimer.clear();
  96. // See https://github.com/richardgirges/express-fileupload/issues/191
  97. // Do not add file instance to the req.files if original name and size are empty.
  98. // Empty name and zero size indicates empty file field in the posted form.
  99. if (!name && size === 0) {
  100. if (options.useTempFiles) {
  101. cleanup();
  102. debugLog(options, `Removing the empty file ${field}->${filename}`);
  103. }
  104. return debugLog(options, `Don't add file instance if original name and size are empty`);
  105. }
  106. req.files = buildFields(req.files, field, fileFactory({
  107. buffer: complete(),
  108. name: filename,
  109. tempFilePath: getFilePath(),
  110. hash: getHash(),
  111. size,
  112. encoding,
  113. truncated: file.truncated,
  114. mimetype: mime
  115. }, options));
  116. if (!req[waitFlushProperty]) {
  117. req[waitFlushProperty] = [];
  118. }
  119. req[waitFlushProperty].push(writePromise);
  120. });
  121. file.on('error', (err) => {
  122. uploadTimer.clear(); // Reset upload timer in case of errors.
  123. debugLog(options, err);
  124. cleanup();
  125. next();
  126. });
  127. // Debug logging for a new file upload.
  128. debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`);
  129. // Set new upload timeout for a new file.
  130. uploadTimer.set();
  131. });
  132. busboy.on('finish', () => {
  133. debugLog(options, `Busboy finished parsing request.`);
  134. if (options.parseNested) {
  135. req.body = processNested(req.body);
  136. req.files = processNested(req.files);
  137. }
  138. if (!req[waitFlushProperty]) return next();
  139. Promise.all(req[waitFlushProperty])
  140. .then(() => {
  141. delete req[waitFlushProperty];
  142. next();
  143. });
  144. });
  145. busboy.on('error', (err) => {
  146. debugLog(options, `Busboy error`);
  147. next(err);
  148. });
  149. req.pipe(busboy);
  150. };