Skip to content

Commit

Permalink
improve value type inferred and remove shouldInfer argument
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Jan 11, 2023
1 parent 824e900 commit d620833
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 50 deletions.
14 changes: 10 additions & 4 deletions src/helpers/string-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,9 @@ export function generateRandomResponse(customResponseText) {
* Infers value from string argument
* Inferring goes from more specific to more ambiguous options
*
* @param {string} value
* @returns {any}
* @param {string} value arbitrary string
* @returns {any} converted value
* @throws on unexpected input
*/
export function inferValue(value) {
if (value === 'undefined') {
Expand All @@ -393,7 +394,9 @@ export function inferValue(value) {
return NaN;
}

const numVal = parseFloat(value, 10);
// Number class constructor works 2 times faster than JSON.parse
// and wont interpret mixed inputs like '123asd' as parseFloat would
const numVal = Number(value);
if (!nativeIsNaN(numVal)) {
if (Math.abs(numVal) > 32767) {
throw new Error('number values bigger than 32767 are not allowed');
Expand All @@ -403,8 +406,11 @@ export function inferValue(value) {

let errorMessage = `'${value}' value type can't be inferred`;
try {
// Parse strings, arrays and objects represented as JSON strings
// '[1,2,3,"string"]' > [1, 2, 3, 'string']
// '"arbitrary string"' > 'arbitrary string'
const parsableVal = JSON.parse(value);
if (parsableVal instanceof Object) {
if (parsableVal instanceof Object || typeof parsableVal === 'string') {
return parsableVal;
}
} catch (e) {
Expand Down
37 changes: 20 additions & 17 deletions src/scriptlets/trusted-set-constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
/* eslint-disable max-len */
/**
* @scriptlet trusted-set-constant
*
* @description
* Creates a constant property and assigns it a specified value.
*
Expand All @@ -42,50 +41,54 @@ import {
* ```
*
* - `property` - required, path to a property (joined with `.` if needed). The property must be attached to `window`.
* - `value` - required, an arbitrary value to be set.
* - `inferType` - optional, boolean, to convert specified `value` argument to an inferred type, e.g set `NaN` value instead of `'NaN'` string;
* defaults to no conversion, effectively making `string` the default value type.
* - `value` - required, an arbitrary value to be set. Value type is being inferred from the argument.
* - `stack` - optional, string or regular expression that must match the current function call stack trace;
* if regular expression is invalid it will be skipped
*
* **Examples**
* 1. Set property values of different types
* ```
* ! Set string value
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '0')
* ! Set string value wrapping argument into another pair of quotes
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '"null"')
*
* ✔ window.click_r === '0'
* ✔ window.click_r === 'null'
* ✔ typeof window.click_r === 'string'
*
* ! Set number value
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '0', 'true')
* ! Set inferred null value
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', 'null')
*
* ✔ window.click_r === null
* ✔ typeof window.click_r === 'object'
*
* ! Set number type value
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '48')
*
* ✔ window.click_r === 0
* ✔ window.click_r === 48
* ✔ typeof window.click_r === 'number'
*
* ! Set array or object as property value, argument should be a JSON string
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '[1,"string"]', 'true')
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '{"aaa":123,"bbb":{"ccc":"string"}}', 'true')
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '[1,"string"]')
* example.org#%#//scriptlet('trusted-set-constant', 'click_r', '{"aaa":123,"bbb":{"ccc":"string"}}')
* ```
*
* 2. Use script stack matching to set value
* ```
* ! Any call to `document.first` will return `'1'` if the method is related to `checking.js`
* example.org#%#//scriptlet('trusted-set-constant', 'document.first', '1', '', 'checking.js')
* ! `document.first` will return `1` if the method is related to `checking.js`
* example.org#%#//scriptlet('trusted-set-constant', 'document.first', '1', 'checking.js')
*
* ✔ document.first === '1' // if the condition described above is met
* ✔ document.first === 1 // if the condition described above is met
* ```
*/
/* eslint-enable max-len */
export function trustedSetConstant(source, property, value, inferType, stack) {
export function trustedSetConstant(source, property, value, stack) {
if (!property
|| !matchStackTrace(stack, new Error().stack)) {
return;
}

let constantValue;
try {
constantValue = inferType === 'true' ? inferValue(value) : value;
constantValue = inferValue(value);
} catch (e) {
logMessage(source, e);
return;
Expand Down
68 changes: 39 additions & 29 deletions tests/scriptlets/trusted-set-constant.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ const addSetPropTag = (property, value) => {
*/
const isSupported = (() => typeof document.body.append !== 'undefined')();

const SHOULD_INFER_KEYWORD = 'true';

if (!isSupported) {
test('unsupported', (assert) => {
assert.ok(true, 'Browser does not support it');
Expand All @@ -47,49 +45,55 @@ if (!isSupported) {
test('Infer and set values correctly', (assert) => {
// setting constant to true
const trueProp = 'trueProp';
runScriptletFromTag(trueProp, 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag(trueProp, 'true');
assert.strictEqual(window[trueProp], true, '"true" is set as boolean');
clearGlobalProps(trueProp);

// setting constant to false
const falseProp = 'falseProp';
runScriptletFromTag(falseProp, 'false', SHOULD_INFER_KEYWORD);
runScriptletFromTag(falseProp, 'false');
assert.strictEqual(window[falseProp], false, '"false" is set as boolean');
clearGlobalProps(falseProp);

// setting constant to undefined
const undefinedProp = 'undefinedProp';
runScriptletFromTag(undefinedProp, 'undefined', SHOULD_INFER_KEYWORD);
runScriptletFromTag(undefinedProp, 'undefined');
assert.strictEqual(window[undefinedProp], undefined, '"undefined" is set as undefined');
clearGlobalProps(undefinedProp);

// setting constant to null
const nullProp1 = 'nullProp';
runScriptletFromTag(nullProp1, 'null', SHOULD_INFER_KEYWORD);
runScriptletFromTag(nullProp1, 'null');
assert.strictEqual(window[nullProp1], null, '"null" is set as null');
clearGlobalProps(nullProp1);

// setting constant to string
const stringProp1 = 'stringProp';
runScriptletFromTag(stringProp1, '"123arbitrary string"');
assert.strictEqual(window[stringProp1], '123arbitrary string', 'string type value is set');
clearGlobalProps(stringProp1);

// setting constant to NaN
const nanProp1 = 'nanProp';
runScriptletFromTag(nanProp1, 'NaN', SHOULD_INFER_KEYWORD);
runScriptletFromTag(nanProp1, 'NaN');
assert.ok(nativeIsNaN(window[nanProp1]), '"NaN" is set as NaN');
clearGlobalProps(nanProp1);

// setting constant to number
const numberProp = 'numberProp';
runScriptletFromTag(numberProp, '1234', SHOULD_INFER_KEYWORD);
runScriptletFromTag(numberProp, '1234');
assert.strictEqual(window[numberProp], 1234, '"1234" is set as number');
clearGlobalProps(numberProp);

// setting constant to a negative number
const minusOneProp = 'minusOneProp';
runScriptletFromTag(minusOneProp, '-12.34', SHOULD_INFER_KEYWORD);
runScriptletFromTag(minusOneProp, '-12.34');
assert.strictEqual(window[minusOneProp], -12.34, '"-12.34" is set as number');
clearGlobalProps(minusOneProp);

// setting constant to array
const arrayProp = 'arrayProp';
runScriptletFromTag(arrayProp, '[1,2,3,"string"]', SHOULD_INFER_KEYWORD);
runScriptletFromTag(arrayProp, '[1,2,3,"string"]');
assert.deepEqual(window[arrayProp], [1, 2, 3, 'string'], '"[1,2,3,"string"]" is set as array');
clearGlobalProps(arrayProp);

Expand All @@ -101,7 +105,7 @@ if (!isSupported) {
ccc: 'string',
},
};
runScriptletFromTag(objectProp, '{"aaa":123,"bbb":{"ccc":"string"}}', SHOULD_INFER_KEYWORD);
runScriptletFromTag(objectProp, '{"aaa":123,"bbb":{"ccc":"string"}}');
assert.deepEqual(window[objectProp], expected, '"{"aaa":123,"bbb":{"ccc":"string"}}" is set as object');
clearGlobalProps(objectProp);
});
Expand All @@ -111,19 +115,22 @@ if (!isSupported) {
const illegalProp = 'illegalProp';

console.log = function log(input) {
if (typeof input !== 'string') {
return;
}
const messageLogged = input.includes('number values bigger than 32767 are not allowed')
|| input.includes('value type can\'t be inferred');

assert.ok(messageLogged, 'appropriate message is logged');
};

// not setting constant to illegalNumber
runScriptletFromTag(illegalProp, 32768, SHOULD_INFER_KEYWORD);
runScriptletFromTag(illegalProp, 32768);
assert.strictEqual(window[illegalProp], undefined);
clearGlobalProps(illegalProp);

// not setting constant to unknown value
runScriptletFromTag(illegalProp, '{|', SHOULD_INFER_KEYWORD);
runScriptletFromTag(illegalProp, '{|');
assert.strictEqual(window[illegalProp], undefined);
clearGlobalProps(illegalProp);
});
Expand All @@ -132,7 +139,7 @@ if (!isSupported) {
window.testObj = {
testChain: null,
};
runScriptletFromTag('testObj.testChain.testProp', 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag('testObj.testChain.testProp', 'true');
window.testObj.testChain = {
testProp: false,
otherProp: 'someValue',
Expand All @@ -147,14 +154,14 @@ if (!isSupported) {
test('set value on null prop', (assert) => {
// end prop is null
window.nullProp2 = null;
runScriptletFromTag('nullProp2', '15', SHOULD_INFER_KEYWORD);
runScriptletFromTag('nullProp2', '15');
assert.strictEqual(window.nullProp2, 15, 'null end prop changed');
clearGlobalProps('nullProp2');
});

test('set value through chain with empty object', (assert) => {
window.emptyObj = {};
runScriptletFromTag('emptyObj.a.prop', 'true');
runScriptletFromTag('emptyObj.a.prop', '"true"');
window.emptyObj.a = {};
assert.strictEqual(window.emptyObj.a.prop, 'true', 'target prop set');
clearGlobalProps('emptyObj');
Expand All @@ -165,7 +172,7 @@ if (!isSupported) {
window.nullChain = {
nullProp: null,
};
runScriptletFromTag('nullChain.nullProp.endProp', 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag('nullChain.nullProp.endProp', 'true');
window.nullChain.nullProp = {
endProp: false,
};
Expand All @@ -175,15 +182,15 @@ if (!isSupported) {

test('sets values to the chained properties', (assert) => {
window.chained = { property: {} };
runScriptletFromTag('chained.property.aaa', 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag('chained.property.aaa', 'true');
assert.strictEqual(window.chained.property.aaa, true);
clearGlobalProps('chained');
});

test('sets values on the same chain (defined)', (assert) => {
window.chained = { property: {} };
runScriptletFromTag('chained.property.aaa', 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag('chained.property.bbb', '10', SHOULD_INFER_KEYWORD);
runScriptletFromTag('chained.property.aaa', 'true');
runScriptletFromTag('chained.property.bbb', '10');

assert.strictEqual(window.chained.property.aaa, true);
assert.strictEqual(window.chained.property.bbb, 10);
Expand All @@ -192,8 +199,8 @@ if (!isSupported) {
});

test('sets values on the same chain (undefined)', (assert) => {
runScriptletFromTag('chained.property.aaa', 'true', SHOULD_INFER_KEYWORD);
runScriptletFromTag('chained.property.bbb', '10', SHOULD_INFER_KEYWORD);
runScriptletFromTag('chained.property.aaa', 'true');
runScriptletFromTag('chained.property.bbb', '10');
window.chained = { property: {} };

assert.strictEqual(window.chained.property.aaa, true);
Expand Down Expand Up @@ -223,12 +230,12 @@ if (!isSupported) {
test('sets values correctly + stack match', (assert) => {
const stackMatch = 'trusted-set-constant';
const trueProp = 'trueProp02';
runScriptletFromTag(trueProp, 'true', SHOULD_INFER_KEYWORD, stackMatch);
runScriptletFromTag(trueProp, 'true', stackMatch);
assert.strictEqual(window[trueProp], true, 'stack match: trueProp - ok');
clearGlobalProps(trueProp);

const numProp = 'numProp';
runScriptletFromTag(numProp, '123', SHOULD_INFER_KEYWORD, stackMatch);
runScriptletFromTag(numProp, '123', stackMatch);
assert.strictEqual(window[numProp], 123, 'stack match: numProp - ok');
clearGlobalProps(numProp);
});
Expand All @@ -237,15 +244,15 @@ if (!isSupported) {
window.chained = { property: {} };
const stackNoMatch = 'no_match.js';

runScriptletFromTag('chained.property.aaa', 'true', '', stackNoMatch);
runScriptletFromTag('chained.property.aaa', 'true', stackNoMatch);

assert.strictEqual(window.chained.property.aaa, undefined);
clearGlobalProps('chained');

const property = 'customProp';
const firstValue = 10;

runScriptletFromTag(property, firstValue, '', stackNoMatch);
runScriptletFromTag(property, firstValue, stackNoMatch);

assert.strictEqual(window[property], undefined);
clearGlobalProps(property);
Expand All @@ -257,7 +264,7 @@ if (!isSupported) {
const property = 'customProp';
const value = '10';

runScriptletFromTag(property, value, '', stackArg);
runScriptletFromTag(property, value, stackArg);

assert.strictEqual(window[property], undefined, 'property should not be set');
clearGlobalProps(property);
Expand Down Expand Up @@ -315,7 +322,7 @@ if (!isSupported) {
aaa: true,
},
};
runScriptletFromTag('loopObj.chainProp.bbb', '1', SHOULD_INFER_KEYWORD);
runScriptletFromTag('loopObj.chainProp.bbb', '1');
// eslint-disable-next-line no-self-assign
window.loopObj = window.loopObj;
window.loopObj.chainProp.bbb = 0;
Expand All @@ -326,13 +333,16 @@ if (!isSupported) {
test('trying to set non-configurable silently exits', (assert) => {
assert.expect(2);
console.log = function log(input) {
if (typeof input !== 'string') {
return;
}
assert.ok(input.includes('testProp'), 'non-configurable prop logged');
};
Object.defineProperty(window, 'testProp', {
value: 5,
configurable: false,
});
runScriptletFromTag('window.testProp', '0', SHOULD_INFER_KEYWORD);
runScriptletFromTag('window.testProp', '0');
assert.strictEqual(window.testProp, 5, 'error avoided');
clearGlobalProps('testProp');
});
Expand Down

0 comments on commit d620833

Please sign in to comment.