zlib: support Uint8Array in convenience methods

Also support Uint8Array as a `dictionary` option.

PR-URL: https://github.com/nodejs/node/pull/12001
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
Timothy Gu 2017-03-22 19:45:03 -07:00 committed by Anna Henningsen
parent 2d039ffa29
commit 91383e47fd
No known key found for this signature in database
GPG Key ID: D8B9F5AEAE84E4CF
6 changed files with 176 additions and 87 deletions

View File

@ -278,6 +278,9 @@ Compression strategy.
<!-- YAML <!-- YAML
added: v0.11.1 added: v0.11.1
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `dictionary` option can be an Uint8Array now.
- version: v5.11.0 - version: v5.11.0
pr-url: https://github.com/nodejs/node/pull/6069 pr-url: https://github.com/nodejs/node/pull/6069
description: The `finishFlush` option is supported now. description: The `finishFlush` option is supported now.
@ -290,14 +293,15 @@ Each class takes an `options` object. All options are optional.
Note that some options are only relevant when compressing, and are Note that some options are only relevant when compressing, and are
ignored by the decompression classes. ignored by the decompression classes.
* `flush` (default: `zlib.constants.Z_NO_FLUSH`) * `flush` {integer} (default: `zlib.constants.Z_NO_FLUSH`)
* `finishFlush` (default: `zlib.constants.Z_FINISH`) * `finishFlush` {integer} (default: `zlib.constants.Z_FINISH`)
* `chunkSize` (default: 16*1024) * `chunkSize` {integer} (default: 16\*1024)
* `windowBits` * `windowBits` {integer}
* `level` (compression only) * `level` {integer} (compression only)
* `memLevel` (compression only) * `memLevel` {integer} (compression only)
* `strategy` (compression only) * `strategy` {integer} (compression only)
* `dictionary` (deflate/inflate only, empty dictionary by default) * `dictionary` {Buffer|Uint8Array} (deflate/inflate only, empty dictionary by
default)
See the description of `deflateInit2` and `inflateInit2` at See the description of `deflateInit2` and `inflateInit2` at
<http://zlib.net/manual.html#Advanced> for more information on these. <http://zlib.net/manual.html#Advanced> for more information on these.
@ -473,89 +477,159 @@ Returns a new [Unzip][] object with an [options][].
<!--type=misc--> <!--type=misc-->
All of these take a [Buffer][] or string as the first argument, an optional All of these take a [Buffer][], [Uint8Array][], or string as the first
second argument to supply options to the `zlib` classes and will call the argument, an optional second argument to supply options to the `zlib` classes
supplied callback with `callback(error, result)`. and will call the supplied callback with `callback(error, result)`.
Every method has a `*Sync` counterpart, which accept the same arguments, but Every method has a `*Sync` counterpart, which accept the same arguments, but
without a callback. without a callback.
### zlib.deflate(buf[, options], callback) ### zlib.deflate(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.deflateSync(buf[, options]) ### zlib.deflateSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Compress a [Buffer][] or string with [Deflate][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.deflateRaw(buf[, options], callback) Compress a chunk of data with [Deflate][].
### zlib.deflateRaw(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.deflateRawSync(buf[, options]) ### zlib.deflateRawSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Compress a [Buffer][] or string with [DeflateRaw][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.gunzip(buf[, options], callback) Compress a chunk of data with [DeflateRaw][].
### zlib.gunzip(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.gunzipSync(buf[, options]) ### zlib.gunzipSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Decompress a [Buffer][] or string with [Gunzip][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.gzip(buf[, options], callback) Decompress a chunk of data with [Gunzip][].
### zlib.gzip(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.gzipSync(buf[, options]) ### zlib.gzipSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Compress a [Buffer][] or string with [Gzip][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.inflate(buf[, options], callback) Compress a chunk of data with [Gzip][].
### zlib.inflate(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.inflateSync(buf[, options]) ### zlib.inflateSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Decompress a [Buffer][] or string with [Inflate][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.inflateRaw(buf[, options], callback) Decompress a chunk of data with [Inflate][].
### zlib.inflateRaw(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.inflateRawSync(buf[, options]) ### zlib.inflateRawSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Decompress a [Buffer][] or string with [InflateRaw][]. - `buffer` {Buffer|Uint8Array|string}
### zlib.unzip(buf[, options], callback) Decompress a chunk of data with [InflateRaw][].
### zlib.unzip(buffer[, options], callback)
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
### zlib.unzipSync(buf[, options]) ### zlib.unzipSync(buffer[, options])
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now.
--> -->
Decompress a [Buffer][] or string with [Unzip][]. - `buffer` {Buffer|Uint8Array|string}
Decompress a chunk of data with [Unzip][].
[`Accept-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 [`Accept-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
[`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 [`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
@ -571,3 +645,4 @@ Decompress a [Buffer][] or string with [Unzip][].
[Unzip]: #zlib_class_zlib_unzip [Unzip]: #zlib_class_zlib_unzip
[`.flush()`]: #zlib_zlib_flush_kind_callback [`.flush()`]: #zlib_zlib_flush_kind_callback
[Buffer]: buffer.html [Buffer]: buffer.html
[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

View File

@ -24,6 +24,7 @@
const Buffer = require('buffer').Buffer; const Buffer = require('buffer').Buffer;
const internalUtil = require('internal/util'); const internalUtil = require('internal/util');
const Transform = require('_stream_transform'); const Transform = require('_stream_transform');
const { isUint8Array } = process.binding('util');
const binding = process.binding('zlib'); const binding = process.binding('zlib');
const assert = require('assert').ok; const assert = require('assert').ok;
const kMaxLength = require('buffer').kMaxLength; const kMaxLength = require('buffer').kMaxLength;
@ -78,6 +79,13 @@ function isInvalidStrategy(strategy) {
} }
function zlibBuffer(engine, buffer, callback) { function zlibBuffer(engine, buffer, callback) {
// Streams do not support non-Buffer Uint8Arrays yet. Convert it to a
// Buffer without copying.
if (isUint8Array(buffer) &&
Object.getPrototypeOf(buffer) !== Buffer.prototype) {
buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
var buffers = []; var buffers = [];
var nread = 0; var nread = 0;
@ -121,8 +129,9 @@ function zlibBuffer(engine, buffer, callback) {
function zlibBufferSync(engine, buffer) { function zlibBufferSync(engine, buffer) {
if (typeof buffer === 'string') if (typeof buffer === 'string')
buffer = Buffer.from(buffer); buffer = Buffer.from(buffer);
if (!(buffer instanceof Buffer)) else if (!isUint8Array(buffer))
throw new TypeError('Not a string or buffer'); throw new TypeError('"buffer" argument must be a string, Buffer, or ' +
'Uint8Array');
var flushFlag = engine._finishFlushFlag; var flushFlag = engine._finishFlushFlag;
@ -205,9 +214,9 @@ class Zlib extends Transform {
throw new TypeError('Invalid strategy: ' + opts.strategy); throw new TypeError('Invalid strategy: ' + opts.strategy);
if (opts.dictionary) { if (opts.dictionary) {
if (!(opts.dictionary instanceof Buffer)) { if (!isUint8Array(opts.dictionary)) {
throw new TypeError( throw new TypeError(
'Invalid dictionary: it should be a Buffer instance'); 'Invalid dictionary: it should be a Buffer or an Uint8Array');
} }
} }
@ -302,7 +311,7 @@ class Zlib extends Transform {
var ending = ws.ending || ws.ended; var ending = ws.ending || ws.ended;
var last = ending && (!chunk || ws.length === chunk.length); var last = ending && (!chunk || ws.length === chunk.length);
if (chunk !== null && !(chunk instanceof Buffer)) if (chunk !== null && !isUint8Array(chunk))
return cb(new TypeError('invalid input')); return cb(new TypeError('invalid input'));
if (!this._handle) if (!this._handle)

View File

@ -22,59 +22,60 @@
'use strict'; 'use strict';
// test convenience methods with and without options supplied // test convenience methods with and without options supplied
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const zlib = require('zlib'); const zlib = require('zlib');
let hadRun = 0; const expectStr = 'blahblahblahblahblahblah';
const expectBuf = Buffer.from(expectStr);
const expect = 'blahblahblahblahblahblah'; const expectUint8Array = new Uint8Array(expectBuf);
const opts = { const opts = {
level: 9, level: 9,
chunkSize: 1024, chunkSize: 1024,
}; };
[ for (const method of [
['gzip', 'gunzip'], ['gzip', 'gunzip'],
['gzip', 'unzip'], ['gzip', 'unzip'],
['deflate', 'inflate'], ['deflate', 'inflate'],
['deflateRaw', 'inflateRaw'], ['deflateRaw', 'inflateRaw'],
].forEach(function(method) { ]) {
for (const [type, expect] of [
['string', expectStr],
['Buffer', expectBuf],
['Uint8Array', expectUint8Array]
]) {
zlib[method[0]](expect, opts, common.mustCall((err, result) => {
zlib[method[1]](result, opts, common.mustCall((err, result) => {
assert.strictEqual(result.toString(), expectStr,
`Should get original string after ${method[0]}/` +
`${method[1]} ${type} with options.`);
}));
}));
zlib[method[0]](expect, opts, function(err, result) { zlib[method[0]](expect, common.mustCall((err, result) => {
zlib[method[1]](result, opts, function(err, result) { zlib[method[1]](result, common.mustCall((err, result) => {
assert.strictEqual(result.toString(), expect, assert.strictEqual(result.toString(), expectStr,
'Should get original string after ' + `Should get original string after ${method[0]}/` +
method[0] + '/' + method[1] + ' with options.'); `${method[1]} ${type} without options.`);
hadRun++; }));
}); }));
});
zlib[method[0]](expect, function(err, result) { {
zlib[method[1]](result, function(err, result) { const compressed = zlib[method[0] + 'Sync'](expect, opts);
assert.strictEqual(result.toString(), expect, const decompressed = zlib[method[1] + 'Sync'](compressed, opts);
'Should get original string after ' + assert.strictEqual(decompressed.toString(), expectStr,
method[0] + '/' + method[1] + ' without options.'); `Should get original string after ${method[0]}Sync/` +
hadRun++; `${method[1]}Sync ${type} with options.`);
}); }
});
let result = zlib[method[0] + 'Sync'](expect, opts);
result = zlib[method[1] + 'Sync'](result, opts);
assert.strictEqual(result.toString(), expect,
'Should get original string after ' +
method[0] + '/' + method[1] + ' with options.');
hadRun++;
result = zlib[method[0] + 'Sync'](expect); {
result = zlib[method[1] + 'Sync'](result); const compressed = zlib[method[0] + 'Sync'](expect);
assert.strictEqual(result.toString(), expect, const decompressed = zlib[method[1] + 'Sync'](compressed);
'Should get original string after ' + assert.strictEqual(decompressed.toString(), expectStr,
method[0] + '/' + method[1] + ' without options.'); `Should get original string after ${method[0]}Sync/` +
hadRun++; `${method[1]}Sync ${type} without options.`);
}
}); }
}
process.on('exit', function() {
assert.strictEqual(hadRun, 16, 'expect 16 compressions');
});

View File

@ -107,5 +107,5 @@ assert.throws(
// Throws if opts.dictionary is not a Buffer // Throws if opts.dictionary is not a Buffer
assert.throws( assert.throws(
() => { new zlib.Deflate({dictionary: 'not a buffer'}); }, () => { new zlib.Deflate({dictionary: 'not a buffer'}); },
/^TypeError: Invalid dictionary: it should be a Buffer instance$/ /^TypeError: Invalid dictionary: it should be a Buffer or an Uint8Array$/
); );

View File

@ -41,6 +41,7 @@ const spdyDict = Buffer.from([
'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1', 'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
'.1statusversionurl\0' '.1statusversionurl\0'
].join('')); ].join(''));
const spdyDictUint8Array = new Uint8Array(spdyDict);
const input = [ const input = [
'HTTP/1.1 200 Ok', 'HTTP/1.1 200 Ok',
@ -49,7 +50,7 @@ const input = [
'' ''
].join('\r\n'); ].join('\r\n');
function basicDictionaryTest() { function basicDictionaryTest(spdyDict) {
let output = ''; let output = '';
const deflate = zlib.createDeflate({ dictionary: spdyDict }); const deflate = zlib.createDeflate({ dictionary: spdyDict });
const inflate = zlib.createInflate({ dictionary: spdyDict }); const inflate = zlib.createInflate({ dictionary: spdyDict });
@ -75,7 +76,7 @@ function basicDictionaryTest() {
deflate.end(); deflate.end();
} }
function deflateResetDictionaryTest() { function deflateResetDictionaryTest(spdyDict) {
let doneReset = false; let doneReset = false;
let output = ''; let output = '';
const deflate = zlib.createDeflate({ dictionary: spdyDict }); const deflate = zlib.createDeflate({ dictionary: spdyDict });
@ -108,7 +109,7 @@ function deflateResetDictionaryTest() {
}); });
} }
function rawDictionaryTest() { function rawDictionaryTest(spdyDict) {
let output = ''; let output = '';
const deflate = zlib.createDeflateRaw({ dictionary: spdyDict }); const deflate = zlib.createDeflateRaw({ dictionary: spdyDict });
const inflate = zlib.createInflateRaw({ dictionary: spdyDict }); const inflate = zlib.createInflateRaw({ dictionary: spdyDict });
@ -134,7 +135,7 @@ function rawDictionaryTest() {
deflate.end(); deflate.end();
} }
function deflateRawResetDictionaryTest() { function deflateRawResetDictionaryTest(spdyDict) {
let doneReset = false; let doneReset = false;
let output = ''; let output = '';
const deflate = zlib.createDeflateRaw({ dictionary: spdyDict }); const deflate = zlib.createDeflateRaw({ dictionary: spdyDict });
@ -167,7 +168,9 @@ function deflateRawResetDictionaryTest() {
}); });
} }
basicDictionaryTest(); for (const dict of [spdyDict, spdyDictUint8Array]) {
deflateResetDictionaryTest(); basicDictionaryTest(dict);
rawDictionaryTest(); deflateResetDictionaryTest(dict);
deflateRawResetDictionaryTest(); rawDictionaryTest(dict);
deflateRawResetDictionaryTest(dict);
}

View File

@ -7,7 +7,8 @@ require('../common');
const assert = require('assert'); const assert = require('assert');
const zlib = require('zlib'); const zlib = require('zlib');
const expected = /^TypeError: Not a string or buffer$/; const expected =
/^TypeError: "buffer" argument must be a string, Buffer, or Uint8Array$/;
assert.throws(() => { zlib.deflateSync(undefined); }, expected); assert.throws(() => { zlib.deflateSync(undefined); }, expected);
assert.throws(() => { zlib.deflateSync(null); }, expected); assert.throws(() => { zlib.deflateSync(null); }, expected);