1 | 'use strict'
|
---|
2 |
|
---|
3 | const assert = require('assert')
|
---|
4 | const Buffer = require('buffer').Buffer
|
---|
5 | const realZlib = require('zlib')
|
---|
6 |
|
---|
7 | const constants = exports.constants = require('./constants.js')
|
---|
8 | const Minipass = require('minipass')
|
---|
9 |
|
---|
10 | const OriginalBufferConcat = Buffer.concat
|
---|
11 |
|
---|
12 | const _superWrite = Symbol('_superWrite')
|
---|
13 | class ZlibError extends Error {
|
---|
14 | constructor (err) {
|
---|
15 | super('zlib: ' + err.message)
|
---|
16 | this.code = err.code
|
---|
17 | this.errno = err.errno
|
---|
18 | /* istanbul ignore if */
|
---|
19 | if (!this.code)
|
---|
20 | this.code = 'ZLIB_ERROR'
|
---|
21 |
|
---|
22 | this.message = 'zlib: ' + err.message
|
---|
23 | Error.captureStackTrace(this, this.constructor)
|
---|
24 | }
|
---|
25 |
|
---|
26 | get name () {
|
---|
27 | return 'ZlibError'
|
---|
28 | }
|
---|
29 | }
|
---|
30 |
|
---|
31 | // the Zlib class they all inherit from
|
---|
32 | // This thing manages the queue of requests, and returns
|
---|
33 | // true or false if there is anything in the queue when
|
---|
34 | // you call the .write() method.
|
---|
35 | const _opts = Symbol('opts')
|
---|
36 | const _flushFlag = Symbol('flushFlag')
|
---|
37 | const _finishFlushFlag = Symbol('finishFlushFlag')
|
---|
38 | const _fullFlushFlag = Symbol('fullFlushFlag')
|
---|
39 | const _handle = Symbol('handle')
|
---|
40 | const _onError = Symbol('onError')
|
---|
41 | const _sawError = Symbol('sawError')
|
---|
42 | const _level = Symbol('level')
|
---|
43 | const _strategy = Symbol('strategy')
|
---|
44 | const _ended = Symbol('ended')
|
---|
45 | const _defaultFullFlush = Symbol('_defaultFullFlush')
|
---|
46 |
|
---|
47 | class ZlibBase extends Minipass {
|
---|
48 | constructor (opts, mode) {
|
---|
49 | if (!opts || typeof opts !== 'object')
|
---|
50 | throw new TypeError('invalid options for ZlibBase constructor')
|
---|
51 |
|
---|
52 | super(opts)
|
---|
53 | this[_sawError] = false
|
---|
54 | this[_ended] = false
|
---|
55 | this[_opts] = opts
|
---|
56 |
|
---|
57 | this[_flushFlag] = opts.flush
|
---|
58 | this[_finishFlushFlag] = opts.finishFlush
|
---|
59 | // this will throw if any options are invalid for the class selected
|
---|
60 | try {
|
---|
61 | this[_handle] = new realZlib[mode](opts)
|
---|
62 | } catch (er) {
|
---|
63 | // make sure that all errors get decorated properly
|
---|
64 | throw new ZlibError(er)
|
---|
65 | }
|
---|
66 |
|
---|
67 | this[_onError] = (err) => {
|
---|
68 | // no sense raising multiple errors, since we abort on the first one.
|
---|
69 | if (this[_sawError])
|
---|
70 | return
|
---|
71 |
|
---|
72 | this[_sawError] = true
|
---|
73 |
|
---|
74 | // there is no way to cleanly recover.
|
---|
75 | // continuing only obscures problems.
|
---|
76 | this.close()
|
---|
77 | this.emit('error', err)
|
---|
78 | }
|
---|
79 |
|
---|
80 | this[_handle].on('error', er => this[_onError](new ZlibError(er)))
|
---|
81 | this.once('end', () => this.close)
|
---|
82 | }
|
---|
83 |
|
---|
84 | close () {
|
---|
85 | if (this[_handle]) {
|
---|
86 | this[_handle].close()
|
---|
87 | this[_handle] = null
|
---|
88 | this.emit('close')
|
---|
89 | }
|
---|
90 | }
|
---|
91 |
|
---|
92 | reset () {
|
---|
93 | if (!this[_sawError]) {
|
---|
94 | assert(this[_handle], 'zlib binding closed')
|
---|
95 | return this[_handle].reset()
|
---|
96 | }
|
---|
97 | }
|
---|
98 |
|
---|
99 | flush (flushFlag) {
|
---|
100 | if (this.ended)
|
---|
101 | return
|
---|
102 |
|
---|
103 | if (typeof flushFlag !== 'number')
|
---|
104 | flushFlag = this[_fullFlushFlag]
|
---|
105 | this.write(Object.assign(Buffer.alloc(0), { [_flushFlag]: flushFlag }))
|
---|
106 | }
|
---|
107 |
|
---|
108 | end (chunk, encoding, cb) {
|
---|
109 | if (chunk)
|
---|
110 | this.write(chunk, encoding)
|
---|
111 | this.flush(this[_finishFlushFlag])
|
---|
112 | this[_ended] = true
|
---|
113 | return super.end(null, null, cb)
|
---|
114 | }
|
---|
115 |
|
---|
116 | get ended () {
|
---|
117 | return this[_ended]
|
---|
118 | }
|
---|
119 |
|
---|
120 | write (chunk, encoding, cb) {
|
---|
121 | // process the chunk using the sync process
|
---|
122 | // then super.write() all the outputted chunks
|
---|
123 | if (typeof encoding === 'function')
|
---|
124 | cb = encoding, encoding = 'utf8'
|
---|
125 |
|
---|
126 | if (typeof chunk === 'string')
|
---|
127 | chunk = Buffer.from(chunk, encoding)
|
---|
128 |
|
---|
129 | if (this[_sawError])
|
---|
130 | return
|
---|
131 | assert(this[_handle], 'zlib binding closed')
|
---|
132 |
|
---|
133 | // _processChunk tries to .close() the native handle after it's done, so we
|
---|
134 | // intercept that by temporarily making it a no-op.
|
---|
135 | const nativeHandle = this[_handle]._handle
|
---|
136 | const originalNativeClose = nativeHandle.close
|
---|
137 | nativeHandle.close = () => {}
|
---|
138 | const originalClose = this[_handle].close
|
---|
139 | this[_handle].close = () => {}
|
---|
140 | // It also calls `Buffer.concat()` at the end, which may be convenient
|
---|
141 | // for some, but which we are not interested in as it slows us down.
|
---|
142 | Buffer.concat = (args) => args
|
---|
143 | let result
|
---|
144 | try {
|
---|
145 | const flushFlag = typeof chunk[_flushFlag] === 'number'
|
---|
146 | ? chunk[_flushFlag] : this[_flushFlag]
|
---|
147 | result = this[_handle]._processChunk(chunk, flushFlag)
|
---|
148 | // if we don't throw, reset it back how it was
|
---|
149 | Buffer.concat = OriginalBufferConcat
|
---|
150 | } catch (err) {
|
---|
151 | // or if we do, put Buffer.concat() back before we emit error
|
---|
152 | // Error events call into user code, which may call Buffer.concat()
|
---|
153 | Buffer.concat = OriginalBufferConcat
|
---|
154 | this[_onError](new ZlibError(err))
|
---|
155 | } finally {
|
---|
156 | if (this[_handle]) {
|
---|
157 | // Core zlib resets `_handle` to null after attempting to close the
|
---|
158 | // native handle. Our no-op handler prevented actual closure, but we
|
---|
159 | // need to restore the `._handle` property.
|
---|
160 | this[_handle]._handle = nativeHandle
|
---|
161 | nativeHandle.close = originalNativeClose
|
---|
162 | this[_handle].close = originalClose
|
---|
163 | // `_processChunk()` adds an 'error' listener. If we don't remove it
|
---|
164 | // after each call, these handlers start piling up.
|
---|
165 | this[_handle].removeAllListeners('error')
|
---|
166 | // make sure OUR error listener is still attached tho
|
---|
167 | }
|
---|
168 | }
|
---|
169 |
|
---|
170 | if (this[_handle])
|
---|
171 | this[_handle].on('error', er => this[_onError](new ZlibError(er)))
|
---|
172 |
|
---|
173 | let writeReturn
|
---|
174 | if (result) {
|
---|
175 | if (Array.isArray(result) && result.length > 0) {
|
---|
176 | // The first buffer is always `handle._outBuffer`, which would be
|
---|
177 | // re-used for later invocations; so, we always have to copy that one.
|
---|
178 | writeReturn = this[_superWrite](Buffer.from(result[0]))
|
---|
179 | for (let i = 1; i < result.length; i++) {
|
---|
180 | writeReturn = this[_superWrite](result[i])
|
---|
181 | }
|
---|
182 | } else {
|
---|
183 | writeReturn = this[_superWrite](Buffer.from(result))
|
---|
184 | }
|
---|
185 | }
|
---|
186 |
|
---|
187 | if (cb)
|
---|
188 | cb()
|
---|
189 | return writeReturn
|
---|
190 | }
|
---|
191 |
|
---|
192 | [_superWrite] (data) {
|
---|
193 | return super.write(data)
|
---|
194 | }
|
---|
195 | }
|
---|
196 |
|
---|
197 | class Zlib extends ZlibBase {
|
---|
198 | constructor (opts, mode) {
|
---|
199 | opts = opts || {}
|
---|
200 |
|
---|
201 | opts.flush = opts.flush || constants.Z_NO_FLUSH
|
---|
202 | opts.finishFlush = opts.finishFlush || constants.Z_FINISH
|
---|
203 | super(opts, mode)
|
---|
204 |
|
---|
205 | this[_fullFlushFlag] = constants.Z_FULL_FLUSH
|
---|
206 | this[_level] = opts.level
|
---|
207 | this[_strategy] = opts.strategy
|
---|
208 | }
|
---|
209 |
|
---|
210 | params (level, strategy) {
|
---|
211 | if (this[_sawError])
|
---|
212 | return
|
---|
213 |
|
---|
214 | if (!this[_handle])
|
---|
215 | throw new Error('cannot switch params when binding is closed')
|
---|
216 |
|
---|
217 | // no way to test this without also not supporting params at all
|
---|
218 | /* istanbul ignore if */
|
---|
219 | if (!this[_handle].params)
|
---|
220 | throw new Error('not supported in this implementation')
|
---|
221 |
|
---|
222 | if (this[_level] !== level || this[_strategy] !== strategy) {
|
---|
223 | this.flush(constants.Z_SYNC_FLUSH)
|
---|
224 | assert(this[_handle], 'zlib binding closed')
|
---|
225 | // .params() calls .flush(), but the latter is always async in the
|
---|
226 | // core zlib. We override .flush() temporarily to intercept that and
|
---|
227 | // flush synchronously.
|
---|
228 | const origFlush = this[_handle].flush
|
---|
229 | this[_handle].flush = (flushFlag, cb) => {
|
---|
230 | this.flush(flushFlag)
|
---|
231 | cb()
|
---|
232 | }
|
---|
233 | try {
|
---|
234 | this[_handle].params(level, strategy)
|
---|
235 | } finally {
|
---|
236 | this[_handle].flush = origFlush
|
---|
237 | }
|
---|
238 | /* istanbul ignore else */
|
---|
239 | if (this[_handle]) {
|
---|
240 | this[_level] = level
|
---|
241 | this[_strategy] = strategy
|
---|
242 | }
|
---|
243 | }
|
---|
244 | }
|
---|
245 | }
|
---|
246 |
|
---|
247 | // minimal 2-byte header
|
---|
248 | class Deflate extends Zlib {
|
---|
249 | constructor (opts) {
|
---|
250 | super(opts, 'Deflate')
|
---|
251 | }
|
---|
252 | }
|
---|
253 |
|
---|
254 | class Inflate extends Zlib {
|
---|
255 | constructor (opts) {
|
---|
256 | super(opts, 'Inflate')
|
---|
257 | }
|
---|
258 | }
|
---|
259 |
|
---|
260 | // gzip - bigger header, same deflate compression
|
---|
261 | const _portable = Symbol('_portable')
|
---|
262 | class Gzip extends Zlib {
|
---|
263 | constructor (opts) {
|
---|
264 | super(opts, 'Gzip')
|
---|
265 | this[_portable] = opts && !!opts.portable
|
---|
266 | }
|
---|
267 |
|
---|
268 | [_superWrite] (data) {
|
---|
269 | if (!this[_portable])
|
---|
270 | return super[_superWrite](data)
|
---|
271 |
|
---|
272 | // we'll always get the header emitted in one first chunk
|
---|
273 | // overwrite the OS indicator byte with 0xFF
|
---|
274 | this[_portable] = false
|
---|
275 | data[9] = 255
|
---|
276 | return super[_superWrite](data)
|
---|
277 | }
|
---|
278 | }
|
---|
279 |
|
---|
280 | class Gunzip extends Zlib {
|
---|
281 | constructor (opts) {
|
---|
282 | super(opts, 'Gunzip')
|
---|
283 | }
|
---|
284 | }
|
---|
285 |
|
---|
286 | // raw - no header
|
---|
287 | class DeflateRaw extends Zlib {
|
---|
288 | constructor (opts) {
|
---|
289 | super(opts, 'DeflateRaw')
|
---|
290 | }
|
---|
291 | }
|
---|
292 |
|
---|
293 | class InflateRaw extends Zlib {
|
---|
294 | constructor (opts) {
|
---|
295 | super(opts, 'InflateRaw')
|
---|
296 | }
|
---|
297 | }
|
---|
298 |
|
---|
299 | // auto-detect header.
|
---|
300 | class Unzip extends Zlib {
|
---|
301 | constructor (opts) {
|
---|
302 | super(opts, 'Unzip')
|
---|
303 | }
|
---|
304 | }
|
---|
305 |
|
---|
306 | class Brotli extends ZlibBase {
|
---|
307 | constructor (opts, mode) {
|
---|
308 | opts = opts || {}
|
---|
309 |
|
---|
310 | opts.flush = opts.flush || constants.BROTLI_OPERATION_PROCESS
|
---|
311 | opts.finishFlush = opts.finishFlush || constants.BROTLI_OPERATION_FINISH
|
---|
312 |
|
---|
313 | super(opts, mode)
|
---|
314 |
|
---|
315 | this[_fullFlushFlag] = constants.BROTLI_OPERATION_FLUSH
|
---|
316 | }
|
---|
317 | }
|
---|
318 |
|
---|
319 | class BrotliCompress extends Brotli {
|
---|
320 | constructor (opts) {
|
---|
321 | super(opts, 'BrotliCompress')
|
---|
322 | }
|
---|
323 | }
|
---|
324 |
|
---|
325 | class BrotliDecompress extends Brotli {
|
---|
326 | constructor (opts) {
|
---|
327 | super(opts, 'BrotliDecompress')
|
---|
328 | }
|
---|
329 | }
|
---|
330 |
|
---|
331 | exports.Deflate = Deflate
|
---|
332 | exports.Inflate = Inflate
|
---|
333 | exports.Gzip = Gzip
|
---|
334 | exports.Gunzip = Gunzip
|
---|
335 | exports.DeflateRaw = DeflateRaw
|
---|
336 | exports.InflateRaw = InflateRaw
|
---|
337 | exports.Unzip = Unzip
|
---|
338 | /* istanbul ignore else */
|
---|
339 | if (typeof realZlib.BrotliCompress === 'function') {
|
---|
340 | exports.BrotliCompress = BrotliCompress
|
---|
341 | exports.BrotliDecompress = BrotliDecompress
|
---|
342 | } else {
|
---|
343 | exports.BrotliCompress = exports.BrotliDecompress = class {
|
---|
344 | constructor () {
|
---|
345 | throw new Error('Brotli is not supported in this version of Node.js')
|
---|
346 | }
|
---|
347 | }
|
---|
348 | }
|
---|