From b1cd9a567b2a3e3382b03146205219990e39451e Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 24 Nov 2022 13:01:12 +0100 Subject: [PATCH 1/4] readline: improve robustness against prototype mutation --- lib/internal/readline/interface.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 5de6a8fe03da13..7bce950f0cbbb0 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -28,12 +28,12 @@ const { StringPrototypeEndsWith, StringPrototypeRepeat, StringPrototypeSlice, - StringPrototypeSplit, StringPrototypeStartsWith, StringPrototypeTrim, Symbol, SymbolAsyncIterator, SafeStringIterator, + hardenRegExp, } = primordials; const { codes } = require('internal/errors'); @@ -75,7 +75,7 @@ const kHistorySize = 30; const kMaxUndoRedoStackSize = 2048; const kMincrlfDelay = 100; // \r\n, \n, or \r followed by something other than \n -const lineEnding = /\r?\n|\r(?!\n)/; +const lineEnding = hardenRegExp(/\r?\n|\r(?!\n)/); const kLineObjectStream = Symbol('line object stream'); const kQuestionCancel = Symbol('kQuestionCancel'); @@ -589,7 +589,7 @@ class Interface extends InterfaceConstructor { this[kSawReturnAt] && DateNow() - this[kSawReturnAt] <= this.crlfDelay ) { - string = RegExpPrototypeSymbolReplace(/^\n/, string, ''); + string = RegExpPrototypeSymbolReplace(hardenRegExp(/^\n/), string, ''); this[kSawReturnAt] = 0; } @@ -606,7 +606,7 @@ class Interface extends InterfaceConstructor { 0; // Got one or more newlines; process into "line" events - const lines = StringPrototypeSplit(string, lineEnding); + const lines = RegExpPrototypeSymbolSplit(lineEnding, string); // Either '' or (conceivably) the unfinished portion of the next line string = ArrayPrototypePop(lines); this[kLine_buffer] = string; @@ -1321,7 +1321,7 @@ class Interface extends InterfaceConstructor { // falls through default: if (typeof s === 'string' && s) { - const lines = RegExpPrototypeSymbolSplit(/\r\n|\n|\r/, s); + const lines = RegExpPrototypeSymbolSplit(hardenRegExp(/\r\n|\n|\r/), s); for (let i = 0, len = lines.length; i < len; i++) { if (i > 0) { this[kLine](); From ab8fd8a80388e1efe29d4cf8c1ecf06cf22fe5c8 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 25 Nov 2022 10:33:57 +0100 Subject: [PATCH 2/4] fixup --- lib/internal/readline/interface.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 7bce950f0cbbb0..c71d29fd418455 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -22,7 +22,6 @@ const { NumberIsNaN, ObjectSetPrototypeOf, RegExpPrototypeExec, - RegExpPrototypeSymbolReplace, RegExpPrototypeSymbolSplit, StringPrototypeCodePointAt, StringPrototypeEndsWith, @@ -75,7 +74,7 @@ const kHistorySize = 30; const kMaxUndoRedoStackSize = 2048; const kMincrlfDelay = 100; // \r\n, \n, or \r followed by something other than \n -const lineEnding = hardenRegExp(/\r?\n|\r(?!\n)/); +const lineEnding = /\r?\n|\r(?!\n)/g; const kLineObjectStream = Symbol('line object stream'); const kQuestionCancel = Symbol('kQuestionCancel'); @@ -589,28 +588,33 @@ class Interface extends InterfaceConstructor { this[kSawReturnAt] && DateNow() - this[kSawReturnAt] <= this.crlfDelay ) { - string = RegExpPrototypeSymbolReplace(hardenRegExp(/^\n/), string, ''); + if (StringPrototypeCodePointAt(string) === 10) string = StringPrototypeSlice(string, 1); this[kSawReturnAt] = 0; } // Run test() on the new string chunk, not on the entire line buffer. - const newPartContainsEnding = RegExpPrototypeExec(lineEnding, string) !== null; + const newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); if (this[kLine_buffer]) { string = this[kLine_buffer] + string; this[kLine_buffer] = null; } - if (newPartContainsEnding) { + if (newPartContainsEnding !== null) { this[kSawReturnAt] = StringPrototypeEndsWith(string, '\r') ? DateNow() : 0; - // Got one or more newlines; process into "line" events - const lines = RegExpPrototypeSymbolSplit(lineEnding, string); + const indexes = [0, newPartContainsEnding.index, newPartContainsEnding.index + newPartContainsEnding[0].length]; + let nextMatch; + while ((nextMatch = RegExpPrototypeExec(lineEnding, string)) !== null) { + ArrayPrototypePush(indexes, nextMatch.index, nextMatch.index + nextMatch[0].length); + } + const lastIndex = indexes.length - 1; // Either '' or (conceivably) the unfinished portion of the next line - string = ArrayPrototypePop(lines); - this[kLine_buffer] = string; - for (let n = 0; n < lines.length; n++) this[kOnLine](lines[n]); + this[kLine_buffer] = StringPrototypeSlice(string, indexes[lastIndex]); + for (let i = 1; i < lastIndex; i += 2) { + this[kOnLine](StringPrototypeSlice(string, indexes[i - 1], indexes[i])); + } } else if (string) { // No newlines this time, save what we have for next time this[kLine_buffer] = string; From 5d62f5533296bb0a009574a09f4c3cec9bb11186 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 28 Nov 2022 20:37:53 +0100 Subject: [PATCH 3/4] fixup --- lib/internal/readline/interface.js | 39 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index c71d29fd418455..2d2964ad1b5333 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -22,7 +22,6 @@ const { NumberIsNaN, ObjectSetPrototypeOf, RegExpPrototypeExec, - RegExpPrototypeSymbolSplit, StringPrototypeCodePointAt, StringPrototypeEndsWith, StringPrototypeRepeat, @@ -32,7 +31,6 @@ const { Symbol, SymbolAsyncIterator, SafeStringIterator, - hardenRegExp, } = primordials; const { codes } = require('internal/errors'); @@ -593,21 +591,21 @@ class Interface extends InterfaceConstructor { } // Run test() on the new string chunk, not on the entire line buffer. - const newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); - - if (this[kLine_buffer]) { - string = this[kLine_buffer] + string; - this[kLine_buffer] = null; - } + let newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); if (newPartContainsEnding !== null) { + if (this[kLine_buffer]) { + string = this[kLine_buffer] + string; + this[kLine_buffer] = null; + newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); + } this[kSawReturnAt] = StringPrototypeEndsWith(string, '\r') ? DateNow() : 0; - const indexes = [0, newPartContainsEnding.index, newPartContainsEnding.index + newPartContainsEnding[0].length]; + const indexes = [0, newPartContainsEnding.index, lineEnding.lastIndex]; let nextMatch; while ((nextMatch = RegExpPrototypeExec(lineEnding, string)) !== null) { - ArrayPrototypePush(indexes, nextMatch.index, nextMatch.index + nextMatch[0].length); + ArrayPrototypePush(indexes, nextMatch.index, lineEnding.lastIndex); } const lastIndex = indexes.length - 1; // Either '' or (conceivably) the unfinished portion of the next line @@ -617,7 +615,11 @@ class Interface extends InterfaceConstructor { } } else if (string) { // No newlines this time, save what we have for next time - this[kLine_buffer] = string; + if (this[kLine_buffer]) { + this[kLine_buffer] += string; + } else { + this[kLine_buffer] = string; + } } } @@ -1325,12 +1327,15 @@ class Interface extends InterfaceConstructor { // falls through default: if (typeof s === 'string' && s) { - const lines = RegExpPrototypeSymbolSplit(hardenRegExp(/\r\n|\n|\r/), s); - for (let i = 0, len = lines.length; i < len; i++) { - if (i > 0) { - this[kLine](); - } - this[kInsertString](lines[i]); + let nextMatch = RegExpPrototypeExec(lineEnding, s); + if (nextMatch !== null) { + this[kInsertString](StringPrototypeSlice(s, 0, nextMatch.index)); + } + let { lastIndex } = lineEnding; + while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) { + this[kLine](); + this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index)); + ({ lastIndex } = lineEnding); } } } From cd1e34a3838e6f51f8553bbb80ec690d40b7a9e7 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 1 Dec 2022 23:36:16 +0100 Subject: [PATCH 4/4] fixup --- lib/internal/readline/interface.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 2d2964ad1b5333..8822073f971ef7 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -1330,12 +1330,15 @@ class Interface extends InterfaceConstructor { let nextMatch = RegExpPrototypeExec(lineEnding, s); if (nextMatch !== null) { this[kInsertString](StringPrototypeSlice(s, 0, nextMatch.index)); - } - let { lastIndex } = lineEnding; - while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) { - this[kLine](); - this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index)); - ({ lastIndex } = lineEnding); + let { lastIndex } = lineEnding; + while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) { + this[kLine](); + this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index)); + ({ lastIndex } = lineEnding); + } + if (lastIndex === s.length) this[kLine](); + } else { + this[kInsertString](s); } } }