You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							349 lines
						
					
					
						
							9.2 KiB
						
					
					
				
			
		
		
	
	
							349 lines
						
					
					
						
							9.2 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| const assert = require('assert')
 | |
| const Buffer = require('buffer').Buffer
 | |
| const realZlib = require('zlib')
 | |
| 
 | |
| const constants = exports.constants = require('./constants.js')
 | |
| const Minipass = require('minipass')
 | |
| 
 | |
| const OriginalBufferConcat = Buffer.concat
 | |
| 
 | |
| const _superWrite = Symbol('_superWrite')
 | |
| class ZlibError extends Error {
 | |
|   constructor (err) {
 | |
|     super('zlib: ' + err.message)
 | |
|     this.code = err.code
 | |
|     this.errno = err.errno
 | |
|     /* istanbul ignore if */
 | |
|     if (!this.code)
 | |
|       this.code = 'ZLIB_ERROR'
 | |
| 
 | |
|     this.message = 'zlib: ' + err.message
 | |
|     Error.captureStackTrace(this, this.constructor)
 | |
|   }
 | |
| 
 | |
|   get name () {
 | |
|     return 'ZlibError'
 | |
|   }
 | |
| }
 | |
| 
 | |
| // the Zlib class they all inherit from
 | |
| // This thing manages the queue of requests, and returns
 | |
| // true or false if there is anything in the queue when
 | |
| // you call the .write() method.
 | |
| const _opts = Symbol('opts')
 | |
| const _flushFlag = Symbol('flushFlag')
 | |
| const _finishFlushFlag = Symbol('finishFlushFlag')
 | |
| const _fullFlushFlag = Symbol('fullFlushFlag')
 | |
| const _handle = Symbol('handle')
 | |
| const _onError = Symbol('onError')
 | |
| const _sawError = Symbol('sawError')
 | |
| const _level = Symbol('level')
 | |
| const _strategy = Symbol('strategy')
 | |
| const _ended = Symbol('ended')
 | |
| const _defaultFullFlush = Symbol('_defaultFullFlush')
 | |
| 
 | |
| class ZlibBase extends Minipass {
 | |
|   constructor (opts, mode) {
 | |
|     if (!opts || typeof opts !== 'object')
 | |
|       throw new TypeError('invalid options for ZlibBase constructor')
 | |
| 
 | |
|     super(opts)
 | |
|     this[_sawError] = false
 | |
|     this[_ended] = false
 | |
|     this[_opts] = opts
 | |
| 
 | |
|     this[_flushFlag] = opts.flush
 | |
|     this[_finishFlushFlag] = opts.finishFlush
 | |
|     // this will throw if any options are invalid for the class selected
 | |
|     try {
 | |
|       this[_handle] = new realZlib[mode](opts)
 | |
|     } catch (er) {
 | |
|       // make sure that all errors get decorated properly
 | |
|       throw new ZlibError(er)
 | |
|     }
 | |
| 
 | |
|     this[_onError] = (err) => {
 | |
|       // no sense raising multiple errors, since we abort on the first one.
 | |
|       if (this[_sawError])
 | |
|         return
 | |
| 
 | |
|       this[_sawError] = true
 | |
| 
 | |
|       // there is no way to cleanly recover.
 | |
|       // continuing only obscures problems.
 | |
|       this.close()
 | |
|       this.emit('error', err)
 | |
|     }
 | |
| 
 | |
|     this[_handle].on('error', er => this[_onError](new ZlibError(er)))
 | |
|     this.once('end', () => this.close)
 | |
|   }
 | |
| 
 | |
|   close () {
 | |
|     if (this[_handle]) {
 | |
|       this[_handle].close()
 | |
|       this[_handle] = null
 | |
|       this.emit('close')
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   reset () {
 | |
|     if (!this[_sawError]) {
 | |
|       assert(this[_handle], 'zlib binding closed')
 | |
|       return this[_handle].reset()
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   flush (flushFlag) {
 | |
|     if (this.ended)
 | |
|       return
 | |
| 
 | |
|     if (typeof flushFlag !== 'number')
 | |
|       flushFlag = this[_fullFlushFlag]
 | |
|     this.write(Object.assign(Buffer.alloc(0), { [_flushFlag]: flushFlag }))
 | |
|   }
 | |
| 
 | |
|   end (chunk, encoding, cb) {
 | |
|     if (chunk)
 | |
|       this.write(chunk, encoding)
 | |
|     this.flush(this[_finishFlushFlag])
 | |
|     this[_ended] = true
 | |
|     return super.end(null, null, cb)
 | |
|   }
 | |
| 
 | |
|   get ended () {
 | |
|     return this[_ended]
 | |
|   }
 | |
| 
 | |
|   write (chunk, encoding, cb) {
 | |
|     // process the chunk using the sync process
 | |
|     // then super.write() all the outputted chunks
 | |
|     if (typeof encoding === 'function')
 | |
|       cb = encoding, encoding = 'utf8'
 | |
| 
 | |
|     if (typeof chunk === 'string')
 | |
|       chunk = Buffer.from(chunk, encoding)
 | |
| 
 | |
|     if (this[_sawError])
 | |
|       return
 | |
|     assert(this[_handle], 'zlib binding closed')
 | |
| 
 | |
|     // _processChunk tries to .close() the native handle after it's done, so we
 | |
|     // intercept that by temporarily making it a no-op.
 | |
|     const nativeHandle = this[_handle]._handle
 | |
|     const originalNativeClose = nativeHandle.close
 | |
|     nativeHandle.close = () => {}
 | |
|     const originalClose = this[_handle].close
 | |
|     this[_handle].close = () => {}
 | |
|     // It also calls `Buffer.concat()` at the end, which may be convenient
 | |
|     // for some, but which we are not interested in as it slows us down.
 | |
|     Buffer.concat = (args) => args
 | |
|     let result
 | |
|     try {
 | |
|       const flushFlag = typeof chunk[_flushFlag] === 'number'
 | |
|         ? chunk[_flushFlag] : this[_flushFlag]
 | |
|       result = this[_handle]._processChunk(chunk, flushFlag)
 | |
|       // if we don't throw, reset it back how it was
 | |
|       Buffer.concat = OriginalBufferConcat
 | |
|     } catch (err) {
 | |
|       // or if we do, put Buffer.concat() back before we emit error
 | |
|       // Error events call into user code, which may call Buffer.concat()
 | |
|       Buffer.concat = OriginalBufferConcat
 | |
|       this[_onError](new ZlibError(err))
 | |
|     } finally {
 | |
|       if (this[_handle]) {
 | |
|         // Core zlib resets `_handle` to null after attempting to close the
 | |
|         // native handle. Our no-op handler prevented actual closure, but we
 | |
|         // need to restore the `._handle` property.
 | |
|         this[_handle]._handle = nativeHandle
 | |
|         nativeHandle.close = originalNativeClose
 | |
|         this[_handle].close = originalClose
 | |
|         // `_processChunk()` adds an 'error' listener. If we don't remove it
 | |
|         // after each call, these handlers start piling up.
 | |
|         this[_handle].removeAllListeners('error')
 | |
|         // make sure OUR error listener is still attached tho
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (this[_handle])
 | |
|       this[_handle].on('error', er => this[_onError](new ZlibError(er)))
 | |
| 
 | |
|     let writeReturn
 | |
|     if (result) {
 | |
|       if (Array.isArray(result) && result.length > 0) {
 | |
|         // The first buffer is always `handle._outBuffer`, which would be
 | |
|         // re-used for later invocations; so, we always have to copy that one.
 | |
|         writeReturn = this[_superWrite](Buffer.from(result[0]))
 | |
|         for (let i = 1; i < result.length; i++) {
 | |
|           writeReturn = this[_superWrite](result[i])
 | |
|         }
 | |
|       } else {
 | |
|         writeReturn = this[_superWrite](Buffer.from(result))
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (cb)
 | |
|       cb()
 | |
|     return writeReturn
 | |
|   }
 | |
| 
 | |
|   [_superWrite] (data) {
 | |
|     return super.write(data)
 | |
|   }
 | |
| }
 | |
| 
 | |
| class Zlib extends ZlibBase {
 | |
|   constructor (opts, mode) {
 | |
|     opts = opts || {}
 | |
| 
 | |
|     opts.flush = opts.flush || constants.Z_NO_FLUSH
 | |
|     opts.finishFlush = opts.finishFlush || constants.Z_FINISH
 | |
|     super(opts, mode)
 | |
| 
 | |
|     this[_fullFlushFlag] = constants.Z_FULL_FLUSH
 | |
|     this[_level] = opts.level
 | |
|     this[_strategy] = opts.strategy
 | |
|   }
 | |
| 
 | |
|   params (level, strategy) {
 | |
|     if (this[_sawError])
 | |
|       return
 | |
| 
 | |
|     if (!this[_handle])
 | |
|       throw new Error('cannot switch params when binding is closed')
 | |
| 
 | |
|     // no way to test this without also not supporting params at all
 | |
|     /* istanbul ignore if */
 | |
|     if (!this[_handle].params)
 | |
|       throw new Error('not supported in this implementation')
 | |
| 
 | |
|     if (this[_level] !== level || this[_strategy] !== strategy) {
 | |
|       this.flush(constants.Z_SYNC_FLUSH)
 | |
|       assert(this[_handle], 'zlib binding closed')
 | |
|       // .params() calls .flush(), but the latter is always async in the
 | |
|       // core zlib. We override .flush() temporarily to intercept that and
 | |
|       // flush synchronously.
 | |
|       const origFlush = this[_handle].flush
 | |
|       this[_handle].flush = (flushFlag, cb) => {
 | |
|         this.flush(flushFlag)
 | |
|         cb()
 | |
|       }
 | |
|       try {
 | |
|         this[_handle].params(level, strategy)
 | |
|       } finally {
 | |
|         this[_handle].flush = origFlush
 | |
|       }
 | |
|       /* istanbul ignore else */
 | |
|       if (this[_handle]) {
 | |
|         this[_level] = level
 | |
|         this[_strategy] = strategy
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // minimal 2-byte header
 | |
| class Deflate extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'Deflate')
 | |
|   }
 | |
| }
 | |
| 
 | |
| class Inflate extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'Inflate')
 | |
|   }
 | |
| }
 | |
| 
 | |
| // gzip - bigger header, same deflate compression
 | |
| const _portable = Symbol('_portable')
 | |
| class Gzip extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'Gzip')
 | |
|     this[_portable] = opts && !!opts.portable
 | |
|   }
 | |
| 
 | |
|   [_superWrite] (data) {
 | |
|     if (!this[_portable])
 | |
|       return super[_superWrite](data)
 | |
| 
 | |
|     // we'll always get the header emitted in one first chunk
 | |
|     // overwrite the OS indicator byte with 0xFF
 | |
|     this[_portable] = false
 | |
|     data[9] = 255
 | |
|     return super[_superWrite](data)
 | |
|   }
 | |
| }
 | |
| 
 | |
| class Gunzip extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'Gunzip')
 | |
|   }
 | |
| }
 | |
| 
 | |
| // raw - no header
 | |
| class DeflateRaw extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'DeflateRaw')
 | |
|   }
 | |
| }
 | |
| 
 | |
| class InflateRaw extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'InflateRaw')
 | |
|   }
 | |
| }
 | |
| 
 | |
| // auto-detect header.
 | |
| class Unzip extends Zlib {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'Unzip')
 | |
|   }
 | |
| }
 | |
| 
 | |
| class Brotli extends ZlibBase {
 | |
|   constructor (opts, mode) {
 | |
|     opts = opts || {}
 | |
| 
 | |
|     opts.flush = opts.flush || constants.BROTLI_OPERATION_PROCESS
 | |
|     opts.finishFlush = opts.finishFlush || constants.BROTLI_OPERATION_FINISH
 | |
| 
 | |
|     super(opts, mode)
 | |
| 
 | |
|     this[_fullFlushFlag] = constants.BROTLI_OPERATION_FLUSH
 | |
|   }
 | |
| }
 | |
| 
 | |
| class BrotliCompress extends Brotli {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'BrotliCompress')
 | |
|   }
 | |
| }
 | |
| 
 | |
| class BrotliDecompress extends Brotli {
 | |
|   constructor (opts) {
 | |
|     super(opts, 'BrotliDecompress')
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.Deflate = Deflate
 | |
| exports.Inflate = Inflate
 | |
| exports.Gzip = Gzip
 | |
| exports.Gunzip = Gunzip
 | |
| exports.DeflateRaw = DeflateRaw
 | |
| exports.InflateRaw = InflateRaw
 | |
| exports.Unzip = Unzip
 | |
| /* istanbul ignore else */
 | |
| if (typeof realZlib.BrotliCompress === 'function') {
 | |
|   exports.BrotliCompress = BrotliCompress
 | |
|   exports.BrotliDecompress = BrotliDecompress
 | |
| } else {
 | |
|   exports.BrotliCompress = exports.BrotliDecompress = class {
 | |
|     constructor () {
 | |
|       throw new Error('Brotli is not supported in this version of Node.js')
 | |
|     }
 | |
|   }
 | |
| }
 |