From e782715d0a1c33f98212bdc064b4b4eea198e605 Mon Sep 17 00:00:00 2001 From: Anatoli Papirovski Date: Mon, 12 Feb 2018 01:21:19 -0500 Subject: [PATCH] string_decoder: fix regressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are libraries which invoke StringDecoder using .call and .inherits, which directly conflicts with making StringDecoder be a class which can only be invoked with the new keyword. Revert to declaring it as a function. StringDecoder#lastNeed was not defined, redefine it using the new interface and fix StringDecoder#lastTotal. PR-URL: https://github.com/nodejs/node/pull/18723 Refs: https://github.com/nodejs/node/pull/18537 Reviewed-By: Michaël Zasso Reviewed-By: Ruben Bridgewater Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Evan Lucas Reviewed-By: Luigi Pinca Reviewed-By: Tiancheng "Timothy" Gu --- lib/string_decoder.js | 96 ++++++++++++++++------------ test/parallel/test-string-decoder.js | 10 +++ 2 files changed, 65 insertions(+), 41 deletions(-) diff --git a/lib/string_decoder.js b/lib/string_decoder.js index d955a663307..04d31b2607c 100644 --- a/lib/string_decoder.js +++ b/lib/string_decoder.js @@ -56,47 +56,61 @@ for (var i = 0; i < encodings.length; ++i) // StringDecoder provides an interface for efficiently splitting a series of // buffers into a series of JS strings without breaking apart multi-byte // characters. -class StringDecoder { - constructor(encoding) { - this.encoding = normalizeEncoding(encoding); - this[kNativeDecoder] = Buffer.alloc(kSize); - this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding]; - } - - write(buf) { - if (typeof buf === 'string') - return buf; - if (!ArrayBuffer.isView(buf)) - throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buf', - ['Buffer', 'Uint8Array', 'ArrayBufferView']); - return decode(this[kNativeDecoder], buf); - } - - end(buf) { - let ret = ''; - if (buf !== undefined) - ret = this.write(buf); - if (this[kNativeDecoder][kBufferedBytes] > 0) - ret += flush(this[kNativeDecoder]); - return ret; - } - - /* Everything below this line is undocumented legacy stuff. */ - - text(buf, offset) { - this[kNativeDecoder][kMissingBytes] = 0; - this[kNativeDecoder][kBufferedBytes] = 0; - return this.write(buf.slice(offset)); - } - - get lastTotal() { - return this[kNativeDecoder][kBufferedBytes] + this.lastNeed; - } - - get lastChar() { - return this[kNativeDecoder].subarray(kIncompleteCharactersStart, - kIncompleteCharactersEnd); - } +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + this[kNativeDecoder] = Buffer.alloc(kSize); + this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding]; } +StringDecoder.prototype.write = function write(buf) { + if (typeof buf === 'string') + return buf; + if (!ArrayBuffer.isView(buf)) + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buf', + ['Buffer', 'Uint8Array', 'ArrayBufferView']); + return decode(this[kNativeDecoder], buf); +}; + +StringDecoder.prototype.end = function end(buf) { + let ret = ''; + if (buf !== undefined) + ret = this.write(buf); + if (this[kNativeDecoder][kBufferedBytes] > 0) + ret += flush(this[kNativeDecoder]); + return ret; +}; + +/* Everything below this line is undocumented legacy stuff. */ +StringDecoder.prototype.text = function text(buf, offset) { + this[kNativeDecoder][kMissingBytes] = 0; + this[kNativeDecoder][kBufferedBytes] = 0; + return this.write(buf.slice(offset)); +}; + +Object.defineProperties(StringDecoder.prototype, { + lastChar: { + configurable: true, + enumerable: true, + get() { + return this[kNativeDecoder].subarray(kIncompleteCharactersStart, + kIncompleteCharactersEnd); + } + }, + lastNeed: { + configurable: true, + enumerable: true, + get() { + return this[kNativeDecoder][kMissingBytes]; + } + }, + lastTotal: { + configurable: true, + enumerable: true, + get() { + return this[kNativeDecoder][kBufferedBytes] + + this[kNativeDecoder][kMissingBytes]; + } + } +}); + exports.StringDecoder = StringDecoder; diff --git a/test/parallel/test-string-decoder.js b/test/parallel/test-string-decoder.js index 21a0b6c3e38..0e7ea8ffdd5 100644 --- a/test/parallel/test-string-decoder.js +++ b/test/parallel/test-string-decoder.js @@ -29,6 +29,11 @@ const StringDecoder = require('string_decoder').StringDecoder; let decoder = new StringDecoder(); assert.strictEqual(decoder.encoding, 'utf8'); +// Should work without 'new' keyword +const decoder2 = {}; +StringDecoder.call(decoder2); +assert.strictEqual(decoder2.encoding, 'utf8'); + // UTF-8 test('utf-8', Buffer.from('$', 'utf-8'), '$'); test('utf-8', Buffer.from('¢', 'utf-8'), '¢'); @@ -84,6 +89,11 @@ test('utf16le', Buffer.from('3DD84DDC', 'hex'), '\ud83d\udc4d'); // thumbs up // Additional UTF-8 tests decoder = new StringDecoder('utf8'); assert.strictEqual(decoder.write(Buffer.from('E1', 'hex')), ''); + +// A quick test for lastNeed & lastTotal which are undocumented. +assert.strictEqual(decoder.lastNeed, 2); +assert.strictEqual(decoder.lastTotal, 3); + assert.strictEqual(decoder.end(), '\ufffd'); decoder = new StringDecoder('utf8');