• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

richardgirges / express-fileupload / 1cffbe29-1693-4258-a2c0-32d8f02cd02c

01 Nov 2023 01:24PM UTC coverage: 94.311% (-0.5%) from 94.785%
1cffbe29-1693-4258-a2c0-32d8f02cd02c

push

circleci

web-flow
Merge pull request #362 from RomanBurunkov/master

Do not run next after abortion on limit

142 of 156 branches covered (0.0%)

12 of 12 new or added lines in 1 file covered. (100.0%)

315 of 334 relevant lines covered (94.31%)

843.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.71
/lib/processMultipart.js
1
const Busboy = require('busboy');
1✔
2
const UploadTimer = require('./uploadtimer');
1✔
3
const fileFactory = require('./fileFactory');
1✔
4
const memHandler = require('./memHandler');
1✔
5
const tempFileHandler = require('./tempFileHandler');
1✔
6
const processNested = require('./processNested');
1✔
7
const {
8
  isFunc,
9
  debugLog,
10
  buildFields,
11
  buildOptions,
12
  parseFileName
13
} = require('./utilities');
1✔
14

15
const waitFlushProperty = Symbol('wait flush property symbol');
1✔
16

17
/**
18
 * Processes multipart request
19
 * Builds a req.body object for fields
20
 * Builds a req.files object for files
21
 * @param  {Object}   options expressFileupload and Busboy options
22
 * @param  {Object}   req     Express request object
23
 * @param  {Object}   res     Express response object
24
 * @param  {Function} next    Express next method
25
 * @return {void}
26
 */
27
module.exports = (options, req, res, next) => {
1✔
28
  req.files = null;
86✔
29

30
  // Build busboy options and init busboy instance.
31
  const busboyOptions = buildOptions(options, { headers: req.headers });
86✔
32
  const busboy = Busboy(busboyOptions);
86✔
33

34
  /**
35
   * Closes connection with specified reason and http code.
36
   * @param {number} code HTTP response code, default: 400.
37
   * @param {*} reason Reason to close connection, default: 'Bad Request'.
38
   */
39
  const closeConnection = (code, reason) => {
86✔
40
    req.unpipe(busboy);
1✔
41
    req.resume();
1✔
42
    if (res.headersSent) {
1!
43
      debugLog(options, 'Headers already sent, can\'t close connection.');
×
44
      return;
×
45
    }
46
    const resCode = code || 400;
1!
47
    const resReason = reason || 'Bad Request';
1!
48
    debugLog(options, `Closing connection with ${resCode}: ${resReason}`);
1✔
49
    res.writeHead(resCode, { Connection: 'close' });
1✔
50
    res.end(resReason);
1✔
51
  };
52

53
  // Express proxies sometimes attach multipart data to a buffer
54
  if (req.body instanceof Buffer) {
86!
55
    req.body = Object.create(null);
×
56
  }
57
  // Build multipart req.body fields
58
  busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val));
86✔
59

60
  // Build req.files fields
61
  busboy.on('file', (field, file, info) => {
86✔
62
    // Parse file name(cutting huge names, decoding, etc..).
63
    const {filename:name, encoding, mimeType: mime} = info;
87✔
64
    const filename = parseFileName(options, name);
87✔
65
    // Define methods and handlers for upload process.
66
    const {
67
      dataHandler,
68
      getFilePath,
69
      getFileSize,
70
      getHash,
71
      complete,
72
      cleanup,
73
      getWritePromise
74
    } = options.useTempFiles
87✔
75
      ? tempFileHandler(options, field, filename) // Upload into temporary file.
76
      : memHandler(options, field, filename);     // Upload into RAM.
77

78
    const writePromise = options.useTempFiles
87✔
79
      ? getWritePromise().catch(err => {
80
        req.unpipe(busboy);
×
81
        req.resume();
×
82
        cleanup();
×
83
        next(err);
×
84
      }) : getWritePromise();
85

86
    // Define upload timer.
87
    const uploadTimer = new UploadTimer(options.uploadTimeout, () => {
87✔
88
      file.removeAllListeners('data');
3✔
89
      file.resume();
3✔
90
      // After destroy an error event will be emitted and file clean up will be done.
91
      // In some cases file.destroy() doesn't exist, so we need to check this, see issue:
92
      // https://github.com/richardgirges/express-fileupload/issues/259.
93
      const err = new Error(`Upload timeout for ${field}->${filename}, bytes:${getFileSize()}`);
3✔
94
      return isFunc(file.destroy) ? file.destroy(err) : file.emit('error', err);
3!
95
    });
96

97
    file.on('limit', () => {
87✔
98
      debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`);
3✔
99
      // Reset upload timer in case of file limit reached.
100
      uploadTimer.clear();
3✔
101
      // Run a user defined limit handler if it has been set.
102
      if (isFunc(options.limitHandler)) {
3✔
103
        options.limitHandler(req, res, next);
1✔
104
      }
105
      // Close connection with 413 code and do cleanup if abortOnLimit set(default: false).
106
      if (options.abortOnLimit) {
3✔
107
        debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`);
1✔
108
        closeConnection(413, options.responseOnLimit);
1✔
109
        cleanup();
1✔
110
      }
111
    });
112

113
    file.on('data', (data) => {
87✔
114
      uploadTimer.set(); // Refresh upload timer each time new data chunk came.
367✔
115
      dataHandler(data); // Handle new piece of data.
367✔
116
    });
117

118
    file.on('end', () => {
87✔
119
      const size = getFileSize();
82✔
120
      // Debug logging for file upload ending.
121
      debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`);
82✔
122
      // Reset upload timer in case of end event.
123
      uploadTimer.clear();
82✔
124
      // See https://github.com/richardgirges/express-fileupload/issues/191
125
      // Do not add file instance to the req.files if original name and size are empty.
126
      // Empty name and zero size indicates empty file field in the posted form.
127
      if (!name && size === 0) {
82!
128
        if (options.useTempFiles) {
×
129
          cleanup();
×
130
          debugLog(options, `Removing the empty file ${field}->${filename}`);
×
131
        }
132
        return debugLog(options, `Don't add file instance if original name and size are empty`);
×
133
      }
134
      req.files = buildFields(req.files, field, fileFactory({
82✔
135
        buffer: complete(),
136
        name: filename,
137
        tempFilePath: getFilePath(),
138
        hash: getHash(),
139
        size,
140
        encoding,
141
        truncated: file.truncated,
142
        mimetype: mime
143
      }, options));
144

145
      if (!req[waitFlushProperty]) {
82✔
146
        req[waitFlushProperty] = [];
76✔
147
      }
148
      req[waitFlushProperty].push(writePromise);
82✔
149
    });
150

151
    file.on('error', (err) => {
87✔
152
      uploadTimer.clear(); // Reset upload timer in case of errors.
3✔
153
      debugLog(options, err);
3✔
154
      cleanup();
3✔
155
      next();
3✔
156
    });
157

158
    // Debug logging for a new file upload.
159
    debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`);
87✔
160
    // Set new upload timeout for a new file.
161
    uploadTimer.set();
87✔
162
  });
163

164
  busboy.on('finish', () => {
86✔
165
    debugLog(options, `Busboy finished parsing request.`);
81✔
166
    if (options.parseNested) {
81✔
167
      req.body = processNested(req.body);
1✔
168
      req.files = processNested(req.files);
1✔
169
    }
170

171
    if (!req[waitFlushProperty]) return next();
81✔
172
    Promise.all(req[waitFlushProperty])
76✔
173
      .then(() => {
174
        delete req[waitFlushProperty];
76✔
175
        next();
76✔
176
      });
177
  });
178

179
  busboy.on('error', (err) => {
86✔
180
    debugLog(options, `Busboy error`);
×
181
    next(err);
×
182
  });
183

184
  req.pipe(busboy);
86✔
185
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc