Skip to content

Commit

Permalink
string_decoder: fix regressions
Browse files Browse the repository at this point in the history
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: #18723
Refs: #18537
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
  • Loading branch information
apapirovski committed Feb 13, 2018
1 parent b6000d8 commit e782715
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 37 deletions.
88 changes: 51 additions & 37 deletions lib/string_decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
function StringDecoder(encoding) {
this.encoding = normalizeEncoding(encoding);
this[kNativeDecoder] = Buffer.alloc(kSize);
this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding];
}

/* Everything below this line is undocumented legacy stuff. */
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);
};

text(buf, offset) {
this[kNativeDecoder][kMissingBytes] = 0;
this[kNativeDecoder][kBufferedBytes] = 0;
return this.write(buf.slice(offset));
}
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;
};

get lastTotal() {
return this[kNativeDecoder][kBufferedBytes] + this.lastNeed;
}
/* 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));
};

get lastChar() {
return this[kNativeDecoder].subarray(kIncompleteCharactersStart,
kIncompleteCharactersEnd);
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;
10 changes: 10 additions & 0 deletions test/parallel/test-string-decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'), '¢');
Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit e782715

Please sign in to comment.