forked from expressjs/multer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathread-body.js
More file actions
130 lines (100 loc) · 4.04 KB
/
read-body.js
File metadata and controls
130 lines (100 loc) · 4.04 KB
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
import { extname } from 'node:path'
import { pipeline as _pipeline } from 'node:stream'
import { promisify } from 'node:util'
import Busboy from '@fastify/busboy'
import { createWriteStream } from 'fs-temp'
import hasOwnProperty from 'has-own-property'
import _onFinished from 'on-finished'
import FileType from 'stream-file-type'
import MulterError from './error.js'
const onFinished = promisify(_onFinished)
const pipeline = promisify(_pipeline)
function drainStream (stream) {
stream.on('readable', stream.read.bind(stream))
}
function collectFields (busboy, limits) {
return new Promise((resolve, reject) => {
const result = []
busboy.on('field', (fieldname, value, fieldnameTruncated, valueTruncated) => {
// Currently not implemented (https://github.com/mscdex/busboy/issues/6)
/* c8 ignore next */
if (fieldnameTruncated) return reject(new MulterError('LIMIT_FIELD_KEY'))
if (valueTruncated) return reject(new MulterError('LIMIT_FIELD_VALUE', fieldname))
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) {
return reject(new MulterError('LIMIT_FIELD_KEY'))
}
result.push({ key: fieldname, value: value })
})
busboy.on('finish', () => resolve(result))
})
}
function collectFiles (busboy, limits, fileFilter) {
return new Promise((resolve, reject) => {
const result = []
busboy.on('file', async (fieldname, fileStream, filename, encoding, mimetype) => {
// Catch all errors on file stream
fileStream.on('error', reject)
// Catch limit exceeded on file stream
fileStream.on('limit', () => {
reject(new MulterError('LIMIT_FILE_SIZE', fieldname))
})
// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && hasOwnProperty(limits, 'fieldNameSize') && fieldname.length > limits.fieldNameSize) {
return reject(new MulterError('LIMIT_FIELD_KEY'))
}
const file = {
fieldName: fieldname,
originalName: filename,
clientReportedMimeType: mimetype,
clientReportedFileExtension: extname(filename || '')
}
try {
fileFilter(file)
} catch (err) {
return reject(err)
}
const target = createWriteStream()
const detector = new FileType()
const fileClosed = new Promise((resolve) => target.on('close', resolve))
const promise = pipeline(fileStream, detector, target)
.then(async () => {
await fileClosed
file.path = target.path
file.size = target.bytesWritten
const fileType = await detector.fileTypePromise()
file.detectedMimeType = (fileType ? fileType.mime : null)
file.detectedFileExtension = (fileType ? `.${fileType.ext}` : '')
return file
})
.catch(reject)
result.push(promise)
})
busboy.on('finish', () => resolve(Promise.all(result)))
})
}
export default async function readBody (req, limits, fileFilter) {
const busboy = new Busboy({ headers: req.headers, limits: limits })
const fields = collectFields(busboy, limits)
const files = collectFiles(busboy, limits, fileFilter)
const guard = new Promise((resolve, reject) => {
req.on('error', (err) => reject(err))
busboy.on('error', (err) => reject(err))
req.on('aborted', () => reject(new MulterError('CLIENT_ABORTED')))
busboy.on('filesLimit', () => reject(new MulterError('LIMIT_FILE_COUNT')))
busboy.on('fieldsLimit', () => reject(new MulterError('LIMIT_FIELD_COUNT')))
busboy.on('finish', resolve)
})
req.pipe(busboy)
try {
const result = await Promise.all([fields, files, guard])
return { fields: result[0], files: result[1] }
} catch (err) {
req.unpipe(busboy)
drainStream(req)
busboy.removeAllListeners()
// Wait for request to close, finish, or error
await onFinished(req).catch(/* c8 ignore next: Already handled by req.on('error', _) */ () => {})
throw err
}
}