Skip to content

Commit f77bffe

Browse files
committed
fix: support multi-byte characters in WebSocket handler
This commit updates the way the WebSocketHandler processes string data. Prior to this commit, it relied on string lengths, which does not properly account for multi-byte characters. This commit changes the logic to account for string encodings that contain multi-byte characters. Fixes: #1772
1 parent 2c1c5ff commit f77bffe

File tree

2 files changed

+54
-23
lines changed

2 files changed

+54
-23
lines changed

src/web-socket-handler.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,11 @@ export class WebSocketHandler implements WebSocketInterface {
5151

5252
public static handleStandardInput(
5353
ws: WebSocket.WebSocket,
54-
stdin: stream.Readable | any,
54+
stdin: stream.Readable,
5555
streamNum: number = 0,
5656
): boolean {
5757
stdin.on('data', (data) => {
58-
const buff = Buffer.alloc(data.length + 1);
59-
buff.writeInt8(streamNum, 0);
60-
if (data instanceof Buffer) {
61-
data.copy(buff, 1);
62-
} else {
63-
buff.write(data, 1);
64-
}
65-
ws.send(buff);
58+
ws.send(copyChunkForWebSocket(streamNum, data, stdin.readableEncoding));
6659
});
6760

6861
stdin.on('end', () => {
@@ -78,16 +71,9 @@ export class WebSocketHandler implements WebSocketInterface {
7871
createWS: () => Promise<WebSocket.WebSocket>,
7972
streamNum: number = 0,
8073
retryCount: number = 3,
74+
encoding?: BufferEncoding | null,
8175
): Promise<WebSocket.WebSocket | null> {
82-
const buff = Buffer.alloc(data.length + 1);
83-
84-
buff.writeInt8(streamNum, 0);
85-
if (data instanceof Buffer) {
86-
data.copy(buff, 1);
87-
} else {
88-
buff.write(data, 1);
89-
}
90-
76+
const buff = copyChunkForWebSocket(streamNum, data, encoding);
9177
let i = 0;
9278
for (; i < retryCount; ++i) {
9379
if (ws !== null && ws.readyState === WebSocket.OPEN) {
@@ -109,7 +95,7 @@ export class WebSocketHandler implements WebSocketInterface {
10995

11096
public static restartableHandleStandardInput(
11197
createWS: () => Promise<WebSocket.WebSocket>,
112-
stdin: stream.Readable | any,
98+
stdin: stream.Readable,
11399
streamNum: number = 0,
114100
retryCount: number = 3,
115101
): () => WebSocket.WebSocket | null {
@@ -122,7 +108,14 @@ export class WebSocketHandler implements WebSocketInterface {
122108

123109
stdin.on('data', (data) => {
124110
queue = queue.then(async () => {
125-
ws = await WebSocketHandler.processData(data, ws, createWS, streamNum, retryCount);
111+
ws = await WebSocketHandler.processData(
112+
data,
113+
ws,
114+
createWS,
115+
streamNum,
116+
retryCount,
117+
stdin.readableEncoding,
118+
);
126119
});
127120
});
128121

@@ -201,3 +194,24 @@ export class WebSocketHandler implements WebSocketInterface {
201194
});
202195
}
203196
}
197+
198+
function copyChunkForWebSocket(
199+
streamNum: number,
200+
chunk: string | Buffer,
201+
encoding?: BufferEncoding | null,
202+
): Buffer {
203+
let buff: Buffer;
204+
205+
if (chunk instanceof Buffer) {
206+
buff = Buffer.alloc(chunk.length + 1);
207+
chunk.copy(buff, 1);
208+
} else {
209+
encoding ??= 'utf-8';
210+
const size = Buffer.byteLength(chunk, encoding);
211+
buff = Buffer.alloc(size + 1);
212+
buff.write(chunk, 1, size, encoding);
213+
}
214+
215+
buff.writeInt8(streamNum, 0);
216+
return buff;
217+
}

src/web-socket-handler_test.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Readable } from 'node:stream';
12
import { promisify } from 'util';
23
import { expect } from 'chai';
34
import WebSocket = require('isomorphic-ws');
@@ -303,14 +304,30 @@ describe('WebSocket', () => {
303304
expect(datum).to.equal(fill);
304305
}
305306
});
307+
it('handles multi-byte characters', () => {
308+
return new Promise((resolve) => {
309+
const stream = new Readable({ read() {} });
310+
const mockWs = {
311+
close() {},
312+
send(data) {
313+
expect(data).to.deep.equal(Buffer.from([0x0f, 0xe2, 0x98, 0x83]));
314+
resolve(undefined);
315+
},
316+
} as WebSocket.WebSocket;
317+
318+
stream.setEncoding('utf8');
319+
stream.push('☃');
320+
WebSocketHandler.handleStandardInput(mockWs, stream, 0x0f);
321+
});
322+
});
306323
});
307324

308325
describe('Restartable Handle Standard Input', () => {
309326
it('should throw on negative retry', () => {
310327
const p = new Promise<WebSocket.WebSocket>(() => {});
311-
expect(() => WebSocketHandler.restartableHandleStandardInput(() => p, null, 0, -1)).to.throw(
312-
"retryCount can't be lower than 0.",
313-
);
328+
expect(() =>
329+
WebSocketHandler.restartableHandleStandardInput(() => p, new Readable({ read() {} }), 0, -1),
330+
).to.throw("retryCount can't be lower than 0.");
314331
});
315332

316333
it('should retry n times', () => {

0 commit comments

Comments
 (0)