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.
348 lines
9.2 KiB
348 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') |
|
} |
|
} |
|
}
|
|
|