Skip to content

Commit dd8d1da

Browse files
addaleaxMylesBorins
authored andcommitted
zlib: split JS code as prep for non-zlib-backed streams
Split the `Zlib` class into `ZlibBase` and `Zlib` classes, to facilitate introduction of similar streams with minor implementation differences. Backport-PR-URL: #25228 PR-URL: #24939 Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 281b52d commit dd8d1da

File tree

1 file changed

+128
-111
lines changed

1 file changed

+128
-111
lines changed

lib/zlib.js

+128-111
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,10 @@ function checkRangesOrGetDefault(number, name, lower, upper, def) {
205205
return number;
206206
}
207207

208-
// the Zlib class they all inherit from
209-
// This thing manages the queue of requests, and returns
210-
// true or false if there is anything in the queue when
211-
// you call the .write() method.
212-
function Zlib(opts, mode) {
208+
// The base class for all Zlib-style streams.
209+
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
213210
var chunkSize = Z_DEFAULT_CHUNK;
214-
var flush = Z_NO_FLUSH;
215-
var finishFlush = Z_FINISH;
216-
var windowBits = Z_DEFAULT_WINDOWBITS;
217-
var level = Z_DEFAULT_COMPRESSION;
218-
var memLevel = Z_DEFAULT_MEMLEVEL;
219-
var strategy = Z_DEFAULT_STRATEGY;
220-
var dictionary;
221-
222-
// The Zlib class is not exported to user land, the mode should only be
211+
// The ZlibBase class is not exported to user land, the mode should only be
223212
// passed in by us.
224213
assert(typeof mode === 'number');
225214
assert(mode >= DEFLATE && mode <= UNZIP);
@@ -235,50 +224,11 @@ function Zlib(opts, mode) {
235224

236225
flush = checkRangesOrGetDefault(
237226
opts.flush, 'options.flush',
238-
Z_NO_FLUSH, Z_BLOCK, Z_NO_FLUSH);
227+
Z_NO_FLUSH, Z_BLOCK, flush);
239228

240229
finishFlush = checkRangesOrGetDefault(
241230
opts.finishFlush, 'options.finishFlush',
242-
Z_NO_FLUSH, Z_BLOCK, Z_FINISH);
243-
244-
// windowBits is special. On the compression side, 0 is an invalid value.
245-
// But on the decompression side, a value of 0 for windowBits tells zlib
246-
// to use the window size in the zlib header of the compressed stream.
247-
if ((opts.windowBits == null || opts.windowBits === 0) &&
248-
(mode === INFLATE ||
249-
mode === GUNZIP ||
250-
mode === UNZIP)) {
251-
windowBits = 0;
252-
} else {
253-
windowBits = checkRangesOrGetDefault(
254-
opts.windowBits, 'options.windowBits',
255-
Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS);
256-
}
257-
258-
level = checkRangesOrGetDefault(
259-
opts.level, 'options.level',
260-
Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION);
261-
262-
memLevel = checkRangesOrGetDefault(
263-
opts.memLevel, 'options.memLevel',
264-
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL);
265-
266-
strategy = checkRangesOrGetDefault(
267-
opts.strategy, 'options.strategy',
268-
Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY);
269-
270-
dictionary = opts.dictionary;
271-
if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
272-
if (isAnyArrayBuffer(dictionary)) {
273-
dictionary = Buffer.from(dictionary);
274-
} else {
275-
throw new ERR_INVALID_ARG_TYPE(
276-
'options.dictionary',
277-
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
278-
dictionary
279-
);
280-
}
281-
}
231+
Z_NO_FLUSH, Z_BLOCK, finishFlush);
282232

283233
if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
284234
opts = _extend({}, opts);
@@ -287,39 +237,29 @@ function Zlib(opts, mode) {
287237
opts.writableObjectMode = false;
288238
}
289239
}
240+
290241
Transform.call(this, opts);
242+
this._hadError = false;
291243
this.bytesWritten = 0;
292-
this._handle = new binding.Zlib(mode);
244+
this._handle = handle;
245+
handle[owner_symbol] = this;
293246
// Used by processCallback() and zlibOnError()
294-
this._handle[owner_symbol] = this;
295-
this._handle.onerror = zlibOnError;
296-
this._hadError = false;
297-
this._writeState = new Uint32Array(2);
298-
299-
if (!this._handle.init(windowBits,
300-
level,
301-
memLevel,
302-
strategy,
303-
this._writeState,
304-
processCallback,
305-
dictionary)) {
306-
throw new ERR_ZLIB_INITIALIZATION_FAILED();
307-
}
308-
247+
handle.onerror = zlibOnError;
309248
this._outBuffer = Buffer.allocUnsafe(chunkSize);
310249
this._outOffset = 0;
311-
this._level = level;
312-
this._strategy = strategy;
250+
313251
this._chunkSize = chunkSize;
314252
this._defaultFlushFlag = flush;
315253
this._finishFlushFlag = finishFlush;
316254
this._nextFlush = -1;
317-
this._info = opts && opts.info;
255+
this._defaultFullFlushFlag = fullFlush;
318256
this.once('end', this.close);
257+
this._info = opts && opts.info;
319258
}
320-
inherits(Zlib, Transform);
321259

322-
Object.defineProperty(Zlib.prototype, '_closed', {
260+
inherits(ZlibBase, Transform);
261+
262+
Object.defineProperty(ZlibBase.prototype, '_closed', {
323263
configurable: true,
324264
enumerable: true,
325265
get() {
@@ -331,7 +271,7 @@ Object.defineProperty(Zlib.prototype, '_closed', {
331271
// perspective, but it is inconsistent with all other streams exposed by Node.js
332272
// that have this concept, where it stands for the number of bytes read
333273
// *from* the stream (that is, net.Socket/tls.Socket & file system streams).
334-
Object.defineProperty(Zlib.prototype, 'bytesRead', {
274+
Object.defineProperty(ZlibBase.prototype, 'bytesRead', {
335275
configurable: true,
336276
enumerable: true,
337277
get() {
@@ -342,41 +282,15 @@ Object.defineProperty(Zlib.prototype, 'bytesRead', {
342282
}
343283
});
344284

345-
// This callback is used by `.params()` to wait until a full flush happened
346-
// before adjusting the parameters. In particular, the call to the native
347-
// `params()` function should not happen while a write is currently in progress
348-
// on the threadpool.
349-
function paramsAfterFlushCallback(level, strategy, callback) {
350-
assert(this._handle, 'zlib binding closed');
351-
this._handle.params(level, strategy);
352-
if (!this._hadError) {
353-
this._level = level;
354-
this._strategy = strategy;
355-
if (callback) callback();
356-
}
357-
}
358-
359-
Zlib.prototype.params = function params(level, strategy, callback) {
360-
checkRangesOrGetDefault(level, 'level', Z_MIN_LEVEL, Z_MAX_LEVEL);
361-
checkRangesOrGetDefault(strategy, 'strategy', Z_DEFAULT_STRATEGY, Z_FIXED);
362-
363-
if (this._level !== level || this._strategy !== strategy) {
364-
this.flush(Z_SYNC_FLUSH,
365-
paramsAfterFlushCallback.bind(this, level, strategy, callback));
366-
} else {
367-
process.nextTick(callback);
368-
}
369-
};
370-
371-
Zlib.prototype.reset = function reset() {
285+
ZlibBase.prototype.reset = function() {
372286
if (!this._handle)
373287
assert(false, 'zlib binding closed');
374288
return this._handle.reset();
375289
};
376290

377291
// This is the _flush function called by the transform class,
378292
// internally, when the last chunk has been written.
379-
Zlib.prototype._flush = function _flush(callback) {
293+
ZlibBase.prototype._flush = function(callback) {
380294
this._transform(Buffer.alloc(0), '', callback);
381295
};
382296

@@ -397,12 +311,12 @@ function maxFlush(a, b) {
397311
}
398312

399313
const flushBuffer = Buffer.alloc(0);
400-
Zlib.prototype.flush = function flush(kind, callback) {
314+
ZlibBase.prototype.flush = function(kind, callback) {
401315
var ws = this._writableState;
402316

403317
if (typeof kind === 'function' || (kind === undefined && !callback)) {
404318
callback = kind;
405-
kind = Z_FULL_FLUSH;
319+
kind = this._defaultFullFlushFlag;
406320
}
407321

408322
if (ws.ended) {
@@ -421,17 +335,17 @@ Zlib.prototype.flush = function flush(kind, callback) {
421335
}
422336
};
423337

424-
Zlib.prototype.close = function close(callback) {
338+
ZlibBase.prototype.close = function(callback) {
425339
_close(this, callback);
426340
this.destroy();
427341
};
428342

429-
Zlib.prototype._destroy = function _destroy(err, callback) {
343+
ZlibBase.prototype._destroy = function(err, callback) {
430344
_close(this);
431345
callback(err);
432346
};
433347

434-
Zlib.prototype._transform = function _transform(chunk, encoding, cb) {
348+
ZlibBase.prototype._transform = function(chunk, encoding, cb) {
435349
var flushFlag = this._defaultFlushFlag;
436350
// We use a 'fake' zero-length chunk to carry information about flushes from
437351
// the public API to the actual stream implementation.
@@ -448,7 +362,7 @@ Zlib.prototype._transform = function _transform(chunk, encoding, cb) {
448362
processChunk(this, chunk, flushFlag, cb);
449363
};
450364

451-
Zlib.prototype._processChunk = function _processChunk(chunk, flushFlag, cb) {
365+
ZlibBase.prototype._processChunk = function(chunk, flushFlag, cb) {
452366
// _processChunk() is left for backwards compatibility
453367
if (typeof cb === 'function')
454368
processChunk(this, chunk, flushFlag, cb);
@@ -638,6 +552,109 @@ function _close(engine, callback) {
638552
engine._handle = null;
639553
}
640554

555+
const zlibDefaultOpts = {
556+
flush: Z_NO_FLUSH,
557+
finishFlush: Z_FINISH,
558+
fullFlush: Z_FULL_FLUSH
559+
};
560+
// Base class for all streams actually backed by zlib and using zlib-specific
561+
// parameters.
562+
function Zlib(opts, mode) {
563+
var windowBits = Z_DEFAULT_WINDOWBITS;
564+
var level = Z_DEFAULT_COMPRESSION;
565+
var memLevel = Z_DEFAULT_MEMLEVEL;
566+
var strategy = Z_DEFAULT_STRATEGY;
567+
var dictionary;
568+
569+
if (opts) {
570+
// windowBits is special. On the compression side, 0 is an invalid value.
571+
// But on the decompression side, a value of 0 for windowBits tells zlib
572+
// to use the window size in the zlib header of the compressed stream.
573+
if ((opts.windowBits == null || opts.windowBits === 0) &&
574+
(mode === INFLATE ||
575+
mode === GUNZIP ||
576+
mode === UNZIP)) {
577+
windowBits = 0;
578+
} else {
579+
windowBits = checkRangesOrGetDefault(
580+
opts.windowBits, 'options.windowBits',
581+
Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS);
582+
}
583+
584+
level = checkRangesOrGetDefault(
585+
opts.level, 'options.level',
586+
Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION);
587+
588+
memLevel = checkRangesOrGetDefault(
589+
opts.memLevel, 'options.memLevel',
590+
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL);
591+
592+
strategy = checkRangesOrGetDefault(
593+
opts.strategy, 'options.strategy',
594+
Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY);
595+
596+
dictionary = opts.dictionary;
597+
if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
598+
if (isAnyArrayBuffer(dictionary)) {
599+
dictionary = Buffer.from(dictionary);
600+
} else {
601+
throw new ERR_INVALID_ARG_TYPE(
602+
'options.dictionary',
603+
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
604+
dictionary
605+
);
606+
}
607+
}
608+
}
609+
610+
const handle = new binding.Zlib(mode);
611+
// Ideally, we could let ZlibBase() set up _writeState. I haven't been able
612+
// to come up with a good solution that doesn't break our internal API,
613+
// and with it all supported npm versions at the time of writing.
614+
this._writeState = new Uint32Array(2);
615+
if (!handle.init(windowBits,
616+
level,
617+
memLevel,
618+
strategy,
619+
this._writeState,
620+
processCallback,
621+
dictionary)) {
622+
throw new ERR_ZLIB_INITIALIZATION_FAILED();
623+
}
624+
625+
ZlibBase.call(this, opts, mode, handle, zlibDefaultOpts);
626+
627+
this._level = level;
628+
this._strategy = strategy;
629+
}
630+
inherits(Zlib, ZlibBase);
631+
632+
// This callback is used by `.params()` to wait until a full flush happened
633+
// before adjusting the parameters. In particular, the call to the native
634+
// `params()` function should not happen while a write is currently in progress
635+
// on the threadpool.
636+
function paramsAfterFlushCallback(level, strategy, callback) {
637+
assert(this._handle, 'zlib binding closed');
638+
this._handle.params(level, strategy);
639+
if (!this._hadError) {
640+
this._level = level;
641+
this._strategy = strategy;
642+
if (callback) callback();
643+
}
644+
}
645+
646+
Zlib.prototype.params = function params(level, strategy, callback) {
647+
checkRangesOrGetDefault(level, 'level', Z_MIN_LEVEL, Z_MAX_LEVEL);
648+
checkRangesOrGetDefault(strategy, 'strategy', Z_DEFAULT_STRATEGY, Z_FIXED);
649+
650+
if (this._level !== level || this._strategy !== strategy) {
651+
this.flush(Z_SYNC_FLUSH,
652+
paramsAfterFlushCallback.bind(this, level, strategy, callback));
653+
} else {
654+
process.nextTick(callback);
655+
}
656+
};
657+
641658
// generic zlib
642659
// minimal 2-byte header
643660
function Deflate(opts) {

0 commit comments

Comments
 (0)