1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-02 17:01:14 -05:00

fix(core): introduce SafeRegExp to primordials (#17592)

This commit is contained in:
Kenta Moriuchi 2023-03-01 08:14:16 +09:00 committed by Yoshiya Hinosawa
parent 8a6fad7e0d
commit d13055e317
15 changed files with 171 additions and 60 deletions

View file

@ -2138,6 +2138,18 @@ Deno.test(async function inspectAggregateError() {
} }
}); });
Deno.test(function inspectWithPrototypePollution() {
const originalExec = RegExp.prototype.exec;
try {
RegExp.prototype.exec = () => {
throw Error();
};
Deno.inspect("foo");
} finally {
RegExp.prototype.exec = originalExec;
}
});
Deno.test(function inspectorMethods() { Deno.test(function inspectorMethods() {
console.timeStamp("test"); console.timeStamp("test");
console.profile("test"); console.profile("test");

View file

@ -325,6 +325,21 @@ Deno.test(function headersInitMultiple() {
]); ]);
}); });
Deno.test(function headerInitWithPrototypePollution() {
const originalExec = RegExp.prototype.exec;
try {
RegExp.prototype.exec = () => {
throw Error();
};
new Headers([
["X-Deno", "foo"],
["X-Deno", "bar"],
]);
} finally {
RegExp.prototype.exec = originalExec;
}
});
Deno.test(function headersAppendMultiple() { Deno.test(function headersAppendMultiple() {
const headers = new Headers([ const headers = new Headers([
["Set-Cookie", "foo=bar"], ["Set-Cookie", "foo=bar"],

View file

@ -43,3 +43,18 @@ Deno.test(function urlPatternFromInit() {
assert(pattern.test({ pathname: "/foo/x" })); assert(pattern.test({ pathname: "/foo/x" }));
}); });
Deno.test(function urlPatternWithPrototypePollution() {
const originalExec = RegExp.prototype.exec;
try {
RegExp.prototype.exec = () => {
throw Error();
};
const pattern = new URLPattern({
pathname: "/foo/:bar",
});
assert(pattern.test("https://deno.land/foo/x"));
} finally {
RegExp.prototype.exec = originalExec;
}
});

View file

@ -434,6 +434,15 @@
}, },
); );
primordials.SafeRegExp = makeSafe(
RegExp,
class SafeRegExp extends RegExp {
constructor(pattern, flags) {
super(pattern, flags);
}
},
);
primordials.SafeFinalizationRegistry = makeSafe( primordials.SafeFinalizationRegistry = makeSafe(
FinalizationRegistry, FinalizationRegistry,
class SafeFinalizationRegistry extends FinalizationRegistry { class SafeFinalizationRegistry extends FinalizationRegistry {

1
core/internal.d.ts vendored
View file

@ -78,6 +78,7 @@ declare namespace __bootstrap {
export const SafePromisePrototypeFinally: UncurryThis< export const SafePromisePrototypeFinally: UncurryThis<
Promise.prototype.finally Promise.prototype.finally
>; >;
export const SafeRegExp: typeof RegExp;
// safe iterators // safe iterators
export const SafeArrayIterator: new <T>(array: T[]) => IterableIterator<T>; export const SafeArrayIterator: new <T>(array: T[]) => IterableIterator<T>;

View file

@ -4,7 +4,7 @@
const primordials = globalThis.__bootstrap.primordials; const primordials = globalThis.__bootstrap.primordials;
const { const {
RegExp, SafeRegExp,
StringPrototypeReplace, StringPrototypeReplace,
ArrayPrototypeJoin, ArrayPrototypeJoin,
} = primordials; } = primordials;
@ -23,7 +23,7 @@ function code(open, close) {
return { return {
open: `\x1b[${open}m`, open: `\x1b[${open}m`,
close: `\x1b[${close}m`, close: `\x1b[${close}m`,
regexp: new RegExp(`\\x1b\\[${close}m`, "g"), regexp: new SafeRegExp(`\\x1b\\[${close}m`, "g"),
}; };
} }
@ -74,7 +74,7 @@ function magenta(str) {
} }
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js // https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp( const ANSI_PATTERN = new SafeRegExp(
ArrayPrototypeJoin([ ArrayPrototypeJoin([
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",

View file

@ -17,8 +17,9 @@ const {
BooleanPrototype, BooleanPrototype,
BooleanPrototypeToString, BooleanPrototypeToString,
ObjectKeys, ObjectKeys,
ObjectCreate,
ObjectAssign, ObjectAssign,
ObjectCreate,
ObjectFreeze,
ObjectIs, ObjectIs,
ObjectValues, ObjectValues,
ObjectFromEntries, ObjectFromEntries,
@ -49,13 +50,13 @@ const {
TypeError, TypeError,
NumberIsInteger, NumberIsInteger,
NumberParseInt, NumberParseInt,
RegExp,
RegExpPrototype, RegExpPrototype,
RegExpPrototypeTest, RegExpPrototypeTest,
RegExpPrototypeToString, RegExpPrototypeToString,
SafeArrayIterator, SafeArrayIterator,
SafeStringIterator, SafeStringIterator,
SafeSet, SafeSet,
SafeRegExp,
SetPrototype, SetPrototype,
SetPrototypeEntries, SetPrototypeEntries,
SetPrototypeGetSize, SetPrototypeGetSize,
@ -747,31 +748,34 @@ function quoteString(string) {
const quote = const quote =
ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ?? ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ??
QUOTES[0]; QUOTES[0];
const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); const escapePattern = new SafeRegExp(`(?=[${quote}\\\\])`, "g");
string = StringPrototypeReplace(string, escapePattern, "\\"); string = StringPrototypeReplace(string, escapePattern, "\\");
string = replaceEscapeSequences(string); string = replaceEscapeSequences(string);
return `${quote}${string}${quote}`; return `${quote}${string}${quote}`;
} }
const ESCAPE_PATTERN = new SafeRegExp(/([\b\f\n\r\t\v])/g);
const ESCAPE_MAP = ObjectFreeze({
"\b": "\\b",
"\f": "\\f",
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
"\v": "\\v",
});
// deno-lint-ignore no-control-regex
const ESCAPE_PATTERN2 = new SafeRegExp(/[\x00-\x1f\x7f-\x9f]/g);
// Replace escape sequences that can modify output. // Replace escape sequences that can modify output.
function replaceEscapeSequences(string) { function replaceEscapeSequences(string) {
const escapeMap = {
"\b": "\\b",
"\f": "\\f",
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
"\v": "\\v",
};
return StringPrototypeReplace( return StringPrototypeReplace(
StringPrototypeReplace( StringPrototypeReplace(
string, string,
/([\b\f\n\r\t\v])/g, ESCAPE_PATTERN,
(c) => escapeMap[c], (c) => ESCAPE_MAP[c],
), ),
// deno-lint-ignore no-control-regex new SafeRegExp(ESCAPE_PATTERN2),
/[\x00-\x1f\x7f-\x9f]/g,
(c) => (c) =>
"\\x" + "\\x" +
StringPrototypePadStart( StringPrototypePadStart(
@ -782,22 +786,33 @@ function replaceEscapeSequences(string) {
); );
} }
const QUOTE_STRING_PATTERN = new SafeRegExp(/^[a-zA-Z_][a-zA-Z_0-9]*$/);
// Surround a string with quotes when it is required (e.g the string not a valid identifier). // Surround a string with quotes when it is required (e.g the string not a valid identifier).
function maybeQuoteString(string) { function maybeQuoteString(string) {
if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_0-9]*$/, string)) { if (
RegExpPrototypeTest(QUOTE_STRING_PATTERN, string)
) {
return replaceEscapeSequences(string); return replaceEscapeSequences(string);
} }
return quoteString(string); return quoteString(string);
} }
const QUOTE_SYMBOL_REG = new SafeRegExp(/^[a-zA-Z_][a-zA-Z_.0-9]*$/);
// Surround a symbol's description in quotes when it is required (e.g the description has non printable characters). // Surround a symbol's description in quotes when it is required (e.g the description has non printable characters).
function maybeQuoteSymbol(symbol) { function maybeQuoteSymbol(symbol) {
if (symbol.description === undefined) { if (symbol.description === undefined) {
return SymbolPrototypeToString(symbol); return SymbolPrototypeToString(symbol);
} }
if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { if (
RegExpPrototypeTest(
QUOTE_SYMBOL_REG,
symbol.description,
)
) {
return SymbolPrototypeToString(symbol); return SymbolPrototypeToString(symbol);
} }
@ -980,6 +995,9 @@ function inspectRegExp(value, inspectOptions) {
return red(RegExpPrototypeToString(value)); // RegExps are red return red(RegExpPrototypeToString(value)); // RegExps are red
} }
const AGGREGATE_ERROR_HAS_AT_PATTERN = new SafeRegExp(/\s+at/);
const AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN = new SafeRegExp(/^(?!\s*$)/gm);
function inspectError(value, cyan) { function inspectError(value, cyan) {
const causes = [value]; const causes = [value];
@ -1012,7 +1030,7 @@ function inspectError(value, cyan) {
const stackLines = StringPrototypeSplit(value.stack, "\n"); const stackLines = StringPrototypeSplit(value.stack, "\n");
while (true) { while (true) {
const line = ArrayPrototypeShift(stackLines); const line = ArrayPrototypeShift(stackLines);
if (RegExpPrototypeTest(/\s+at/, line)) { if (RegExpPrototypeTest(AGGREGATE_ERROR_HAS_AT_PATTERN, line)) {
ArrayPrototypeUnshift(stackLines, line); ArrayPrototypeUnshift(stackLines, line);
break; break;
} else if (typeof line === "undefined") { } else if (typeof line === "undefined") {
@ -1028,7 +1046,7 @@ function inspectError(value, cyan) {
(error) => (error) =>
StringPrototypeReplace( StringPrototypeReplace(
inspectArgs([error]), inspectArgs([error]),
/^(?!\s*$)/gm, AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN,
StringPrototypeRepeat(" ", 4), StringPrototypeRepeat(" ", 4),
), ),
), ),
@ -1519,12 +1537,25 @@ const colorKeywords = new Map([
["rebeccapurple", "#663399"], ["rebeccapurple", "#663399"],
]); ]);
const HASH_PATTERN = new SafeRegExp(
/^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/,
);
const SMALL_HASH_PATTERN = new SafeRegExp(
/^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/,
);
const RGB_PATTERN = new SafeRegExp(
/^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/,
);
const HSL_PATTERN = new SafeRegExp(
/^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/,
);
function parseCssColor(colorString) { function parseCssColor(colorString) {
if (MapPrototypeHas(colorKeywords, colorString)) { if (MapPrototypeHas(colorKeywords, colorString)) {
colorString = MapPrototypeGet(colorKeywords, colorString); colorString = MapPrototypeGet(colorKeywords, colorString);
} }
// deno-fmt-ignore // deno-fmt-ignore
const hashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/); const hashMatch = StringPrototypeMatch(colorString, HASH_PATTERN);
if (hashMatch != null) { if (hashMatch != null) {
return [ return [
Number(`0x${hashMatch[1]}`), Number(`0x${hashMatch[1]}`),
@ -1533,7 +1564,7 @@ function parseCssColor(colorString) {
]; ];
} }
// deno-fmt-ignore // deno-fmt-ignore
const smallHashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/); const smallHashMatch = StringPrototypeMatch(colorString, SMALL_HASH_PATTERN);
if (smallHashMatch != null) { if (smallHashMatch != null) {
return [ return [
Number(`0x${smallHashMatch[1]}0`), Number(`0x${smallHashMatch[1]}0`),
@ -1542,7 +1573,7 @@ function parseCssColor(colorString) {
]; ];
} }
// deno-fmt-ignore // deno-fmt-ignore
const rgbMatch = StringPrototypeMatch(colorString, /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); const rgbMatch = StringPrototypeMatch(colorString, RGB_PATTERN);
if (rgbMatch != null) { if (rgbMatch != null) {
return [ return [
MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))),
@ -1551,7 +1582,7 @@ function parseCssColor(colorString) {
]; ];
} }
// deno-fmt-ignore // deno-fmt-ignore
const hslMatch = StringPrototypeMatch(colorString, /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); const hslMatch = StringPrototypeMatch(colorString, HSL_PATTERN);
if (hslMatch != null) { if (hslMatch != null) {
// https://www.rapidtables.com/convert/color/hsl-to-rgb.html // https://www.rapidtables.com/convert/color/hsl-to-rgb.html
let h = Number(hslMatch[1]) % 360; let h = Number(hslMatch[1]) % 360;
@ -1599,6 +1630,8 @@ function getDefaultCss() {
}; };
} }
const SPACE_PATTERN = new SafeRegExp(/\s+/g);
function parseCss(cssString) { function parseCss(cssString) {
const css = getDefaultCss(); const css = getDefaultCss();
@ -1665,7 +1698,7 @@ function parseCss(cssString) {
} }
} else if (key == "text-decoration-line") { } else if (key == "text-decoration-line") {
css.textDecorationLine = []; css.textDecorationLine = [];
const lineTypes = StringPrototypeSplit(value, /\s+/g); const lineTypes = StringPrototypeSplit(value, SPACE_PATTERN);
for (let i = 0; i < lineTypes.length; ++i) { for (let i = 0; i < lineTypes.length; ++i) {
const lineType = lineTypes[i]; const lineType = lineTypes[i];
if ( if (
@ -1685,7 +1718,7 @@ function parseCss(cssString) {
} else if (key == "text-decoration") { } else if (key == "text-decoration") {
css.textDecorationColor = null; css.textDecorationColor = null;
css.textDecorationLine = []; css.textDecorationLine = [];
const args = StringPrototypeSplit(value, /\s+/g); const args = StringPrototypeSplit(value, SPACE_PATTERN);
for (let i = 0; i < args.length; ++i) { for (let i = 0; i < args.length; ++i) {
const arg = args[i]; const arg = args[i];
const maybeColor = parseCssColor(arg); const maybeColor = parseCssColor(arg);

View file

@ -32,6 +32,7 @@ const {
ObjectEntries, ObjectEntries,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeArrayIterator, SafeArrayIterator,
SafeRegExp,
Symbol, Symbol,
SymbolFor, SymbolFor,
SymbolIterator, SymbolIterator,
@ -88,7 +89,7 @@ function fillHeaders(headers, object) {
// Regex matching illegal chars in a header value // Regex matching illegal chars in a header value
// deno-lint-ignore no-control-regex // deno-lint-ignore no-control-regex
const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; const ILLEGAL_VALUE_CHARS = new SafeRegExp(/[\x00\x0A\x0D]/);
/** /**
* https://fetch.spec.whatwg.org/#concept-headers-append * https://fetch.spec.whatwg.org/#concept-headers-append

View file

@ -26,7 +26,9 @@ const {
MapPrototypeGet, MapPrototypeGet,
MapPrototypeSet, MapPrototypeSet,
MathRandom, MathRandom,
ObjectFreeze,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
SafeRegExp,
Symbol, Symbol,
StringFromCharCode, StringFromCharCode,
StringPrototypeTrim, StringPrototypeTrim,
@ -277,19 +279,25 @@ webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
webidl.configurePrototype(FormData); webidl.configurePrototype(FormData);
const FormDataPrototype = FormData.prototype; const FormDataPrototype = FormData.prototype;
const escape = (str, isFilename) => { const ESCAPE_FILENAME_PATTERN = new SafeRegExp(/\r?\n|\r/g);
const escapeMap = { const ESCAPE_PATTERN = new SafeRegExp(/([\n\r"])/g);
"\n": "%0A", const ESCAPE_MAP = ObjectFreeze({
"\r": "%0D", "\n": "%0A",
'"': "%22", "\r": "%0D",
}; '"': "%22",
});
function escape(str, isFilename) {
return StringPrototypeReplace( return StringPrototypeReplace(
isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"), isFilename
/([\n\r"])/g, ? str
(c) => escapeMap[c], : StringPrototypeReplace(str, ESCAPE_FILENAME_PATTERN, "\r\n"),
ESCAPE_PATTERN,
(c) => ESCAPE_MAP[c],
); );
}; }
const FORM_DETA_SERIALIZE_PATTERN = new SafeRegExp(/\r(?!\n)|(?<!\r)\n/g);
/** /**
* convert FormData to a Blob synchronous without reading all of the files * convert FormData to a Blob synchronous without reading all of the files
@ -313,7 +321,11 @@ function formDataToBlob(formData) {
ArrayPrototypePush( ArrayPrototypePush(
chunks, chunks,
prefix + escape(name) + '"' + CRLF + CRLF + prefix + escape(name) + '"' + CRLF + CRLF +
StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF, StringPrototypeReplace(
value,
FORM_DETA_SERIALIZE_PATTERN,
CRLF,
) + CRLF,
); );
} else { } else {
ArrayPrototypePush( ArrayPrototypePush(

View file

@ -37,9 +37,9 @@ const {
ObjectDefineProperties, ObjectDefineProperties,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
RangeError, RangeError,
RegExp,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeArrayIterator, SafeArrayIterator,
SafeRegExp,
Symbol, Symbol,
SymbolFor, SymbolFor,
TypeError, TypeError,
@ -54,7 +54,7 @@ const REASON_PHRASE = [
...new SafeArrayIterator(OBS_TEXT), ...new SafeArrayIterator(OBS_TEXT),
]; ];
const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE);
const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); const REASON_PHRASE_RE = new SafeRegExp(`^[${REASON_PHRASE_MATCHER}]*$`);
const _response = Symbol("response"); const _response = Symbol("response");
const _headers = Symbol("headers"); const _headers = Symbol("headers");

View file

@ -15,9 +15,9 @@ const {
ArrayPrototypeMap, ArrayPrototypeMap,
ObjectKeys, ObjectKeys,
ObjectFromEntries, ObjectFromEntries,
RegExp,
RegExpPrototypeExec, RegExpPrototypeExec,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeRegExp,
Symbol, Symbol,
SymbolFor, SymbolFor,
TypeError, TypeError,
@ -73,7 +73,7 @@ class URLPattern {
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
const key = keys[i]; const key = keys[i];
try { try {
components[key].regexp = new RegExp( components[key].regexp = new SafeRegExp(
components[key].regexpString, components[key].regexpString,
"u", "u",
); );

View file

@ -15,8 +15,8 @@ const {
Error, Error,
JSONStringify, JSONStringify,
NumberPrototypeToString, NumberPrototypeToString,
RegExp,
SafeArrayIterator, SafeArrayIterator,
SafeRegExp,
String, String,
StringPrototypeCharAt, StringPrototypeCharAt,
StringPrototypeCharCodeAt, StringPrototypeCharCodeAt,
@ -67,7 +67,7 @@ const HTTP_TOKEN_CODE_POINT = [
"\u007E", "\u007E",
...new SafeArrayIterator(ASCII_ALPHANUMERIC), ...new SafeArrayIterator(ASCII_ALPHANUMERIC),
]; ];
const HTTP_TOKEN_CODE_POINT_RE = new RegExp( const HTTP_TOKEN_CODE_POINT_RE = new SafeRegExp(
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
); );
const HTTP_QUOTED_STRING_TOKEN_POINT = [ const HTTP_QUOTED_STRING_TOKEN_POINT = [
@ -75,27 +75,27 @@ const HTTP_QUOTED_STRING_TOKEN_POINT = [
"\u0020-\u007E", "\u0020-\u007E",
"\u0080-\u00FF", "\u0080-\u00FF",
]; ];
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new SafeRegExp(
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
); );
const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE);
const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( const HTTP_TAB_OR_SPACE_PREFIX_RE = new SafeRegExp(
`^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`,
"g", "g",
); );
const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( const HTTP_TAB_OR_SPACE_SUFFIX_RE = new SafeRegExp(
`[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`,
"g", "g",
); );
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
const HTTP_BETWEEN_WHITESPACE = new RegExp( const HTTP_BETWEEN_WHITESPACE = new SafeRegExp(
`^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`, `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`,
); );
const HTTP_WHITESPACE_PREFIX_RE = new RegExp( const HTTP_WHITESPACE_PREFIX_RE = new SafeRegExp(
`^[${HTTP_WHITESPACE_MATCHER}]+`, `^[${HTTP_WHITESPACE_MATCHER}]+`,
"g", "g",
); );
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( const HTTP_WHITESPACE_SUFFIX_RE = new SafeRegExp(
`[${HTTP_WHITESPACE_MATCHER}]+$`, `[${HTTP_WHITESPACE_MATCHER}]+$`,
"g", "g",
); );
@ -150,6 +150,8 @@ function collectSequenceOfCodepoints(input, position, condition) {
return { result: StringPrototypeSlice(input, start, position), position }; return { result: StringPrototypeSlice(input, start, position), position };
} }
const LOWERCASE_PATTERN = new SafeRegExp(/[a-z]/g);
/** /**
* @param {string} s * @param {string} s
* @returns {string} * @returns {string}
@ -157,7 +159,7 @@ function collectSequenceOfCodepoints(input, position, condition) {
function byteUpperCase(s) { function byteUpperCase(s) {
return StringPrototypeReplace( return StringPrototypeReplace(
String(s), String(s),
/[a-z]/g, LOWERCASE_PATTERN,
function byteUpperCaseReplace(c) { function byteUpperCaseReplace(c) {
return StringPrototypeToUpperCase(c); return StringPrototypeToUpperCase(c);
}, },

View file

@ -29,6 +29,7 @@ const {
RegExpPrototypeTest, RegExpPrototypeTest,
// TODO(lucacasonato): add SharedArrayBuffer to primordials // TODO(lucacasonato): add SharedArrayBuffer to primordials
// SharedArrayBufferPrototype // SharedArrayBufferPrototype
SafeRegExp,
StringPrototypeCharAt, StringPrototypeCharAt,
StringPrototypeToLowerCase, StringPrototypeToLowerCase,
StringPrototypeSlice, StringPrototypeSlice,
@ -143,13 +144,15 @@ function processBlobParts(parts, endings) {
return { parts: processedParts, size }; return { parts: processedParts, size };
} }
const NORMALIZE_PATTERN = new SafeRegExp(/^[\x20-\x7E]*$/);
/** /**
* @param {string} str * @param {string} str
* @returns {string} * @returns {string}
*/ */
function normalizeType(str) { function normalizeType(str) {
let normalizedType = str; let normalizedType = str;
if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { if (!RegExpPrototypeTest(NORMALIZE_PATTERN, str)) {
normalizedType = ""; normalizedType = "";
} }
return StringPrototypeToLowerCase(normalizedType); return StringPrototypeToLowerCase(normalizedType);

View file

@ -58,6 +58,7 @@ const {
ReflectHas, ReflectHas,
ReflectOwnKeys, ReflectOwnKeys,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeRegExp,
Set, Set,
SetPrototypeEntries, SetPrototypeEntries,
SetPrototypeForEach, SetPrototypeForEach,
@ -386,7 +387,7 @@ converters.DOMString = function (V, opts = {}) {
}; };
// deno-lint-ignore no-control-regex // deno-lint-ignore no-control-regex
const IS_BYTE_STRING = /^[\x00-\xFF]*$/; const IS_BYTE_STRING = new SafeRegExp(/^[\x00-\xFF]*$/);
converters.ByteString = (V, opts) => { converters.ByteString = (V, opts) => {
const x = converters.DOMString(V, opts); const x = converters.DOMString(V, opts);
if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) { if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) {
@ -500,7 +501,9 @@ ArrayPrototypeForEach(
], ],
(func) => { (func) => {
const name = func.name; const name = func.name;
const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; const article = RegExpPrototypeTest(new SafeRegExp(/^[AEIOU]/), name)
? "an"
: "a";
converters[name] = (V, opts = {}) => { converters[name] = (V, opts = {}) => {
if (TypedArrayPrototypeGetSymbolToStringTag(V) !== name) { if (TypedArrayPrototypeGetSymbolToStringTag(V) !== name) {
throw makeException( throw makeException(

View file

@ -7,6 +7,7 @@ const {
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
Promise, Promise,
SafeArrayIterator, SafeArrayIterator,
SafeRegExp,
StringPrototypeReplace, StringPrototypeReplace,
TypeError, TypeError,
} = primordials; } = primordials;
@ -49,7 +50,7 @@ function createResolvable() {
function pathFromURLWin32(url) { function pathFromURLWin32(url) {
let p = StringPrototypeReplace( let p = StringPrototypeReplace(
url.pathname, url.pathname,
/^\/*([A-Za-z]:)(\/|$)/, new SafeRegExp(/^\/*([A-Za-z]:)(\/|$)/),
"$1/", "$1/",
); );
p = StringPrototypeReplace( p = StringPrototypeReplace(
@ -59,7 +60,7 @@ function pathFromURLWin32(url) {
); );
p = StringPrototypeReplace( p = StringPrototypeReplace(
p, p,
/%(?![0-9A-Fa-f]{2})/g, new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g),
"%25", "%25",
); );
let path = decodeURIComponent(p); let path = decodeURIComponent(p);
@ -79,7 +80,11 @@ function pathFromURLPosix(url) {
} }
return decodeURIComponent( return decodeURIComponent(
StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"), StringPrototypeReplace(
url.pathname,
new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g),
"%25",
),
); );
} }