UNPKG

3.71 kBJavaScriptView Raw
1'use strict'
2
3const Busboy = require('busboy')
4const BlackHoleStream = require('black-hole-stream')
5const Result = require('./result')
6
7const getDescriptor = Object.getOwnPropertyDescriptor
8const isArray = Array.isArray
9
10module.exports = (request, options) => {
11 const res = new Result()
12
13 // - what used to be a `chan` will now be a thenable
14 // - each time the thenable.then() is called, a Promise is returned. when the
15 // next value is ready, the promise is resolved with it OR if the value is an
16 // error, the promise is rejected and the thenable is marked done.
17 // - if a done thenable is awaited, a Promise resolved with
18 // an empty value is returned.
19
20 // koa special sauce
21 request = request.req || request
22
23 options = options || {}
24 options.headers = request.headers
25 // options.checkField hook `function(name, val, fieldnameTruncated, valTruncated)`
26 // options.checkFile hook `function(fieldname, fileStream, filename, encoding, mimetype)`
27 const checkField = options.checkField
28 const checkFile = options.checkFile
29 let lastError
30
31 const busboy = new Busboy(options)
32
33 request.on('close', cleanup)
34
35 busboy
36 .on('field', onField)
37 .on('file', onFile)
38 .on('close', cleanup)
39 .on('error', onEnd)
40 .on('finish', onEnd)
41
42 busboy.on('partsLimit', () => {
43 const err = new Error('Reach parts limit')
44 err.code = 'Request_parts_limit'
45 err.status = 413
46 onError(err)
47 })
48
49 busboy.on('filesLimit', () => {
50 const err = new Error('Reach files limit')
51 err.code = 'Request_files_limit'
52 err.status = 413
53 onError(err)
54 })
55
56 busboy.on('fieldsLimit', () => {
57 const err = new Error('Reach fields limit')
58 err.code = 'Request_fields_limit'
59 err.status = 413
60 onError(err)
61 })
62
63 request.pipe(busboy)
64
65 if (options.autoFields) {
66 var field = res.field = {} // object lookup
67 var fields = res.fields = [] // list lookup
68 }
69
70 return res
71
72 function onField (name, val, fieldnameTruncated, valTruncated) {
73 if (checkField) {
74 const err = checkField(name, val, fieldnameTruncated, valTruncated)
75 if (err) {
76 return onError(err)
77 }
78 }
79
80 const args = [name, val, fieldnameTruncated, valTruncated]
81
82 if (options.autoFields) {
83 fields.push(args)
84
85 // don't overwrite prototypes
86 if (getDescriptor(Object.prototype, name)) return
87
88 const prev = field[name]
89
90 if (prev == null) {
91 field[name] = val
92 return
93 }
94
95 if (isArray(prev)) {
96 prev.push(val)
97 return
98 }
99
100 field[name] = [prev, val]
101 } else {
102 res.add(args)
103 }
104 }
105
106 function onFile (fieldname, file, filename, encoding, mimetype) {
107 if (checkFile) {
108 const err = checkFile(fieldname, file, filename, encoding, mimetype)
109 if (err) {
110 // make sure request stream's data has been read
111 const blackHoleStream = new BlackHoleStream()
112 file.pipe(blackHoleStream)
113 return onError(err)
114 }
115 }
116
117 file.fieldname = fieldname
118 file.filename = filename
119 file.transferEncoding = file.encoding = encoding
120 file.mimeType = file.mime = mimetype
121 res.add(file)
122 }
123
124 function onError (err) {
125 lastError = err
126 }
127
128 function onEnd (err) {
129 cleanup()
130 res.close(err || lastError)
131 }
132
133 function cleanup () {
134 request.removeListener('close', cleanup)
135 busboy.removeListener('field', onField)
136 busboy.removeListener('file', onFile)
137 busboy.removeListener('close', cleanup)
138 busboy.removeListener('error', onEnd)
139 busboy.removeListener('partsLimit', onEnd)
140 busboy.removeListener('filesLimit', onEnd)
141 busboy.removeListener('fieldsLimit', onEnd)
142 busboy.removeListener('finish', onEnd)
143 }
144}