Skip to content

Commit 43ddf46

Browse files
committed
lib: add safe text transform methods to primordials
1 parent 82b44f4 commit 43ddf46

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed

lib/internal/per_context/primordials.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ function copyPrototype(src, dest, prefix) {
225225
copyPrototype(original.prototype, primordials, `${name}Prototype`);
226226
});
227227

228+
const { String } = primordials;
229+
228230
// Create copies of abstract intrinsic objects that are not directly exposed
229231
// on the global object.
230232
// Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object
@@ -248,20 +250,208 @@ function copyPrototype(src, dest, prefix) {
248250

249251
const {
250252
ArrayPrototypeForEach,
253+
Error,
251254
FinalizationRegistry,
252255
FunctionPrototypeCall,
253256
Map,
257+
MathMax,
258+
MathMin,
259+
ObjectCreate,
260+
ObjectDefineProperties,
254261
ObjectFreeze,
255262
ObjectSetPrototypeOf,
256263
Promise,
257264
PromisePrototypeThen,
265+
RegExp,
266+
RegExpPrototype,
267+
RegExpPrototypeExec,
268+
RegExpPrototypeGetUnicode,
269+
RegExpPrototypeGetSticky,
270+
RegExpPrototypeSymbolMatch,
271+
RegExpPrototypeSymbolReplace,
272+
RegExpPrototypeSymbolSearch,
258273
Set,
274+
StringPrototypeReplace,
275+
StringPrototypeReplaceAll,
276+
StringPrototypeSlice,
277+
StringPrototypeSplit,
259278
SymbolIterator,
279+
SymbolMatch,
280+
SymbolMatchAll,
281+
SymbolReplace,
282+
SymbolSearch,
283+
SymbolSplit,
260284
WeakMap,
261285
WeakRef,
262286
WeakSet,
263287
} = primordials;
264288

289+
{
290+
const undefinedDesc = ObjectCreate(null);
291+
undefinedDesc.value = undefined;
292+
const safeTextTransformProperties = {
293+
[SymbolMatch]: undefinedDesc,
294+
[SymbolMatchAll]: undefinedDesc,
295+
[SymbolReplace]: undefinedDesc,
296+
[SymbolSearch]: undefinedDesc,
297+
[SymbolSplit]: undefinedDesc,
298+
exec: undefinedDesc,
299+
};
300+
const defineRegExpGetter = (name) => {
301+
safeTextTransformProperties[name] =
302+
ReflectGetOwnPropertyDescriptor(RegExpPrototype, name);
303+
};
304+
defineRegExpGetter('flags');
305+
defineRegExpGetter('global');
306+
defineRegExpGetter('ignoreCase');
307+
defineRegExpGetter('multiline');
308+
defineRegExpGetter('dotAll');
309+
defineRegExpGetter('unicode');
310+
defineRegExpGetter('sticky');
311+
ObjectFreeze(safeTextTransformProperties);
312+
313+
const toSafeRegex = (stringOrRegex) =>
314+
ObjectDefineProperties(stringOrRegex, safeTextTransformProperties);
315+
const toSafeString = (string) => toSafeRegex(new String(string));
316+
primordials.makeSafeStringObject = toSafeString;
317+
318+
/**
319+
* @param {string} thisString
320+
* @param {string} needle
321+
* @returns {string[]}
322+
*/
323+
primordials.SafeStringPrototypeMatch = (thisString, needle) =>
324+
RegExpPrototypeSymbolMatch(toSafeRegex(new RegExp(needle)), thisString);
325+
326+
/**
327+
* @param {string} thisStr
328+
* @param {string} needle
329+
* @returns {string[]}
330+
*/
331+
primordials.SafeStringPrototypeMatchAll = (thisStr, needle) =>
332+
RegExpPrototypeSymbolMatch(toSafeRegex(new RegExp(needle, 'g')), thisStr);
333+
334+
/**
335+
* @param {string} thisStr
336+
* @param {string} searchVal
337+
* @param {string | (substring: string, ...args: any[]) => string} replaceVal
338+
* @returns {string}
339+
*/
340+
primordials.SafeStringPrototypeReplace = (thisStr, searchVal, replaceVal) =>
341+
StringPrototypeReplace(thisStr, toSafeString(searchVal), replaceVal);
342+
343+
/**
344+
* @param {string} thisStr
345+
* @param {string} searchVal
346+
* @param {string | (substring: string, ...args: any[]) => string} replaceVal
347+
* @returns {string}
348+
*/
349+
primordials.SafeStringPrototypeReplaceAll =
350+
(thisStr, searchVal, replaceVal) =>
351+
StringPrototypeReplaceAll(thisStr, toSafeString(searchVal), replaceVal);
352+
353+
/**
354+
* @param {string} thisString
355+
* @param {string} needle
356+
* @returns {number}
357+
*/
358+
primordials.SafeStringPrototypeSearch = (thisString, needle) =>
359+
RegExpPrototypeSymbolSearch(toSafeRegex(new RegExp(needle)), thisString);
360+
361+
/**
362+
* @param {string} thisString
363+
* @param {string} splitter
364+
* @param {number} [limit]
365+
* @returns {string}
366+
*/
367+
primordials.SafeStringPrototypeSplit = (thisString, splitter, limit) =>
368+
StringPrototypeSplit(thisString, toSafeString(splitter), limit);
369+
370+
/**
371+
* @param {RegExp} thisRegex
372+
* @param {string} string
373+
* @returns {string[]}
374+
*/
375+
primordials.SafeRegExpPrototypeSymbolMatch = (thisRegex, string) =>
376+
RegExpPrototypeSymbolMatch(toSafeRegex(thisRegex), string);
377+
378+
/**
379+
* @param {RegExp} thisRegex
380+
* @param {string} string
381+
* @param {string | (substring: string, ...args: any[]) => string} replaceVal
382+
* @returns {string}
383+
*/
384+
primordials.SafeRegExpPrototypeSymbolReplace =
385+
(thisRegex, string, replaceVal) =>
386+
RegExpPrototypeSymbolReplace(toSafeRegex(thisRegex), string, replaceVal);
387+
388+
/**
389+
* @param {RegExp} thisRegex
390+
* @param {string} string
391+
* @returns {number}
392+
*/
393+
primordials.SafeRegExpPrototypeSymbolSearch = (thisRegex, string) =>
394+
RegExpPrototypeSymbolSearch(toSafeRegex(thisRegex), string);
395+
396+
/**
397+
* Re-implementation of `RegExp.prototype [ @@split ]` that do not rely on
398+
* user-mutable methods.
399+
* @ref https://tc39.es/ecma262/#sec-regexp.prototype-@@split
400+
* @param {RegExp} splitter
401+
* @param {string} string
402+
* @param {number} [limit]
403+
* @returns {string[]}
404+
*/
405+
primordials.SafeRegExpPrototypeSymbolSplit = (splitter, string, limit) => {
406+
if (!RegExpPrototypeGetSticky(splitter))
407+
// eslint-disable-next-line no-restricted-syntax
408+
throw new Error('You must use sticky flag (y).');
409+
if (RegExpPrototypeGetUnicode(splitter))
410+
// eslint-disable-next-line no-restricted-syntax
411+
throw new Error('Unicode matching regex not supported');
412+
const S = string;
413+
const A = [];
414+
let lengthA = 0;
415+
if (limit === 0) return A;
416+
const size = S.length;
417+
if (size === 0) {
418+
const z = RegExpPrototypeExec(splitter, S);
419+
if (z === null) A[0] = S;
420+
return A;
421+
}
422+
423+
let p = 0;
424+
let q = p;
425+
while (q < size) {
426+
splitter.lastIndex = q;
427+
const z = RegExpPrototypeExec(splitter, S);
428+
if (z === null) q++;
429+
else {
430+
const e = MathMin(splitter.lastIndex, size);
431+
if (e === p) q++;
432+
else {
433+
const T = StringPrototypeSlice(S, p, q);
434+
A[lengthA++] = T;
435+
if (lengthA === limit) return A;
436+
p = e;
437+
const numberOfCaptures = MathMax(z.length - 1, 0);
438+
for (let i = 1; i <= numberOfCaptures; i++) {
439+
const nextCapture = z[i];
440+
A[lengthA++] = nextCapture;
441+
if (lengthA === limit) return A;
442+
}
443+
q = p;
444+
}
445+
}
446+
}
447+
A[lengthA] = StringPrototypeSlice(S, p, size);
448+
return A;
449+
};
450+
451+
primordials.SafeRegExpPrototypeTest = (thisRegex, string) =>
452+
RegExpPrototypeExec(thisRegex, string) !== null;
453+
}
454+
265455
// Because these functions are used by `makeSafe`, which is exposed
266456
// on the `primordials` object, it's important to use const references
267457
// to the primordials that they use:
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('assert');
6+
7+
const {
8+
SafeRegExpPrototypeSymbolMatch,
9+
SafeRegExpPrototypeSymbolReplace,
10+
SafeRegExpPrototypeSymbolSearch,
11+
SafeRegExpPrototypeTest,
12+
SafeRegExpPrototypeSymbolSplit,
13+
SafeStringPrototypeMatch,
14+
SafeStringPrototypeMatchAll,
15+
SafeStringPrototypeSearch,
16+
SafeStringPrototypeReplace,
17+
SafeStringPrototypeReplaceAll,
18+
SafeStringPrototypeSplit,
19+
} = require('internal/test/binding').primordials;
20+
21+
const defineRegExpGetter = (name) =>
22+
Reflect.defineProperty(RegExp.prototype, name, {
23+
get: common.mustNotCall(`get RegExp.prototype.${name}`),
24+
});
25+
26+
RegExp.prototype[Symbol.match] = common.mustNotCall(
27+
'RegExp.prototype[@@match]'
28+
);
29+
RegExp.prototype[Symbol.matchAll] = common.mustNotCall(
30+
'RegExp.prototype[@@matchAll]'
31+
);
32+
RegExp.prototype[Symbol.replace] = common.mustNotCall(
33+
'RegExp.prototype[@@replace]'
34+
);
35+
RegExp.prototype[Symbol.search] = common.mustNotCall(
36+
'RegExp.prototype[@@search]'
37+
);
38+
RegExp.prototype[Symbol.split] = common.mustNotCall(
39+
'RegExp.prototype[@@split]'
40+
);
41+
RegExp.prototype.exec = common.mustNotCall('RegExp.prototype.exec');
42+
[
43+
'flags',
44+
'global',
45+
'ignoreCase',
46+
'multiline',
47+
'dotAll',
48+
'unicode',
49+
'sticky',
50+
].forEach(defineRegExpGetter);
51+
52+
String.prototype[Symbol.match] = common.mustNotCall(
53+
'String.prototype[@@match]'
54+
);
55+
String.prototype[Symbol.matchAll] = common.mustNotCall(
56+
'String.prototype[@@matchAll]'
57+
);
58+
String.prototype[Symbol.replace] = common.mustNotCall(
59+
'String.prototype[@@replace]'
60+
);
61+
String.prototype[Symbol.search] = common.mustNotCall(
62+
'String.prototype[@@search]'
63+
);
64+
String.prototype[Symbol.split] = common.mustNotCall(
65+
'String.prototype[@@split]'
66+
);
67+
68+
{
69+
const expected = ['o'];
70+
expected.groups = undefined;
71+
expected.input = 'foo';
72+
expected.index = 1;
73+
assert.deepStrictEqual(SafeStringPrototypeMatch('foo', 'o'), expected);
74+
}
75+
76+
assert.deepStrictEqual(SafeStringPrototypeMatchAll('foo', 'o'),
77+
['o', 'o']);
78+
79+
assert.strictEqual(SafeStringPrototypeReplace('foo', 'o', 'a'), 'fao');
80+
assert.strictEqual(SafeStringPrototypeReplaceAll('foo', 'o', 'a'), 'faa');
81+
assert.strictEqual(SafeStringPrototypeSearch('foo', 'o'), 1);
82+
assert.deepStrictEqual(SafeStringPrototypeSplit('foo', 'o'), ['f', '', '']);
83+
84+
{
85+
const expected = ['o'];
86+
expected.groups = undefined;
87+
expected.input = 'foo';
88+
expected.index = 1;
89+
assert.deepStrictEqual(SafeRegExpPrototypeSymbolMatch(/o/, 'foo'), expected);
90+
}
91+
92+
assert.strictEqual(SafeRegExpPrototypeSymbolReplace(/o/, 'foo', 'a'), 'fao');
93+
assert.strictEqual(SafeRegExpPrototypeSymbolSearch(/o/, 'foo'), 1);
94+
assert.deepStrictEqual(SafeRegExpPrototypeSymbolSplit(/o/y, 'foo'), ['f', '', '']);
95+
assert.strictEqual(SafeRegExpPrototypeTest(/o/, 'foo'), true);

0 commit comments

Comments
 (0)