You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

167 lines
5.5 KiB

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