Skip to content

Commit 9154cfe

Browse files
committed
helpers
1 parent cdc2551 commit 9154cfe

File tree

2 files changed

+218
-5
lines changed

2 files changed

+218
-5
lines changed

spec/Utils.spec.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const Utils = require('../lib/Utils');
22
const { createSanitizedError, createSanitizedHttpError } = require("../lib/Error")
3+
const vm = require('vm');
34

45
describe('Utils', () => {
56
describe('encodeForUrl', () => {
@@ -287,4 +288,156 @@ describe('Utils', () => {
287288
expect(error.message).toBe('Detailed error message');
288289
});
289290
});
291+
292+
describe('isDate', () => {
293+
it('should return true for a Date', () => {
294+
expect(Utils.isDate(new Date())).toBe(true);
295+
});
296+
it('should return true for a cross-realm Date', () => {
297+
const crossRealmDate = vm.runInNewContext('new Date()');
298+
expect(crossRealmDate instanceof Date).toBe(false);
299+
expect(Utils.isDate(crossRealmDate)).toBe(true);
300+
});
301+
it('should return false for non-Date values', () => {
302+
expect(Utils.isDate(null)).toBe(false);
303+
expect(Utils.isDate(undefined)).toBe(false);
304+
expect(Utils.isDate('2021-01-01')).toBe(false);
305+
expect(Utils.isDate(123)).toBe(false);
306+
expect(Utils.isDate({})).toBe(false);
307+
});
308+
});
309+
310+
describe('isRegExp', () => {
311+
it('should return true for a RegExp', () => {
312+
expect(Utils.isRegExp(/test/)).toBe(true);
313+
expect(Utils.isRegExp(new RegExp('test'))).toBe(true);
314+
});
315+
it('should return true for a cross-realm RegExp', () => {
316+
const crossRealmRegExp = vm.runInNewContext('/test/');
317+
expect(crossRealmRegExp instanceof RegExp).toBe(false);
318+
expect(Utils.isRegExp(crossRealmRegExp)).toBe(true);
319+
});
320+
it('should return false for non-RegExp values', () => {
321+
expect(Utils.isRegExp(null)).toBe(false);
322+
expect(Utils.isRegExp(undefined)).toBe(false);
323+
expect(Utils.isRegExp('/test/')).toBe(false);
324+
expect(Utils.isRegExp({})).toBe(false);
325+
});
326+
});
327+
328+
describe('isMap', () => {
329+
it('should return true for a Map', () => {
330+
expect(Utils.isMap(new Map())).toBe(true);
331+
});
332+
it('should return true for a cross-realm Map', () => {
333+
const crossRealmMap = vm.runInNewContext('new Map()');
334+
expect(crossRealmMap instanceof Map).toBe(false);
335+
expect(Utils.isMap(crossRealmMap)).toBe(true);
336+
});
337+
it('should return false for non-Map values', () => {
338+
expect(Utils.isMap(null)).toBe(false);
339+
expect(Utils.isMap(undefined)).toBe(false);
340+
expect(Utils.isMap({})).toBe(false);
341+
expect(Utils.isMap(new Set())).toBe(false);
342+
});
343+
});
344+
345+
describe('isSet', () => {
346+
it('should return true for a Set', () => {
347+
expect(Utils.isSet(new Set())).toBe(true);
348+
});
349+
it('should return true for a cross-realm Set', () => {
350+
const crossRealmSet = vm.runInNewContext('new Set()');
351+
expect(crossRealmSet instanceof Set).toBe(false);
352+
expect(Utils.isSet(crossRealmSet)).toBe(true);
353+
});
354+
it('should return false for non-Set values', () => {
355+
expect(Utils.isSet(null)).toBe(false);
356+
expect(Utils.isSet(undefined)).toBe(false);
357+
expect(Utils.isSet({})).toBe(false);
358+
expect(Utils.isSet(new Map())).toBe(false);
359+
});
360+
});
361+
362+
describe('isNativeError', () => {
363+
it('should return true for an Error', () => {
364+
expect(Utils.isNativeError(new Error('test'))).toBe(true);
365+
});
366+
it('should return true for Error subclasses', () => {
367+
expect(Utils.isNativeError(new TypeError('test'))).toBe(true);
368+
expect(Utils.isNativeError(new RangeError('test'))).toBe(true);
369+
});
370+
it('should return true for a cross-realm Error', () => {
371+
const crossRealmError = vm.runInNewContext('new Error("test")');
372+
expect(crossRealmError instanceof Error).toBe(false);
373+
expect(Utils.isNativeError(crossRealmError)).toBe(true);
374+
});
375+
it('should return false for non-Error values', () => {
376+
expect(Utils.isNativeError(null)).toBe(false);
377+
expect(Utils.isNativeError(undefined)).toBe(false);
378+
expect(Utils.isNativeError({ message: 'fake' })).toBe(false);
379+
expect(Utils.isNativeError('error')).toBe(false);
380+
});
381+
});
382+
383+
describe('isPromise', () => {
384+
it('should return true for a Promise', () => {
385+
expect(Utils.isPromise(Promise.resolve())).toBe(true);
386+
});
387+
it('should return true for a cross-realm Promise', () => {
388+
const crossRealmPromise = vm.runInNewContext('Promise.resolve()');
389+
expect(crossRealmPromise instanceof Promise).toBe(false);
390+
expect(Utils.isPromise(crossRealmPromise)).toBe(true);
391+
});
392+
it('should return true for a thenable', () => {
393+
expect(Utils.isPromise({ then: () => {} })).toBe(true);
394+
});
395+
it('should return false for non-Promise values', () => {
396+
expect(Utils.isPromise(null)).toBe(false);
397+
expect(Utils.isPromise(undefined)).toBe(false);
398+
expect(Utils.isPromise({})).toBe(false);
399+
expect(Utils.isPromise(42)).toBe(false);
400+
});
401+
it('should return false for plain objects when Object.prototype.then is polluted', () => {
402+
Object.prototype.then = () => {};
403+
try {
404+
expect(Utils.isPromise({})).toBe(false);
405+
expect(Utils.isPromise({ a: 1 })).toBe(false);
406+
} finally {
407+
delete Object.prototype.then;
408+
}
409+
});
410+
it('should return true for real thenables even when Object.prototype.then is polluted', () => {
411+
Object.prototype.then = () => {};
412+
try {
413+
expect(Utils.isPromise({ then: () => {} })).toBe(true);
414+
expect(Utils.isPromise(Promise.resolve())).toBe(true);
415+
} finally {
416+
delete Object.prototype.then;
417+
}
418+
});
419+
});
420+
421+
describe('isObject', () => {
422+
it('should return true for plain objects', () => {
423+
expect(Utils.isObject({})).toBe(true);
424+
expect(Utils.isObject({ a: 1 })).toBe(true);
425+
});
426+
it('should return true for a cross-realm object', () => {
427+
const crossRealmObj = vm.runInNewContext('({ a: 1 })');
428+
expect(crossRealmObj instanceof Object).toBe(false);
429+
expect(Utils.isObject(crossRealmObj)).toBe(true);
430+
});
431+
it('should return true for arrays and other objects', () => {
432+
expect(Utils.isObject([])).toBe(true);
433+
expect(Utils.isObject(new Date())).toBe(true);
434+
});
435+
it('should return false for non-object values', () => {
436+
expect(Utils.isObject(null)).toBe(false);
437+
expect(Utils.isObject(undefined)).toBe(false);
438+
expect(Utils.isObject(42)).toBe(false);
439+
expect(Utils.isObject('string')).toBe(false);
440+
expect(Utils.isObject(true)).toBe(false);
441+
});
442+
});
290443
});

src/Utils.js

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
const path = require('path');
88
const fs = require('fs').promises;
9+
const { types } = require('util');
910

1011
/**
1112
* The general purpose utilities.
@@ -120,12 +121,71 @@ class Utils {
120121
}
121122

122123
/**
123-
* Determines whether an object is a Promise.
124-
* @param {any} object The object to validate.
125-
* @returns {Boolean} Returns true if the object is a promise.
124+
* Realm-safe check for Date.
125+
* @param {any} value The value to check.
126+
* @returns {Boolean} Returns true if the value is a Date.
126127
*/
127-
static isPromise(object) {
128-
return object instanceof Promise;
128+
static isDate(value) {
129+
return types.isDate(value);
130+
}
131+
132+
/**
133+
* Realm-safe check for RegExp.
134+
* @param {any} value The value to check.
135+
* @returns {Boolean} Returns true if the value is a RegExp.
136+
*/
137+
static isRegExp(value) {
138+
return types.isRegExp(value);
139+
}
140+
141+
/**
142+
* Realm-safe check for Map.
143+
* @param {any} value The value to check.
144+
* @returns {Boolean} Returns true if the value is a Map.
145+
*/
146+
static isMap(value) {
147+
return types.isMap(value);
148+
}
149+
150+
/**
151+
* Realm-safe check for Set.
152+
* @param {any} value The value to check.
153+
* @returns {Boolean} Returns true if the value is a Set.
154+
*/
155+
static isSet(value) {
156+
return types.isSet(value);
157+
}
158+
159+
/**
160+
* Realm-safe check for native Error.
161+
* @param {any} value The value to check.
162+
* @returns {Boolean} Returns true if the value is a native Error.
163+
*/
164+
static isNativeError(value) {
165+
return types.isNativeError(value);
166+
}
167+
168+
/**
169+
* Realm-safe check for Promise (duck-typed as thenable).
170+
* Guards against Object.prototype pollution by ensuring `then` is not
171+
* inherited solely from Object.prototype.
172+
* @param {any} value The value to check.
173+
* @returns {Boolean} Returns true if the value is a Promise or thenable.
174+
*/
175+
static isPromise(value) {
176+
if (value == null || typeof value.then !== 'function') {
177+
return false;
178+
}
179+
return Object.getPrototypeOf(value) !== Object.prototype || Object.prototype.hasOwnProperty.call(value, 'then');
180+
}
181+
182+
/**
183+
* Realm-safe check for object (non-null, typeof object).
184+
* @param {any} value The value to check.
185+
* @returns {Boolean} Returns true if the value is a non-null object.
186+
*/
187+
static isObject(value) {
188+
return typeof value === 'object' && value !== null;
129189
}
130190

131191
/**

0 commit comments

Comments
 (0)