Skip to content

Commit e782715

Browse files
committed
string_decoder: fix regressions
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>
1 parent b6000d8 commit e782715

File tree

2 files changed

+61
-37
lines changed

2 files changed

+61
-37
lines changed

lib/string_decoder.js

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -56,47 +56,61 @@ for (var i = 0; i < encodings.length; ++i)
5656
// StringDecoder provides an interface for efficiently splitting a series of
5757
// buffers into a series of JS strings without breaking apart multi-byte
5858
// characters.
59-
class StringDecoder {
60-
constructor(encoding) {
61-
this.encoding = normalizeEncoding(encoding);
62-
this[kNativeDecoder] = Buffer.alloc(kSize);
63-
this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding];
64-
}
65-
66-
write(buf) {
67-
if (typeof buf === 'string')
68-
return buf;
69-
if (!ArrayBuffer.isView(buf))
70-
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buf',
71-
['Buffer', 'Uint8Array', 'ArrayBufferView']);
72-
return decode(this[kNativeDecoder], buf);
73-
}
74-
75-
end(buf) {
76-
let ret = '';
77-
if (buf !== undefined)
78-
ret = this.write(buf);
79-
if (this[kNativeDecoder][kBufferedBytes] > 0)
80-
ret += flush(this[kNativeDecoder]);
81-
return ret;
82-
}
59+
function StringDecoder(encoding) {
60+
this.encoding = normalizeEncoding(encoding);
61+
this[kNativeDecoder] = Buffer.alloc(kSize);
62+
this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding];
63+
}
8364

84-
/* Everything below this line is undocumented legacy stuff. */
65+
StringDecoder.prototype.write = function write(buf) {
66+
if (typeof buf === 'string')
67+
return buf;
68+
if (!ArrayBuffer.isView(buf))
69+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buf',
70+
['Buffer', 'Uint8Array', 'ArrayBufferView']);
71+
return decode(this[kNativeDecoder], buf);
72+
};
8573

86-
text(buf, offset) {
87-
this[kNativeDecoder][kMissingBytes] = 0;
88-
this[kNativeDecoder][kBufferedBytes] = 0;
89-
return this.write(buf.slice(offset));
90-
}
74+
StringDecoder.prototype.end = function end(buf) {
75+
let ret = '';
76+
if (buf !== undefined)
77+
ret = this.write(buf);
78+
if (this[kNativeDecoder][kBufferedBytes] > 0)
79+
ret += flush(this[kNativeDecoder]);
80+
return ret;
81+
};
9182

92-
get lastTotal() {
93-
return this[kNativeDecoder][kBufferedBytes] + this.lastNeed;
94-
}
83+
/* Everything below this line is undocumented legacy stuff. */
84+
StringDecoder.prototype.text = function text(buf, offset) {
85+
this[kNativeDecoder][kMissingBytes] = 0;
86+
this[kNativeDecoder][kBufferedBytes] = 0;
87+
return this.write(buf.slice(offset));
88+
};
9589

96-
get lastChar() {
97-
return this[kNativeDecoder].subarray(kIncompleteCharactersStart,
98-
kIncompleteCharactersEnd);
90+
Object.defineProperties(StringDecoder.prototype, {
91+
lastChar: {
92+
configurable: true,
93+
enumerable: true,
94+
get() {
95+
return this[kNativeDecoder].subarray(kIncompleteCharactersStart,
96+
kIncompleteCharactersEnd);
97+
}
98+
},
99+
lastNeed: {
100+
configurable: true,
101+
enumerable: true,
102+
get() {
103+
return this[kNativeDecoder][kMissingBytes];
104+
}
105+
},
106+
lastTotal: {
107+
configurable: true,
108+
enumerable: true,
109+
get() {
110+
return this[kNativeDecoder][kBufferedBytes] +
111+
this[kNativeDecoder][kMissingBytes];
112+
}
99113
}
100-
}
114+
});
101115

102116
exports.StringDecoder = StringDecoder;

test/parallel/test-string-decoder.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ const StringDecoder = require('string_decoder').StringDecoder;
2929
let decoder = new StringDecoder();
3030
assert.strictEqual(decoder.encoding, 'utf8');
3131

32+
// Should work without 'new' keyword
33+
const decoder2 = {};
34+
StringDecoder.call(decoder2);
35+
assert.strictEqual(decoder2.encoding, 'utf8');
36+
3237
// UTF-8
3338
test('utf-8', Buffer.from('$', 'utf-8'), '$');
3439
test('utf-8', Buffer.from('¢', 'utf-8'), '¢');
@@ -84,6 +89,11 @@ test('utf16le', Buffer.from('3DD84DDC', 'hex'), '\ud83d\udc4d'); // thumbs up
8489
// Additional UTF-8 tests
8590
decoder = new StringDecoder('utf8');
8691
assert.strictEqual(decoder.write(Buffer.from('E1', 'hex')), '');
92+
93+
// A quick test for lastNeed & lastTotal which are undocumented.
94+
assert.strictEqual(decoder.lastNeed, 2);
95+
assert.strictEqual(decoder.lastTotal, 3);
96+
8797
assert.strictEqual(decoder.end(), '\ufffd');
8898

8999
decoder = new StringDecoder('utf8');

0 commit comments

Comments
 (0)