do not lose text if malformed escape sequence found

This commit is contained in:
Sergey Simonchik
2018-09-12 01:49:22 +03:00
parent 0a8d4fde57
commit dd8fcf9cb4
2 changed files with 59 additions and 25 deletions
@@ -54,44 +54,52 @@ public class AnsiEscapeDecoder {
public void escapeText(@NotNull String text, @NotNull Key outputType, @NotNull ColoredTextAcceptor textAcceptor) {
text = prependUnhandledText(text, outputType);
text = normalizeAsciiControlCharacters(text);
int pos = 0;
int pos = 0, findEscSeqFromIndex = 0;
List<Pair<String, Key>> chunks = null;
int unhandledSuffixLength = 0;
while (true) {
int escSeqBeginInd = findEscSeqBeginIndex(text, pos);
int escSeqBeginInd = findEscSeqBeginIndex(text, findEscSeqFromIndex);
if (escSeqBeginInd < 0) {
if (escSeqBeginInd < -1) {
unhandledSuffixLength = decodeUnhandledSuffixLength(escSeqBeginInd);
}
if (pos < text.length() - unhandledSuffixLength) {
chunks = processTextChunk(chunks, text.substring(pos, text.length() - unhandledSuffixLength), outputType, textAcceptor);
}
break;
}
if (pos < escSeqBeginInd) {
chunks = processTextChunk(chunks, text.substring(pos, escSeqBeginInd), outputType, textAcceptor);
}
int escSeqEndInd = findConsecutiveEscSequencesEndIndex(text, escSeqBeginInd);
if (escSeqEndInd < 0) {
if (escSeqEndInd < -1) {
if (escSeqEndInd == -1) {
// malformed escape sequence => add ESC[
findEscSeqFromIndex = escSeqBeginInd + 2;
}
else {
unhandledSuffixLength = decodeUnhandledSuffixLength(escSeqEndInd);
}
break;
}
if (text.charAt(escSeqEndInd) == 'm') {
String escSeq = text.substring(escSeqBeginInd, escSeqEndInd + 1);
// this is a simple fix for RUBY-8996:
// we replace several consecutive escape sequences with one which contains all these sequences
String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";");
ProcessOutputType resultType = myColoredOutputTypeRegistry.getOutputType(colorAttribute, outputType);
if (resultType.isStdout()) {
myCurrentStdoutOutputType = resultType;
}
else if (resultType.isStderr()) {
myCurrentStderrOutputType = resultType;
break;
}
}
pos = escSeqEndInd + 1;
else {
assert escSeqBeginInd <= escSeqEndInd;
if (pos < escSeqBeginInd) {
chunks = processTextChunk(chunks, text.substring(pos, escSeqBeginInd), outputType, textAcceptor);
}
pos = escSeqEndInd + 1;
findEscSeqFromIndex = pos;
if (text.charAt(escSeqEndInd) == 'm') {
String escSeq = text.substring(escSeqBeginInd, escSeqEndInd + 1);
// this is a simple fix for RUBY-8996:
// we replace several consecutive escape sequences with one which contains all these sequences
String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";");
ProcessOutputType resultType = myColoredOutputTypeRegistry.getOutputType(colorAttribute, outputType);
if (resultType.isStdout()) {
myCurrentStdoutOutputType = resultType;
}
else if (resultType.isStderr()) {
myCurrentStderrOutputType = resultType;
}
}
}
}
if (pos < text.length() - unhandledSuffixLength) {
chunks = processTextChunk(chunks, text.substring(pos, text.length() - unhandledSuffixLength), outputType, textAcceptor);
}
updateUnhandledSuffix(text, outputType, unhandledSuffixLength);
if (chunks != null && textAcceptor instanceof ColoredChunksAcceptor) {
@@ -190,7 +198,7 @@ public class AnsiEscapeDecoder {
* If the return value is -1, no string suffix should be kept => a malformed escape sequence has been encountered.
* If the return value is less than -1, no actual handing of the incomplete escape sequence should be performed,
* the string suffix length should be decoded with {@code #decodeUnhandledSuffixLength(the return value)} and the suffix
* should be preserved until the next output chunks is available.
* should be preserved until the next output chunk is available.
*/
private static int findConsecutiveEscSequencesEndIndex(@NotNull String text, int firstEscSeqBeginInd) {
int escSeqBeginInd = firstEscSeqBeginInd;
@@ -69,6 +69,32 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
);
}
public void testMalformedSequence() {
check(false, Collections.singletonList(new ColoredText("\u001B[32mGreen\u001B[\1World\n", ProcessOutputTypes.STDOUT)
.addExpected("Green\u001B[\1World\n", "\u001B[32m")
));
check(false, Collections.singletonList(new ColoredText("\u001B\n", ProcessOutputTypes.STDOUT)
.addExpected("\u001B\n", ProcessOutputTypes.STDOUT.toString())
));
check(false, Collections.singletonList(new ColoredText("\u001B[\n", ProcessOutputTypes.STDOUT)
.addExpected("\u001B[\n", ProcessOutputTypes.STDOUT.toString())
));
check(false, ContainerUtil.newArrayList(
new ColoredText("\u001B\nHello,", ProcessOutputTypes.STDOUT)
.addExpected("\u001B\nHello,", ProcessOutputTypes.STDOUT.toString()),
new ColoredText("\u001B[31mWorld", ProcessOutputTypes.STDOUT)
.addExpected("World", "\u001B[31m")
));
check(false, ContainerUtil.newArrayList(
new ColoredText("\u001BHello,", ProcessOutputTypes.STDOUT)
.addExpected("\u001BHello,", ProcessOutputTypes.STDOUT.toString())
));
check(false, ContainerUtil.newArrayList(
new ColoredText("\u001B[Hello,", ProcessOutputTypes.STDOUT)
.addExpected("ello,", ProcessOutputTypes.STDOUT.toString())
));
}
public void testIncompleteEscapeSequences() {
check(true, ContainerUtil.newArrayList(
new ColoredText("\u001B", ProcessOutputTypes.STDOUT),