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