Skip to content

Commit ab52a06

Browse files
Anna Henningsenrefack
Anna Henningsen
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. PR-URL: nodejs#24939 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
1 parent d79c469 commit ab52a06

File tree

1 file changed

+129
-112
lines changed

1 file changed

+129
-112
lines changed

lib/zlib.js

+129-112
Original file line numberDiff line numberDiff line change
@@ -207,21 +207,10 @@ function checkRangesOrGetDefault(number, name, lower, upper, def) {
207207
return number;
208208
}
209209

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

238227
flush = checkRangesOrGetDefault(
239228
opts.flush, 'options.flush',
240-
Z_NO_FLUSH, Z_BLOCK, Z_NO_FLUSH);
229+
Z_NO_FLUSH, Z_BLOCK, flush);
241230

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

285235
if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
286236
opts = _extend({}, opts);
@@ -289,40 +239,29 @@ function Zlib(opts, mode) {
289239
opts.writableObjectMode = false;
290240
}
291241
}
242+
292243
Transform.call(this, opts);
244+
this._hadError = false;
293245
this.bytesWritten = 0;
294-
this._handle = new binding.Zlib(mode);
246+
this._handle = handle;
247+
handle[owner_symbol] = this;
295248
// Used by processCallback() and zlibOnError()
296-
this._handle[owner_symbol] = this;
297-
this._handle.onerror = zlibOnError;
298-
this._hadError = false;
299-
this._writeState = new Uint32Array(2);
300-
301-
if (!this._handle.init(windowBits,
302-
level,
303-
memLevel,
304-
strategy,
305-
this._writeState,
306-
processCallback,
307-
dictionary)) {
308-
throw new ERR_ZLIB_INITIALIZATION_FAILED();
309-
}
310-
249+
handle.onerror = zlibOnError;
311250
this._outBuffer = Buffer.allocUnsafe(chunkSize);
312251
this._outOffset = 0;
313-
this._level = level;
314-
this._strategy = strategy;
252+
315253
this._chunkSize = chunkSize;
316254
this._defaultFlushFlag = flush;
317255
this._finishFlushFlag = finishFlush;
318256
this._nextFlush = -1;
319-
this._info = opts && opts.info;
257+
this._defaultFullFlushFlag = fullFlush;
320258
this.once('end', this.close);
259+
this._info = opts && opts.info;
321260
}
322-
Object.setPrototypeOf(Zlib.prototype, Transform.prototype);
323-
Object.setPrototypeOf(Zlib, Transform);
261+
Object.setPrototypeOf(ZlibBase.prototype, Transform.prototype);
262+
Object.setPrototypeOf(ZlibBase, Transform);
324263

325-
Object.defineProperty(Zlib.prototype, '_closed', {
264+
Object.defineProperty(ZlibBase.prototype, '_closed', {
326265
configurable: true,
327266
enumerable: true,
328267
get() {
@@ -334,7 +273,7 @@ Object.defineProperty(Zlib.prototype, '_closed', {
334273
// perspective, but it is inconsistent with all other streams exposed by Node.js
335274
// that have this concept, where it stands for the number of bytes read
336275
// *from* the stream (that is, net.Socket/tls.Socket & file system streams).
337-
Object.defineProperty(Zlib.prototype, 'bytesRead', {
276+
Object.defineProperty(ZlibBase.prototype, 'bytesRead', {
338277
configurable: true,
339278
enumerable: true,
340279
get: deprecate(function() {
@@ -347,41 +286,15 @@ Object.defineProperty(Zlib.prototype, 'bytesRead', {
347286
'This feature will be removed in the future.', 'DEP0108')
348287
});
349288

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

382295
// This is the _flush function called by the transform class,
383296
// internally, when the last chunk has been written.
384-
Zlib.prototype._flush = function _flush(callback) {
297+
ZlibBase.prototype._flush = function(callback) {
385298
this._transform(Buffer.alloc(0), '', callback);
386299
};
387300

@@ -402,12 +315,12 @@ function maxFlush(a, b) {
402315
}
403316

404317
const flushBuffer = Buffer.alloc(0);
405-
Zlib.prototype.flush = function flush(kind, callback) {
318+
ZlibBase.prototype.flush = function(kind, callback) {
406319
var ws = this._writableState;
407320

408321
if (typeof kind === 'function' || (kind === undefined && !callback)) {
409322
callback = kind;
410-
kind = Z_FULL_FLUSH;
323+
kind = this._defaultFullFlushFlag;
411324
}
412325

413326
if (ws.ended) {
@@ -426,17 +339,17 @@ Zlib.prototype.flush = function flush(kind, callback) {
426339
}
427340
};
428341

429-
Zlib.prototype.close = function close(callback) {
342+
ZlibBase.prototype.close = function(callback) {
430343
_close(this, callback);
431344
this.destroy();
432345
};
433346

434-
Zlib.prototype._destroy = function _destroy(err, callback) {
347+
ZlibBase.prototype._destroy = function(err, callback) {
435348
_close(this);
436349
callback(err);
437350
};
438351

439-
Zlib.prototype._transform = function _transform(chunk, encoding, cb) {
352+
ZlibBase.prototype._transform = function(chunk, encoding, cb) {
440353
var flushFlag = this._defaultFlushFlag;
441354
// We use a 'fake' zero-length chunk to carry information about flushes from
442355
// the public API to the actual stream implementation.
@@ -453,7 +366,7 @@ Zlib.prototype._transform = function _transform(chunk, encoding, cb) {
453366
processChunk(this, chunk, flushFlag, cb);
454367
};
455368

456-
Zlib.prototype._processChunk = function _processChunk(chunk, flushFlag, cb) {
369+
ZlibBase.prototype._processChunk = function(chunk, flushFlag, cb) {
457370
// _processChunk() is left for backwards compatibility
458371
if (typeof cb === 'function')
459372
processChunk(this, chunk, flushFlag, cb);
@@ -643,6 +556,110 @@ function _close(engine, callback) {
643556
engine._handle = null;
644557
}
645558

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

0 commit comments

Comments
 (0)