diff --git a/src/js/resources/attribute.js b/src/js/resources/attribute.js index b6ecdf7a3..ed735eecd 100644 --- a/src/js/resources/attribute.js +++ b/src/js/resources/attribute.js @@ -229,7 +229,7 @@ export function removeAttr( if ( rawToken === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior); - const tokens = rawToken.split(/\s*\|\s*/); + const tokens = safe.String_split.call(rawToken, /\s*\|\s*/); const selector = tokens .map(a => `${rawSelector}[${CSS.escape(a)}]`) .join(','); @@ -289,7 +289,7 @@ export function removeAttr( subtree: true, }); }; - runAt(( ) => { start(); }, behavior.split(/\s+/)); + runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/)); } registerScriptlet(removeAttr, { name: 'remove-attr.js', diff --git a/src/js/resources/cookie.js b/src/js/resources/cookie.js index 3fbeb1858..29ad7315c 100644 --- a/src/js/resources/cookie.js +++ b/src/js/resources/cookie.js @@ -53,7 +53,8 @@ registerScriptlet(getSafeCookieValuesFn, { /******************************************************************************/ export function getAllCookiesFn() { - return document.cookie.split(/\s*;\s*/).map(s => { + const safe = safeSelf(); + return safe.String_split.call(document.cookie, /\s*;\s*/).map(s => { const pos = s.indexOf('='); if ( pos === 0 ) { return; } if ( pos === -1 ) { return `${s.trim()}=`; } @@ -64,6 +65,9 @@ export function getAllCookiesFn() { } registerScriptlet(getAllCookiesFn, { name: 'get-all-cookies.fn', + dependencies: [ + safeSelf, + ], }); /******************************************************************************/ @@ -71,7 +75,8 @@ registerScriptlet(getAllCookiesFn, { export function getCookieFn( name = '' ) { - for ( const s of document.cookie.split(/\s*;\s*/) ) { + const safe = safeSelf(); + for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) { const pos = s.indexOf('='); if ( pos === -1 ) { continue; } if ( s.slice(0, pos) !== name ) { continue; } @@ -80,6 +85,9 @@ export function getCookieFn( } registerScriptlet(getCookieFn, { name: 'get-cookie.fn', + dependencies: [ + safeSelf, + ], }); /******************************************************************************/ @@ -349,7 +357,7 @@ export function removeCookie( }, ms); }; const remove = ( ) => { - document.cookie.split(';').forEach(cookieStr => { + safe.String_split.call(document.cookie, ';').forEach(cookieStr => { const pos = cookieStr.indexOf('='); if ( pos === -1 ) { return; } const cookieName = cookieStr.slice(0, pos).trim(); @@ -387,7 +395,7 @@ export function removeCookie( window.addEventListener('beforeunload', remove); if ( typeof extraArgs.when !== 'string' ) { return; } const supportedEventTypes = [ 'scroll', 'keydown' ]; - const eventTypes = extraArgs.when.split(/\s/); + const eventTypes = safe.String_split.call(extraArgs.when, /\s/); for ( const type of eventTypes ) { if ( supportedEventTypes.includes(type) === false ) { continue; } document.addEventListener(type, ( ) => { diff --git a/src/js/resources/proxy-apply.js b/src/js/resources/proxy-apply.js index 09d357f90..60326f546 100644 --- a/src/js/resources/proxy-apply.js +++ b/src/js/resources/proxy-apply.js @@ -52,7 +52,7 @@ export function proxyApplyFn( } reflect() { const r = Reflect.construct(this.callFn, this.callArgs); - this.callFn = this.callArgs = undefined; + this.callFn = this.callArgs = this.private = undefined; proxyApplyFn.ctorContexts.push(this); return r; } @@ -75,7 +75,7 @@ export function proxyApplyFn( } reflect() { const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); - this.callFn = this.thisArg = this.callArgs = undefined; + this.callFn = this.thisArg = this.callArgs = this.private = undefined; proxyApplyFn.applyContexts.push(this); return r; } diff --git a/src/js/resources/replace-argument.js b/src/js/resources/replace-argument.js index 150bf047b..f06938b74 100644 --- a/src/js/resources/replace-argument.js +++ b/src/js/resources/replace-argument.js @@ -71,25 +71,39 @@ export function trustedReplaceArgument( const reCondition = extraArgs.condition ? safe.patternToRegex(extraArgs.condition) : /^/; - proxyApplyFn(propChain, function(context) { + const getArg = context => { + if ( argposRaw === 'this' ) { return context.thisArg; } const { callArgs } = context; - if ( argposRaw === '' ) { - safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`); - return context.reflect(); - } const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset; - if ( argpos < 0 || argpos >= callArgs.length ) { + if ( argpos < 0 || argpos >= callArgs.length ) { return; } + context.private = { argpos }; + return callArgs[argpos]; + }; + const setArg = (context, value) => { + if ( argposRaw === 'this' ) { + if ( value !== context.thisArg ) { + context.thisArg = value; + } + } else if ( context.private ) { + context.callArgs[context.private.argpos] = value; + } + }; + proxyApplyFn(propChain, function(context) { + if ( argposRaw === '' ) { + safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`); return context.reflect(); } - const argBefore = callArgs[argpos]; + const argBefore = getArg(context); if ( safe.RegExp_test.call(reCondition, argBefore) === false ) { return context.reflect(); } const argAfter = replacer && typeof argBefore === 'string' ? argBefore.replace(replacer.re, replacer.replacement) : value; - callArgs[argpos] = argAfter; - safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); + if ( argAfter !== argBefore ) { + setArg(context, argAfter); + safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); + } return context.reflect(); }); } diff --git a/src/js/resources/safe-self.js b/src/js/resources/safe-self.js index a1840e13b..afb987221 100644 --- a/src/js/resources/safe-self.js +++ b/src/js/resources/safe-self.js @@ -51,6 +51,7 @@ export function safeSelf() { 'RegExp_exec': self.RegExp.prototype.exec, 'Request_clone': self.Request.prototype.clone, 'String_fromCharCode': String.fromCharCode, + 'String_split': String.prototype.split, 'XMLHttpRequest': self.XMLHttpRequest, 'addEventListener': self.EventTarget.prototype.addEventListener, 'removeEventListener': self.EventTarget.prototype.removeEventListener, diff --git a/src/js/resources/scriptlets.js b/src/js/resources/scriptlets.js index d1e4c4b58..f8a761990 100644 --- a/src/js/resources/scriptlets.js +++ b/src/js/resources/scriptlets.js @@ -192,7 +192,7 @@ function abortCurrentScriptCore( const reContext = safe.patternToRegex(context); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); const thisScript = document.currentScript; - const chain = target.split('.'); + const chain = safe.String_split.call(target, '.'); let owner = window; let prop; for (;;) { @@ -406,6 +406,7 @@ builtinScriptlets.push({ dependencies: [ 'matches-stack-trace.fn', 'object-find-owner.fn', + 'safe-self.fn', ], }); // When no "prune paths" argument is provided, the scriptlet is @@ -422,11 +423,12 @@ function objectPruneFn( extraArgs = {} ) { if ( typeof rawPrunePaths !== 'string' ) { return; } + const safe = safeSelf(); const prunePaths = rawPrunePaths !== '' - ? rawPrunePaths.split(/ +/) + ? safe.String_split.call(rawPrunePaths, / +/) : []; const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' - ? rawNeedlePaths.split(/ +/) + ? safe.String_split.call(rawNeedlePaths, / +/) : []; if ( stackNeedleDetails.matchAll !== true ) { if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) { @@ -547,7 +549,7 @@ function matchesStackTrace( // Normalize stack trace const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/; const lines = []; - for ( let line of error.stack.split(/[\n\r]+/) ) { + for ( let line of safe.String_split.call(error.stack, /[\n\r]+/) ) { if ( line.includes(exceptionToken) ) { continue; } line = line.trim(); const match = safe.RegExp_exec.call(reLine, line); @@ -594,8 +596,8 @@ function parsePropertiesToMatch(propsToMatch, implicit = '') { const needles = new Map(); if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } const options = { canNegate: true }; - for ( const needle of propsToMatch.split(/\s+/) ) { - const [ prop, pattern ] = needle.split(':'); + for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { + const [ prop, pattern ] = safe.String_split.call(needle, ':'); if ( prop === '' ) { continue; } if ( pattern !== undefined ) { needles.set(prop, safe.initPattern(pattern, options)); @@ -1643,7 +1645,7 @@ function noFetchIf( const safe = safeSelf(); const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType); const needles = []; - for ( const condition of propsToMatch.split(/\s+/) ) { + for ( const condition of safe.String_split.call(propsToMatch, /\s+/) ) { if ( condition === '' ) { continue; } const pos = condition.indexOf(':'); let key, value; @@ -1797,7 +1799,7 @@ function removeClass( if ( rawToken === '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior); - const tokens = rawToken.split(/\s*\|\s*/); + const tokens = safe.String_split.call(rawToken, /\s*\|\s*/); const selector = tokens .map(a => `${rawSelector}.${CSS.escape(a)}`) .join(','); @@ -2510,12 +2512,12 @@ function m3uPrune( } text = before.trim() + '\n' + after.trim(); reM3u.lastIndex = before.length + 1; - toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`)); + toLog.push('Discarding', ...safe.String_split.call(discard, /\n+/).map(s => `\t${s}`)); if ( reM3u.global === false ) { break; } } return text; } - const lines = text.split(/\n\r|\n|\r/); + const lines = safe.String_split.call(text, /\n\r|\n|\r/); for ( let i = 0; i < lines.length; i++ ) { if ( lines[i] === undefined ) { continue; } if ( pruneSpliceoutBlock(lines, i) ) { continue; } @@ -2758,13 +2760,17 @@ function hrefSanitizer( builtinScriptlets.push({ name: 'call-nothrow.js', fn: callNothrow, + dependencies: [ + 'safe-self.fn', + ], }); function callNothrow( chain = '' ) { if ( typeof chain !== 'string' ) { return; } if ( chain === '' ) { return; } - const parts = chain.split('.'); + const safe = safeSelf(); + const parts = safe.String_split.call(chain, '.'); let owner = window, prop; for (;;) { prop = parts.shift(); @@ -3095,7 +3101,7 @@ function trustedClickElement( const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay); if ( extraMatch !== '' ) { - const assertions = extraMatch.split(',').map(s => { + const assertions = safe.String_split.call(extraMatch, ',').map(s => { const pos1 = s.indexOf(':'); const s1 = pos1 !== -1 ? s.slice(0, pos1) : s; const not = s1.startsWith('!'); @@ -3163,7 +3169,7 @@ function trustedClickElement( return shadowRoot && querySelectorEx(inside, shadowRoot); }; - const selectorList = selectors.split(/\s*,\s*/) + const selectorList = safe.String_split.call(selectors, /\s*,\s*/) .filter(s => { try { void querySelectorEx(s); @@ -3290,10 +3296,10 @@ function trustedPruneInboundObject( const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); const needlePaths = []; if ( rawPrunePaths !== '' ) { - needlePaths.push(...rawPrunePaths.split(/ +/)); + needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/)); } if ( rawNeedlePaths !== '' ) { - needlePaths.push(...rawNeedlePaths.split(/ +/)); + needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/)); } const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); const mustProcess = root => { @@ -3455,7 +3461,7 @@ function trustedSuppressNativeMethod( if ( stack !== '' ) { return; } const safe = safeSelf(); const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how); - const signatureArgs = signature.split(/\s*\|\s*/).map(v => { + const signatureArgs = safe.String_split.call(signature, /\s*\|\s*/).map(v => { if ( /^".*"$/.test(v) ) { return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) }; } diff --git a/src/js/resources/set-constant.js b/src/js/resources/set-constant.js index 127f27bbb..66584fb81 100644 --- a/src/js/resources/set-constant.js +++ b/src/js/resources/set-constant.js @@ -61,6 +61,7 @@ export function validateConstantFn(trusted, raw, extraArgs = {}) { } else if ( raw.startsWith('{') && raw.endsWith('}') ) { try { value = safe.JSON_parse(raw).value; } catch(ex) { return; } } + return raw; } else { return; }