Skip to content

Commit cccf0fe

Browse files
committed
🔒️ improve disconnect handling
1 parent 2c8cd23 commit cccf0fe

2 files changed

Lines changed: 89 additions & 21 deletions

File tree

lib/make-middleware.js

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,8 @@ function makeMiddleware (setup) {
2828

2929
req.body = Object.create(null)
3030

31-
req.on('error', function (err) {
32-
abortWithError(err)
33-
})
34-
3531
var busboy
36-
37-
try {
38-
busboy = Busboy({
39-
headers: req.headers,
40-
limits: limits,
41-
preservePath: preservePath,
42-
defParamCharset: defParamCharset
43-
})
44-
} catch (err) {
45-
return next(err)
46-
}
47-
48-
var appender = new FileAppender(fileStrategy, req)
32+
var appender = null
4933
var isDone = false
5034
var readFinished = false
5135
var errorOccured = false
@@ -55,12 +39,14 @@ function makeMiddleware (setup) {
5539
function done (err) {
5640
if (isDone) return
5741
isDone = true
58-
req.unpipe(busboy)
42+
if (busboy) {
43+
req.unpipe(busboy)
44+
setImmediate(() => {
45+
busboy.removeAllListeners()
46+
})
47+
}
5948
drainStream(req)
6049
req.resume()
61-
setImmediate(() => {
62-
busboy.removeAllListeners()
63-
})
6450
next(err)
6551
}
6652

@@ -90,6 +76,38 @@ function makeMiddleware (setup) {
9076
abortWithError(new MulterError(code, optionalField))
9177
}
9278

79+
function handleRequestFailure (err) {
80+
if (isDone) return
81+
if (busboy) busboy.destroy(err)
82+
abortWithError(err)
83+
}
84+
85+
req.on('error', function (err) {
86+
handleRequestFailure(err || new Error('Request error'))
87+
})
88+
89+
req.on('aborted', function () {
90+
handleRequestFailure(new Error('Request aborted'))
91+
})
92+
93+
req.on('close', function () {
94+
if (req.readableEnded) return
95+
handleRequestFailure(new Error('Request closed'))
96+
})
97+
98+
try {
99+
busboy = Busboy({
100+
headers: req.headers,
101+
limits: limits,
102+
preservePath: preservePath,
103+
defParamCharset: defParamCharset
104+
})
105+
} catch (err) {
106+
return next(err)
107+
}
108+
109+
appender = new FileAppender(fileStrategy, req)
110+
93111
// handle text field data
94112
busboy.on('field', function (fieldname, value, { nameTruncated, valueTruncated }) {
95113
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')

test/error-handling.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ var util = require('./_util')
77
var multer = require('../')
88
var stream = require('stream')
99
var FormData = require('form-data')
10+
var http = require('http')
11+
var net = require('net')
1012

1113
function withLimits (limits, fields) {
1214
var storage = multer.memoryStorage()
@@ -277,4 +279,52 @@ describe('Error Handling', function () {
277279
done()
278280
})
279281
})
282+
283+
it('should not hang when client aborts multipart upload', function (done) {
284+
this.timeout(5000)
285+
286+
var upload = multer({ storage: multer.memoryStorage() }).any()
287+
288+
var server = http.createServer(function (req, res) {
289+
var hung = false
290+
291+
var timer = setTimeout(function () {
292+
hung = true
293+
server.close()
294+
done(new Error('Middleware hung when client aborted request'))
295+
}, 1000)
296+
297+
upload(req, res, function (/* err */) {
298+
if (hung) return
299+
clearTimeout(timer)
300+
server.close()
301+
done()
302+
})
303+
})
304+
305+
server.listen(0, function () {
306+
var port = server.address().port
307+
var boundary = 'PoC' + Date.now()
308+
var sock = new net.Socket()
309+
310+
sock.connect(port, '127.0.0.1', function () {
311+
sock.write(
312+
'POST / HTTP/1.1\r\n' +
313+
'Host: localhost\r\n' +
314+
'Content-Type: multipart/form-data; boundary=' + boundary + '\r\n' +
315+
'Content-Length: 999999\r\n\r\n' +
316+
'--' + boundary + '\r\n' +
317+
'Content-Disposition: form-data; name="file"; filename="test.bin"\r\n' +
318+
'Content-Type: application/octet-stream\r\n\r\n' +
319+
'AAAAAAAAAAAAAAAA'
320+
)
321+
322+
setTimeout(function () {
323+
sock.destroy()
324+
}, 50)
325+
})
326+
327+
sock.on('error', function () {})
328+
})
329+
})
280330
})

0 commit comments

Comments
 (0)