zlib: support all ArrayBufferView types

PR-URL: https://github.com/nodejs/node/pull/12223
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Timothy Gu 2017-04-03 18:15:13 -07:00
parent a8f460f12d
commit 2ced07ccaf
6 changed files with 89 additions and 43 deletions

View File

@ -300,7 +300,7 @@ ignored by the decompression classes.
* `level` {integer} (compression only) * `level` {integer} (compression only)
* `memLevel` {integer} (compression only) * `memLevel` {integer} (compression only)
* `strategy` {integer} (compression only) * `strategy` {integer} (compression only)
* `dictionary` {Buffer|Uint8Array} (deflate/inflate only, empty dictionary by * `dictionary` {Buffer|TypedArray|DataView} (deflate/inflate only, empty dictionary by
default) default)
See the description of `deflateInit2` and `inflateInit2` at See the description of `deflateInit2` and `inflateInit2` at
@ -477,9 +477,9 @@ Returns a new [Unzip][] object with an [options][].
<!--type=misc--> <!--type=misc-->
All of these take a [Buffer][], [Uint8Array][], or string as the first All of these take a [`Buffer`][], [`TypedArray`][], [`DataView`][], or string as
argument, an optional second argument to supply options to the `zlib` classes the first argument, an optional second argument to supply options to the `zlib`
and will call the supplied callback with `callback(error, result)`. classes 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.
@ -488,6 +488,9 @@ without a callback.
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -496,12 +499,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Compress a chunk of data with [Deflate][]. Compress a chunk of data with [Deflate][].
@ -509,6 +515,9 @@ Compress a chunk of data with [Deflate][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -517,12 +526,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Compress a chunk of data with [DeflateRaw][]. Compress a chunk of data with [DeflateRaw][].
@ -530,6 +542,9 @@ Compress a chunk of data with [DeflateRaw][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -538,12 +553,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Decompress a chunk of data with [Gunzip][]. Decompress a chunk of data with [Gunzip][].
@ -551,6 +569,9 @@ Decompress a chunk of data with [Gunzip][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -559,12 +580,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Compress a chunk of data with [Gzip][]. Compress a chunk of data with [Gzip][].
@ -572,6 +596,9 @@ Compress a chunk of data with [Gzip][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -580,12 +607,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Decompress a chunk of data with [Inflate][]. Decompress a chunk of data with [Inflate][].
@ -593,6 +623,9 @@ Decompress a chunk of data with [Inflate][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -601,12 +634,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Decompress a chunk of data with [InflateRaw][]. Decompress a chunk of data with [InflateRaw][].
@ -614,6 +650,9 @@ Decompress a chunk of data with [InflateRaw][].
<!-- YAML <!-- YAML
added: v0.6.0 added: v0.6.0
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
@ -622,12 +661,15 @@ changes:
<!-- YAML <!-- YAML
added: v0.11.12 added: v0.11.12
changes: changes:
- version: REPLACEME
pr-url: REPLACEME
description: The `buffer` parameter can be any TypedArray or DataView now.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12001 pr-url: https://github.com/nodejs/node/pull/12001
description: The `buffer` parameter can be an Uint8Array now. description: The `buffer` parameter can be an Uint8Array now.
--> -->
- `buffer` {Buffer|Uint8Array|string} - `buffer` {Buffer|TypedArray|DataView|string}
Decompress a chunk of data with [Unzip][]. Decompress a chunk of data with [Unzip][].
@ -644,5 +686,6 @@ Decompress a chunk of data with [Unzip][].
[InflateRaw]: #zlib_class_zlib_inflateraw [InflateRaw]: #zlib_class_zlib_inflateraw
[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#buffer_class_buffer
[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array [`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
[`TypedArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray

View File

@ -24,7 +24,6 @@
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;
@ -79,9 +78,9 @@ 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 // Streams do not support non-Buffer ArrayBufferViews yet. Convert it to a
// Buffer without copying. // Buffer without copying.
if (isUint8Array(buffer) && if (ArrayBuffer.isView(buffer) &&
Object.getPrototypeOf(buffer) !== Buffer.prototype) { Object.getPrototypeOf(buffer) !== Buffer.prototype) {
buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength); buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
} }
@ -99,7 +98,7 @@ function zlibBuffer(engine, buffer, callback) {
var chunk; var chunk;
while (null !== (chunk = engine.read())) { while (null !== (chunk = engine.read())) {
buffers.push(chunk); buffers.push(chunk);
nread += chunk.length; nread += chunk.byteLength;
} }
engine.once('readable', flow); engine.once('readable', flow);
} }
@ -129,9 +128,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);
else if (!isUint8Array(buffer)) else if (!ArrayBuffer.isView(buffer))
throw new TypeError('"buffer" argument must be a string, Buffer, or ' + throw new TypeError('"buffer" argument must be a string, Buffer, ' +
'Uint8Array'); 'TypedArray, or DataView');
var flushFlag = engine._finishFlushFlag; var flushFlag = engine._finishFlushFlag;
@ -214,9 +213,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 (!isUint8Array(opts.dictionary)) { if (!ArrayBuffer.isView(opts.dictionary)) {
throw new TypeError( throw new TypeError(
'Invalid dictionary: it should be a Buffer or an Uint8Array'); 'Invalid dictionary: it should be a Buffer, TypedArray, or DataView');
} }
} }
@ -309,9 +308,9 @@ class Zlib extends Transform {
var flushFlag; var flushFlag;
var ws = this._writableState; var ws = this._writableState;
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.byteLength);
if (chunk !== null && !isUint8Array(chunk)) if (chunk !== null && !ArrayBuffer.isView(chunk))
return cb(new TypeError('invalid input')); return cb(new TypeError('invalid input'));
if (!this._handle) if (!this._handle)
@ -328,7 +327,7 @@ class Zlib extends Transform {
flushFlag = this._flushFlag; flushFlag = this._flushFlag;
// once we've flushed the last of the queue, stop flushing and // once we've flushed the last of the queue, stop flushing and
// go back to the normal behavior. // go back to the normal behavior.
if (chunk.length >= ws.length) { if (chunk.byteLength >= ws.length) {
this._flushFlag = this._opts.flush || constants.Z_NO_FLUSH; this._flushFlag = this._opts.flush || constants.Z_NO_FLUSH;
} }
} }
@ -337,7 +336,7 @@ class Zlib extends Transform {
} }
_processChunk(chunk, flushFlag, cb) { _processChunk(chunk, flushFlag, cb) {
var availInBefore = chunk && chunk.length; var availInBefore = chunk && chunk.byteLength;
var availOutBefore = this._chunkSize - this._offset; var availOutBefore = this._chunkSize - this._offset;
var inOff = 0; var inOff = 0;
@ -417,7 +416,7 @@ class Zlib extends Transform {
self.push(out); self.push(out);
} else { } else {
buffers.push(out); buffers.push(out);
nread += out.length; nread += out.byteLength;
} }
} }

View File

@ -26,24 +26,28 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const zlib = require('zlib'); const zlib = require('zlib');
const expectStr = 'blahblahblahblahblahblah'; // Must be a multiple of 4 characters in total to test all ArrayBufferView
// types.
const expectStr = 'blah'.repeat(8);
const expectBuf = Buffer.from(expectStr); const expectBuf = Buffer.from(expectStr);
const expectUint8Array = new Uint8Array(expectBuf);
const opts = { const opts = {
level: 9, level: 9,
chunkSize: 1024, chunkSize: 1024,
}; };
for (const method of [ for (const [type, expect] of [
['gzip', 'gunzip'], ['string', expectStr],
['gzip', 'unzip'], ['Buffer', expectBuf],
['deflate', 'inflate'], ...common.getArrayBufferViews(expectBuf).map((obj) =>
['deflateRaw', 'inflateRaw'], [obj[Symbol.toStringTag], obj]
)
]) { ]) {
for (const [type, expect] of [ for (const method of [
['string', expectStr], ['gzip', 'gunzip'],
['Buffer', expectBuf], ['gzip', 'unzip'],
['Uint8Array', expectUint8Array] ['deflate', 'inflate'],
['deflateRaw', 'inflateRaw'],
]) { ]) {
zlib[method[0]](expect, opts, common.mustCall((err, result) => { zlib[method[0]](expect, opts, common.mustCall((err, result) => {
zlib[method[1]](result, opts, common.mustCall((err, result) => { zlib[method[1]](result, opts, common.mustCall((err, result) => {

View File

@ -107,5 +107,6 @@ 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 or an Uint8Array$/ new RegExp('^TypeError: Invalid dictionary: it should be a Buffer, ' +
'TypedArray, or DataView$')
); );

View File

@ -41,7 +41,6 @@ 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',
@ -168,7 +167,7 @@ function deflateRawResetDictionaryTest(spdyDict) {
}); });
} }
for (const dict of [spdyDict, spdyDictUint8Array]) { for (const dict of [spdyDict, ...common.getArrayBufferViews(spdyDict)]) {
basicDictionaryTest(dict); basicDictionaryTest(dict);
deflateResetDictionaryTest(dict); deflateResetDictionaryTest(dict);
rawDictionaryTest(dict); rawDictionaryTest(dict);

View File

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