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
			| 
											3 years ago
										 | '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') | ||
|  |     } | ||
|  |   } | ||
|  | } |