Skip to content

Commit f5dba6a

Browse files
committed
Use safer pasted input chunking algorithm
Fixes microsoft#180257
1 parent d03d2fb commit f5dba6a

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

src/vs/platform/terminal/node/terminalProcess.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -437,17 +437,13 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
437437
}
438438
}
439439

440-
input(data: string, isBinary?: boolean): void {
440+
input(data: string, isBinary: boolean = false): void {
441441
if (this._store.isDisposed || !this._ptyProcess) {
442442
return;
443443
}
444-
for (let i = 0; i <= Math.floor(data.length / Constants.WriteMaxChunkSize); i++) {
445-
const obj = {
446-
isBinary: isBinary || false,
447-
data: data.substr(i * Constants.WriteMaxChunkSize, Constants.WriteMaxChunkSize)
448-
};
449-
this._writeQueue.push(obj);
450-
}
444+
this._writeQueue.push(...chunkInput(data).map(e => {
445+
return { isBinary, data: e };
446+
}));
451447
this._startWrite();
452448
}
453449

@@ -649,3 +645,30 @@ class DelayedResizer extends Disposable {
649645
this._register(toDisposable(() => clearTimeout(this._timeout)));
650646
}
651647
}
648+
649+
/**
650+
* Splits incoming pty data into chunks to try prevent data corruption that could occur when pasting
651+
* large amounts of data.
652+
*/
653+
export function chunkInput(data: string): string[] {
654+
const chunks: string[] = [];
655+
let nextChunkStartIndex = 0;
656+
for (let i = 0; i < data.length - 1; i++) {
657+
if (
658+
// If the max chunk size is reached
659+
i - nextChunkStartIndex + 1 >= Constants.WriteMaxChunkSize ||
660+
// If the next character is ESC, send the pending data to avoid splitting the escape
661+
// sequence.
662+
data[i + 1] === '\x1b'
663+
) {
664+
chunks.push(data.substring(nextChunkStartIndex, i + 1));
665+
nextChunkStartIndex = i + 1;
666+
i++;
667+
}
668+
}
669+
// Push final chunk
670+
if (nextChunkStartIndex !== data.length) {
671+
chunks.push(data.substring(nextChunkStartIndex));
672+
}
673+
return chunks;
674+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { deepStrictEqual } from 'assert';
7+
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
8+
import { chunkInput } from 'vs/platform/terminal/node/terminalProcess';
9+
10+
suite('platform - terminalProcess', () => {
11+
ensureNoDisposablesAreLeakedInTestSuite();
12+
suite('chunkInput', () => {
13+
test('single chunk', () => {
14+
deepStrictEqual(chunkInput('foo bar'), ['foo bar']);
15+
});
16+
test('multi chunk', () => {
17+
deepStrictEqual(chunkInput('foo'.repeat(50)), [
18+
'foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofo',
19+
'ofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoof',
20+
'oofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo'
21+
]);
22+
});
23+
test('small data with escapes', () => {
24+
deepStrictEqual(chunkInput('foo \x1b[30mbar'), [
25+
'foo ',
26+
'\x1b[30mbar'
27+
]);
28+
});
29+
test('large data with escapes', () => {
30+
deepStrictEqual(chunkInput('foofoofoofoo\x1b[30mbarbarbarbarbar\x1b[0m'.repeat(3)), [
31+
'foofoofoofoo',
32+
'\x1B[30mbarbarbarbarbar',
33+
'\x1B[0mfoofoofoofoo',
34+
'\x1B[30mbarbarbarbarbar',
35+
'\x1B[0mfoofoofoofoo',
36+
'\x1B[30mbarbarbarbarbar',
37+
'\x1B[0m'
38+
]);
39+
});
40+
});
41+
});

0 commit comments

Comments
 (0)