Skip to content

Commit

Permalink
readline: improve robustness against prototype mutation
Browse files Browse the repository at this point in the history
PR-URL: #45614
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
aduh95 authored and danielleadams committed Jan 3, 2023
1 parent 979d837 commit ff03ed1
Showing 1 changed file with 34 additions and 22 deletions.
56 changes: 34 additions & 22 deletions lib/internal/readline/interface.js
Expand Up @@ -22,13 +22,10 @@ const {
NumberIsNaN,
ObjectSetPrototypeOf,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
RegExpPrototypeSymbolSplit,
StringPrototypeCodePointAt,
StringPrototypeEndsWith,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeTrim,
Symbol,
Expand Down Expand Up @@ -77,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 = /\r?\n|\r(?!\n)/;
const lineEnding = /\r?\n|\r(?!\n)/g;

const kLineObjectStream = Symbol('line object stream');
const kQuestionCancel = Symbol('kQuestionCancel');
Expand Down Expand Up @@ -590,31 +587,40 @@ class Interface extends InterfaceConstructor {
this[kSawReturnAt] &&
DateNow() - this[kSawReturnAt] <= this.crlfDelay
) {
string = RegExpPrototypeSymbolReplace(/^\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;

if (this[kLine_buffer]) {
string = this[kLine_buffer] + string;
this[kLine_buffer] = null;
}
if (newPartContainsEnding) {
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;

// Got one or more newlines; process into "line" events
const lines = StringPrototypeSplit(string, lineEnding);
const indexes = [0, newPartContainsEnding.index, lineEnding.lastIndex];
let nextMatch;
while ((nextMatch = RegExpPrototypeExec(lineEnding, string)) !== null) {
ArrayPrototypePush(indexes, nextMatch.index, lineEnding.lastIndex);
}
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;
if (this[kLine_buffer]) {
this[kLine_buffer] += string;
} else {
this[kLine_buffer] = string;
}
}
}

Expand Down Expand Up @@ -1322,12 +1328,18 @@ class Interface extends InterfaceConstructor {
// falls through
default:
if (typeof s === 'string' && s) {
const lines = RegExpPrototypeSymbolSplit(/\r\n|\n|\r/, s);
for (let i = 0, len = lines.length; i < len; i++) {
if (i > 0) {
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);
}
this[kInsertString](lines[i]);
if (lastIndex === s.length) this[kLine]();
} else {
this[kInsertString](s);
}
}
}
Expand Down

0 comments on commit ff03ed1

Please sign in to comment.