source: node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 6.2 KB
Line 
1'use strict'
2
3const WritableStream = require('node:stream').Writable
4const inherits = require('node:util').inherits
5
6const StreamSearch = require('../../streamsearch/sbmh')
7
8const PartStream = require('./PartStream')
9const HeaderParser = require('./HeaderParser')
10
11const DASH = 45
12const B_ONEDASH = Buffer.from('-')
13const B_CRLF = Buffer.from('\r\n')
14const EMPTY_FN = function () {}
15
16function Dicer (cfg) {
17 if (!(this instanceof Dicer)) { return new Dicer(cfg) }
18 WritableStream.call(this, cfg)
19
20 if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) { throw new TypeError('Boundary required') }
21
22 if (typeof cfg.boundary === 'string') { this.setBoundary(cfg.boundary) } else { this._bparser = undefined }
23
24 this._headerFirst = cfg.headerFirst
25
26 this._dashes = 0
27 this._parts = 0
28 this._finished = false
29 this._realFinish = false
30 this._isPreamble = true
31 this._justMatched = false
32 this._firstWrite = true
33 this._inHeader = true
34 this._part = undefined
35 this._cb = undefined
36 this._ignoreData = false
37 this._partOpts = { highWaterMark: cfg.partHwm }
38 this._pause = false
39
40 const self = this
41 this._hparser = new HeaderParser(cfg)
42 this._hparser.on('header', function (header) {
43 self._inHeader = false
44 self._part.emit('header', header)
45 })
46}
47inherits(Dicer, WritableStream)
48
49Dicer.prototype.emit = function (ev) {
50 if (ev === 'finish' && !this._realFinish) {
51 if (!this._finished) {
52 const self = this
53 process.nextTick(function () {
54 self.emit('error', new Error('Unexpected end of multipart data'))
55 if (self._part && !self._ignoreData) {
56 const type = (self._isPreamble ? 'Preamble' : 'Part')
57 self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data'))
58 self._part.push(null)
59 process.nextTick(function () {
60 self._realFinish = true
61 self.emit('finish')
62 self._realFinish = false
63 })
64 return
65 }
66 self._realFinish = true
67 self.emit('finish')
68 self._realFinish = false
69 })
70 }
71 } else { WritableStream.prototype.emit.apply(this, arguments) }
72}
73
74Dicer.prototype._write = function (data, encoding, cb) {
75 // ignore unexpected data (e.g. extra trailer data after finished)
76 if (!this._hparser && !this._bparser) { return cb() }
77
78 if (this._headerFirst && this._isPreamble) {
79 if (!this._part) {
80 this._part = new PartStream(this._partOpts)
81 if (this._events.preamble) { this.emit('preamble', this._part) } else { this._ignore() }
82 }
83 const r = this._hparser.push(data)
84 if (!this._inHeader && r !== undefined && r < data.length) { data = data.slice(r) } else { return cb() }
85 }
86
87 // allows for "easier" testing
88 if (this._firstWrite) {
89 this._bparser.push(B_CRLF)
90 this._firstWrite = false
91 }
92
93 this._bparser.push(data)
94
95 if (this._pause) { this._cb = cb } else { cb() }
96}
97
98Dicer.prototype.reset = function () {
99 this._part = undefined
100 this._bparser = undefined
101 this._hparser = undefined
102}
103
104Dicer.prototype.setBoundary = function (boundary) {
105 const self = this
106 this._bparser = new StreamSearch('\r\n--' + boundary)
107 this._bparser.on('info', function (isMatch, data, start, end) {
108 self._oninfo(isMatch, data, start, end)
109 })
110}
111
112Dicer.prototype._ignore = function () {
113 if (this._part && !this._ignoreData) {
114 this._ignoreData = true
115 this._part.on('error', EMPTY_FN)
116 // we must perform some kind of read on the stream even though we are
117 // ignoring the data, otherwise node's Readable stream will not emit 'end'
118 // after pushing null to the stream
119 this._part.resume()
120 }
121}
122
123Dicer.prototype._oninfo = function (isMatch, data, start, end) {
124 let buf; const self = this; let i = 0; let r; let shouldWriteMore = true
125
126 if (!this._part && this._justMatched && data) {
127 while (this._dashes < 2 && (start + i) < end) {
128 if (data[start + i] === DASH) {
129 ++i
130 ++this._dashes
131 } else {
132 if (this._dashes) { buf = B_ONEDASH }
133 this._dashes = 0
134 break
135 }
136 }
137 if (this._dashes === 2) {
138 if ((start + i) < end && this._events.trailer) { this.emit('trailer', data.slice(start + i, end)) }
139 this.reset()
140 this._finished = true
141 // no more parts will be added
142 if (self._parts === 0) {
143 self._realFinish = true
144 self.emit('finish')
145 self._realFinish = false
146 }
147 }
148 if (this._dashes) { return }
149 }
150 if (this._justMatched) { this._justMatched = false }
151 if (!this._part) {
152 this._part = new PartStream(this._partOpts)
153 this._part._read = function (n) {
154 self._unpause()
155 }
156 if (this._isPreamble && this._events.preamble) { this.emit('preamble', this._part) } else if (this._isPreamble !== true && this._events.part) { this.emit('part', this._part) } else { this._ignore() }
157 if (!this._isPreamble) { this._inHeader = true }
158 }
159 if (data && start < end && !this._ignoreData) {
160 if (this._isPreamble || !this._inHeader) {
161 if (buf) { shouldWriteMore = this._part.push(buf) }
162 shouldWriteMore = this._part.push(data.slice(start, end))
163 if (!shouldWriteMore) { this._pause = true }
164 } else if (!this._isPreamble && this._inHeader) {
165 if (buf) { this._hparser.push(buf) }
166 r = this._hparser.push(data.slice(start, end))
167 if (!this._inHeader && r !== undefined && r < end) { this._oninfo(false, data, start + r, end) }
168 }
169 }
170 if (isMatch) {
171 this._hparser.reset()
172 if (this._isPreamble) { this._isPreamble = false } else {
173 if (start !== end) {
174 ++this._parts
175 this._part.on('end', function () {
176 if (--self._parts === 0) {
177 if (self._finished) {
178 self._realFinish = true
179 self.emit('finish')
180 self._realFinish = false
181 } else {
182 self._unpause()
183 }
184 }
185 })
186 }
187 }
188 this._part.push(null)
189 this._part = undefined
190 this._ignoreData = false
191 this._justMatched = true
192 this._dashes = 0
193 }
194}
195
196Dicer.prototype._unpause = function () {
197 if (!this._pause) { return }
198
199 this._pause = false
200 if (this._cb) {
201 const cb = this._cb
202 this._cb = undefined
203 cb()
204 }
205}
206
207module.exports = Dicer
Note: See TracBrowser for help on using the repository browser.