Repository navigation
Expand file tree
/
Copy pathindex.js
More file actions
251 lines (221 loc) · 9.01 KB
/
Copy pathindex.js
File metadata and controls
251 lines (221 loc) · 9.01 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
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
var makeMiddleware = require('./lib/make-middleware')
var diskStorage = require('./storage/disk')
var memoryStorage = require('./storage/memory')
var MulterError = require('./lib/multer-error')
var validateLimits = require('./lib/validate-limits')
/**
* A file parsed from a multipart request.
*
* @typedef {Object} File
* @property {string} fieldname Name of the form field
* @property {string} originalname Name of the file on the client (client-supplied, treat as untrusted)
* @property {string} encoding Transfer encoding of the file
* @property {string} mimetype MIME type of the file
* @property {number} [size] Size of the file in bytes; set once the file has been stored
* @property {ReadableStream} [stream] The file data; only present while a storage engine's `_handleFile` runs
* @property {string} [destination] Folder the file was saved to (`DiskStorage`)
* @property {string} [filename] Name of the file within `destination` (`DiskStorage`)
* @property {string} [path] Full path of the saved file (`DiskStorage`)
* @property {Buffer} [buffer] Contents of the file (`MemoryStorage`)
*/
/**
* Size limits, passed to busboy. All are optional.
*
* @typedef {Object} Limits
* @property {number} [fieldNameSize=100] Max field name size in bytes
* @property {number} [fieldSize=1048576] Max field value size in bytes
* @property {number} [fields=Infinity] Max number of non-file fields
* @property {number} [fileSize=Infinity] Max file size in bytes (integer or `Infinity`)
* @property {number} [files=Infinity] Max number of file fields
* @property {number} [parts=Infinity] Max number of parts (fields + files)
* @property {number} [headerPairs=2000] Max number of header key/value pairs to parse
* @property {number} [fieldNestingDepth=Infinity] Max nesting depth of field names (`a[b][c]` has 2 levels)
* @property {number} [fieldArrayIndexLimit=Infinity] Max numeric array index accepted in field names
*/
/**
* Decides whether a file is accepted. Call `cb(null, true)` to accept the
* file, `cb(null, false)` to skip it silently, or `cb(err)` to abort.
*
* @callback FileFilter
* @param {Object} req The request
* @param {File} file The file being uploaded; `size`, `stream`, `path` and `buffer` are not set yet
* @param {function(?Error, boolean=): void} cb
*/
/**
* Storage engine. See StorageEngine.md for the full contract.
*
* @typedef {Object} StorageEngine
* @property {function(Object, File, function(?Error, Object=): void): void} _handleFile
* Consumes `file.stream` and calls back with the properties to merge into the file object
* @property {function(Object, File, function(?Error): void): void} _removeFile
* Removes a stored file when the request fails
*/
/**
* @typedef {Object} Options
* @property {string} [dest] Folder to store files in (uses `DiskStorage`)
* @property {StorageEngine} [storage] Storage engine; defaults to `MemoryStorage` when neither `dest` nor `storage` is set
* @property {FileFilter} [fileFilter] Controls which files are accepted
* @property {Limits|function(Object): Limits} [limits] Size limits, or a function of the request returning them
* @property {boolean} [preservePath=false] Keep the full client-supplied path in `file.originalname`
* @property {string} [defParamCharset='latin1'] Charset for part header parameters (e.g. filename) without an explicit one
* @property {string} [defCharset='utf8'] Charset for text field values that do not declare one
* @property {number} [highWaterMark] `highWaterMark` of the multipart parser stream; busboy's default
* @property {number} [fileHwm] `highWaterMark` of each file stream (`file.stream`); busboy's default
* @property {function(Object, Object): void} [streamHandler] Feeds the request body to busboy; defaults to `req.pipe(busboy)`
*/
function allowAll (req, file, cb) {
cb(null, true)
}
/**
* @constructor
* @private
* @param {Options} options
*/
function Multer (options) {
if (options.storage) {
this.storage = options.storage
} else if (options.dest) {
this.storage = diskStorage({ destination: options.dest })
} else {
this.storage = memoryStorage()
}
if (options.limits && typeof options.limits !== 'function') validateLimits(options.limits)
this.limits = options.limits
this.preservePath = options.preservePath
this.defParamCharset = options.defParamCharset || 'latin1'
this.defCharset = options.defCharset
this.highWaterMark = options.highWaterMark
this.fileHwm = options.fileHwm
this.fileFilter = options.fileFilter || allowAll
if (options.streamHandler !== undefined && typeof options.streamHandler !== 'function') {
throw new TypeError('Expected streamHandler to be a function')
}
this.streamHandler = options.streamHandler
}
Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
function setup () {
var fileFilter = this.fileFilter
var filesLeft = Object.create(null)
fields.forEach(function (field) {
if (typeof field.maxCount === 'number') {
filesLeft[field.name] = field.maxCount
} else {
filesLeft[field.name] = Infinity
}
})
function wrappedFileFilter (req, file, cb) {
if ((filesLeft[file.fieldname] || 0) <= 0) {
return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname, file.originalname))
}
// Reserve the slot synchronously before yielding to the async fileFilter,
// so files emitted together in one busboy write cannot all read the
// pre-decrement count and slip past maxCount. Release it on reject/error
// (a skipped file must not consume a slot, #1419), and only once so a
// filter that calls back repeatedly cannot leak slots.
filesLeft[file.fieldname] -= 1
var settled = false
fileFilter(req, file, function (err, includeFile) {
if (!settled) {
settled = true
if (err || !includeFile) filesLeft[file.fieldname] += 1
}
cb(err, includeFile)
})
}
return {
limits: this.limits,
preservePath: this.preservePath,
defParamCharset: this.defParamCharset,
defCharset: this.defCharset,
highWaterMark: this.highWaterMark,
fileHwm: this.fileHwm,
streamHandler: this.streamHandler,
storage: this.storage,
fileFilter: wrappedFileFilter,
fileStrategy: fileStrategy
}
}
return makeMiddleware(setup.bind(this))
}
/**
* Accept a single file for the field `name`. The file is stored in `req.file`.
*
* @param {string} name
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.single = function (name) {
return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE')
}
/**
* Accept an array of files for the field `name`, stored in `req.files`.
* Files skipped by `fileFilter` do not count towards `maxCount`.
*
* @param {string} name
* @param {number} [maxCount] Error with `LIMIT_UNEXPECTED_FILE` if more files are accepted
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.array = function (name, maxCount) {
return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY')
}
/**
* Accept a mix of files. `req.files` is an object keyed by field name, each
* value an array of files.
*
* @param {Array<{name: string, maxCount?: number}>} fields
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.fields = function (fields) {
return this._makeMiddleware(fields, 'OBJECT')
}
/**
* Accept only text fields. Any file results in a `LIMIT_UNEXPECTED_FILE` error.
*
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.none = function () {
return this._makeMiddleware([], 'NONE')
}
/**
* Accept all files, stored as an array in `req.files`. Only use this on routes
* that handle every uploaded file.
*
* @returns {function(Object, Object, function(?Error): void): void} Express middleware
*/
Multer.prototype.any = function () {
function setup () {
return {
limits: this.limits,
preservePath: this.preservePath,
defParamCharset: this.defParamCharset,
defCharset: this.defCharset,
highWaterMark: this.highWaterMark,
fileHwm: this.fileHwm,
streamHandler: this.streamHandler,
storage: this.storage,
fileFilter: this.fileFilter,
fileStrategy: 'ARRAY'
}
}
return makeMiddleware(setup.bind(this))
}
/**
* Create a multer instance. Text fields are parsed into `req.body`; files go to
* `req.file` or `req.files` depending on the method used.
*
* @param {Options} [options]
* @returns {Multer}
* @throws {TypeError} If `options` is not an object
*/
function multer (options) {
if (options === undefined) {
return new Multer({})
}
if (typeof options === 'object' && options !== null) {
return new Multer(options)
}
throw new TypeError('Expected object for argument options')
}
module.exports = multer
module.exports.diskStorage = diskStorage
module.exports.memoryStorage = memoryStorage
module.exports.MulterError = MulterError