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