Skip to content

Commit

Permalink
stream: convert existing buffer when calling .setEncoding
Browse files Browse the repository at this point in the history
Convert already-stored chunks when `.setEncoding()` is called
so that subsequent `data` events will receive decoded strings,
as they expect.

Fixes: #27932

PR-URL: #27936
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
addaleax authored and BethGriggs committed Jul 16, 2019
1 parent f34bb96 commit f3841c6
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 2 deletions.
17 changes: 15 additions & 2 deletions lib/_stream_readable.js
Expand Up @@ -321,9 +321,22 @@ Readable.prototype.isPaused = function() {
Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
// if setEncoding(null), decoder.encoding equals utf8
const decoder = new StringDecoder(enc);
this._readableState.decoder = decoder;
// If setEncoding(null), decoder.encoding equals utf8
this._readableState.encoding = this._readableState.decoder.encoding;

// Iterate over current buffer to convert already stored Buffers:
let p = this._readableState.buffer.head;
let content = '';
while (p !== null) {
content += decoder.write(p.data);
p = p.next;
}
this._readableState.buffer.clear();
if (content !== '')
this._readableState.buffer.push(content);
this._readableState.length = content.length;
return this;
};

Expand Down
60 changes: 60 additions & 0 deletions test/parallel/test-stream-readable-setEncoding-existing-buffers.js
@@ -0,0 +1,60 @@
'use strict';
require('../common');
const { Readable } = require('stream');
const assert = require('assert');

{
// Call .setEncoding() while there are bytes already in the buffer.
const r = new Readable({ read() {} });

r.push(Buffer.from('a'));
r.push(Buffer.from('b'));

r.setEncoding('utf8');
const chunks = [];
r.on('data', (chunk) => chunks.push(chunk));

process.nextTick(() => {
assert.deepStrictEqual(chunks, ['ab']);
});
}

{
// Call .setEncoding() while the buffer contains a complete,
// but chunked character.
const r = new Readable({ read() {} });

r.push(Buffer.from([0xf0]));
r.push(Buffer.from([0x9f]));
r.push(Buffer.from([0x8e]));
r.push(Buffer.from([0x89]));

r.setEncoding('utf8');
const chunks = [];
r.on('data', (chunk) => chunks.push(chunk));

process.nextTick(() => {
assert.deepStrictEqual(chunks, ['🎉']);
});
}

{
// Call .setEncoding() while the buffer contains an incomplete character,
// and finish the character later.
const r = new Readable({ read() {} });

r.push(Buffer.from([0xf0]));
r.push(Buffer.from([0x9f]));

r.setEncoding('utf8');

r.push(Buffer.from([0x8e]));
r.push(Buffer.from([0x89]));

const chunks = [];
r.on('data', (chunk) => chunks.push(chunk));

process.nextTick(() => {
assert.deepStrictEqual(chunks, ['🎉']);
});
}

0 comments on commit f3841c6

Please sign in to comment.