This repository was archived by the owner on Mar 10, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 298
/
Copy pathmultipart.js
128 lines (103 loc) · 3 KB
/
multipart.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
'use strict'
const Transform = require('readable-stream').Transform
const isNode = require('detect-node')
const isSource = require('is-pull-stream').isSource
const toStream = require('pull-to-stream')
const PADDING = '--'
const NEW_LINE = '\r\n'
const NEW_LINE_BUFFER = Buffer.from(NEW_LINE)
class Multipart extends Transform {
constructor (options) {
super(Object.assign({}, options, { objectMode: true, highWaterMark: 1 }))
this._boundary = this._generateBoundary()
this._files = []
this._draining = false
}
_flush () {
this.push(Buffer.from(PADDING + this._boundary + PADDING + NEW_LINE))
this.push(null)
}
_generateBoundary () {
var boundary = '--------------------------'
for (var i = 0; i < 24; i++) {
boundary += Math.floor(Math.random() * 10).toString(16)
}
return boundary
}
_transform (file, encoding, callback) {
if (Buffer.isBuffer(file)) {
this.push(file)
return callback() // early
}
// not a buffer, must be a file
this._files.push(file)
this._maybeDrain(callback)
}
_maybeDrain (callback) {
if (!this._draining) {
if (this._files.length) {
this._draining = true
const file = this._files.shift()
this._pushFile(file, (err) => {
this._draining = false
if (err) {
this.emit('error', err)
} else {
this._maybeDrain(callback)
}
})
} else {
this.emit('drained all files')
callback()
}
} else {
this.once('drained all files', callback)
}
}
_pushFile (file, callback) {
const leading = this._leading(file.headers || {})
this.push(leading)
let content = file.content || Buffer.alloc(0)
if (Buffer.isBuffer(content)) {
this.push(content)
this.push(NEW_LINE_BUFFER)
return callback() // early
}
if (isSource(content)) {
content = toStream.readable(content)
}
// From now on we assume content is a stream
content.once('error', this.emit.bind(this, 'error'))
content.once('end', () => {
this.push(NEW_LINE_BUFFER)
callback()
// TODO: backpressure!!! wait once self is drained so we can proceed
// This does not work
// this.once('drain', () => {
// callback()
// })
})
content.on('data', (data) => {
const drained = this.push(data)
// Only do the drain dance on Node.js.
// In browserland, the underlying stream
// does NOT drain because the request is only sent
// once this stream ends.
if (!drained && isNode) {
content.pause()
this.once('drain', () => content.resume())
}
})
}
_leading (headers) {
var leading = [PADDING + this._boundary]
Object.keys(headers).forEach((header) => {
leading.push(header + ': ' + headers[header])
})
leading.push('')
leading.push('')
const leadingStr = leading.join(NEW_LINE)
return Buffer.from(leadingStr)
}
}
module.exports = Multipart