-
Notifications
You must be signed in to change notification settings - Fork 261
/
Copy pathprocessMultipart.js
158 lines (142 loc) · 5.33 KB
/
processMultipart.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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 = new 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');
};
// 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, name, encoding, mime) => {
// Parse file name(cutting huge names, decoding, etc..).
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.
// 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}.`);
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(getWritePromise());
});
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();
}).catch(err => {
delete req[waitFlushProperty];
debugLog(options, `Error while waiting files flush: ${err}`);
next(err);
});
});
busboy.on('error', (err) => {
debugLog(options, `Busboy error`);
next(err);
});
req.pipe(busboy);
};