Skip to content

Commit 2ac14ee

Browse files
addaleaxMylesBorins
authored andcommitted
zlib: Make the finish flush flag configurable
Up to now, `Z_FINISH` was always the flushing flag that was used for the last chunk of input data. This patch makes this choice configurable so that advanced users can perform e.g. decompression of partial data using `Z_SYNC_FLUSH`, if that suits their needs. Add tests to make sure that an error is thrown upon encountering invalid `flush` or `finishFlush` flags. Fixes: #5761 PR-URL: #6069 Reviewed-By: James M Snell <[email protected]>
1 parent 3a5dd02 commit 2ac14ee

File tree

4 files changed

+91
-12
lines changed

4 files changed

+91
-12
lines changed

doc/api/zlib.markdown

+26
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,31 @@ http.createServer((request, response) => {
109109
}).listen(1337);
110110
```
111111

112+
By default, the zlib methods with throw an error when decompressing
113+
truncated data. However, if it is known that the data is incomplete, or
114+
the desire is to inspect only the beginning of a compressed file, it is
115+
possible to suppress the default error handling by changing the flushing
116+
method that is used to compressed the last chunk of input data:
117+
118+
```js
119+
// This is a truncated version of the buffer from the above examples
120+
const buffer = new Buffer('eJzT0yMA', 'base64');
121+
122+
zlib.unzip(buffer, { finishFlush: zlib.Z_SYNC_FLUSH }, (err, buffer) => {
123+
if (!err) {
124+
console.log(buffer.toString());
125+
} else {
126+
// handle error
127+
}
128+
});
129+
```
130+
131+
This will not change the behavior in other error-throwing situations, e.g.
132+
when the input data has an invalid format. Using this method, it will not be
133+
possible to determine whether the input ended prematurely or lacks the
134+
integrity checks, making it necessary to manually check that the
135+
decompressed result is valid.
136+
112137
## Memory Usage Tuning
113138

114139
<!--type=misc-->
@@ -231,6 +256,7 @@ Note that some options are only relevant when compressing, and are
231256
ignored by the decompression classes.
232257

233258
* flush (default: `zlib.Z_NO_FLUSH`)
259+
* finishFlush (default: `zlib.Z_FINISH`)
234260
* chunkSize (default: 16*1024)
235261
* windowBits
236262
* level (compression only)

lib/zlib.js

+20-12
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ function zlibBufferSync(engine, buffer) {
234234
if (!(buffer instanceof Buffer))
235235
throw new TypeError('Not a string or buffer');
236236

237-
var flushFlag = binding.Z_FINISH;
237+
var flushFlag = engine._finishFlushFlag;
238238

239239
return engine._processChunk(buffer, flushFlag);
240240
}
@@ -282,6 +282,14 @@ function Unzip(opts) {
282282
Zlib.call(this, opts, binding.UNZIP);
283283
}
284284

285+
function isValidFlushFlag(flag) {
286+
return flag === binding.Z_NO_FLUSH ||
287+
flag === binding.Z_PARTIAL_FLUSH ||
288+
flag === binding.Z_SYNC_FLUSH ||
289+
flag === binding.Z_FULL_FLUSH ||
290+
flag === binding.Z_FINISH ||
291+
flag === binding.Z_BLOCK;
292+
}
285293

286294
// the Zlib class they all inherit from
287295
// This thing manages the queue of requests, and returns
@@ -294,17 +302,16 @@ function Zlib(opts, mode) {
294302

295303
Transform.call(this, opts);
296304

297-
if (opts.flush) {
298-
if (opts.flush !== binding.Z_NO_FLUSH &&
299-
opts.flush !== binding.Z_PARTIAL_FLUSH &&
300-
opts.flush !== binding.Z_SYNC_FLUSH &&
301-
opts.flush !== binding.Z_FULL_FLUSH &&
302-
opts.flush !== binding.Z_FINISH &&
303-
opts.flush !== binding.Z_BLOCK) {
304-
throw new Error('Invalid flush flag: ' + opts.flush);
305-
}
305+
if (opts.flush && !isValidFlushFlag(opts.flush)) {
306+
throw new Error('Invalid flush flag: ' + opts.flush);
306307
}
308+
if (opts.finishFlush && !isValidFlushFlag(opts.finishFlush)) {
309+
throw new Error('Invalid flush flag: ' + opts.finishFlush);
310+
}
311+
307312
this._flushFlag = opts.flush || binding.Z_NO_FLUSH;
313+
this._finishFlushFlag = typeof opts.finishFlush !== 'undefined' ?
314+
opts.finishFlush : binding.Z_FINISH;
308315

309316
if (opts.chunkSize) {
310317
if (opts.chunkSize < exports.Z_MIN_CHUNK ||
@@ -483,12 +490,13 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
483490
if (this._closed)
484491
return cb(new Error('zlib binding closed'));
485492

486-
// If it's the last chunk, or a final flush, we use the Z_FINISH flush flag.
493+
// If it's the last chunk, or a final flush, we use the Z_FINISH flush flag
494+
// (or whatever flag was provided using opts.finishFlush).
487495
// If it's explicitly flushing at some other time, then we use
488496
// Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
489497
// goodness.
490498
if (last)
491-
flushFlag = binding.Z_FINISH;
499+
flushFlag = this._finishFlushFlag;
492500
else {
493501
flushFlag = this._flushFlag;
494502
// once we've flushed the last of the queue, stop flushing and
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const zlib = require('zlib');
5+
6+
assert.doesNotThrow(() => {
7+
zlib.createGzip({ flush: zlib.Z_SYNC_FLUSH });
8+
});
9+
10+
assert.throws(() => {
11+
zlib.createGzip({ flush: 'foobar' });
12+
}, /Invalid flush flag: foobar/);
13+
14+
assert.throws(() => {
15+
zlib.createGzip({ flush: 10000 });
16+
}, /Invalid flush flag: 10000/);
17+
18+
assert.doesNotThrow(() => {
19+
zlib.createGzip({ finishFlush: zlib.Z_SYNC_FLUSH });
20+
});
21+
22+
assert.throws(() => {
23+
zlib.createGzip({ finishFlush: 'foobar' });
24+
}, /Invalid flush flag: foobar/);
25+
26+
assert.throws(() => {
27+
zlib.createGzip({ finishFlush: 10000 });
28+
}, /Invalid flush flag: 10000/);

test/parallel/test-zlib-truncated.js

+17
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,22 @@ const inputString = 'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli'
4646
zlib[methods.decomp](truncated, function(err, result) {
4747
assert(/unexpected end of file/.test(err.message));
4848
});
49+
50+
const syncFlushOpt = { finishFlush: zlib.Z_SYNC_FLUSH };
51+
52+
// sync truncated input test, finishFlush = Z_SYNC_FLUSH
53+
assert.doesNotThrow(function() {
54+
const result = zlib[methods.decompSync](truncated, syncFlushOpt)
55+
.toString();
56+
assert.equal(result, inputString.substr(0, result.length));
57+
});
58+
59+
// async truncated input test, finishFlush = Z_SYNC_FLUSH
60+
zlib[methods.decomp](truncated, syncFlushOpt, function(err, decompressed) {
61+
assert.ifError(err);
62+
63+
const result = decompressed.toString();
64+
assert.equal(result, inputString.substr(0, result.length));
65+
});
4966
});
5067
});

0 commit comments

Comments
 (0)