mirror of
https://github.com/gorhill/uBlock.git
synced 2025-01-31 20:21:35 +01:00
Improve set-cookie
scriptlet
Allow negative integer as valid value. Related issue: https://github.com/gorhill/uBlock/pull/3927 Additionally, move cookie-related scriptlets/helpers into its own source code file.
This commit is contained in:
parent
652f178787
commit
e613282698
@ -198,4 +198,108 @@ trustedSetAttr.details = {
|
||||
world: 'ISOLATED',
|
||||
};
|
||||
|
||||
/**
|
||||
* @scriptlet remove-attr
|
||||
*
|
||||
* @description
|
||||
* Remove one or more attributes from a set of elements.
|
||||
*
|
||||
* @param attribute
|
||||
* The name of the attribute(s) to remove. This can be a list of space-
|
||||
* separated attribute names.
|
||||
*
|
||||
* @param [selector]
|
||||
* Optional. A CSS selector for the elements to target. Default to
|
||||
* `[attribute]`, or `[attribute1],[attribute2],...` if more than one
|
||||
* attribute name is specified.
|
||||
*
|
||||
* @param [behavior]
|
||||
* Optional. Space-separated tokens which modify the default behavior.
|
||||
* - `asap`: Try to remove the attribute as soon as possible. Default behavior
|
||||
* is to remove the attribute(s) asynchronously.
|
||||
* - `stay`: Keep trying to remove the specified attribute(s) on DOM mutations.
|
||||
* */
|
||||
|
||||
export function removeAttr(
|
||||
rawToken = '',
|
||||
rawSelector = '',
|
||||
behavior = ''
|
||||
) {
|
||||
if ( typeof rawToken !== 'string' ) { return; }
|
||||
if ( rawToken === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
|
||||
const tokens = rawToken.split(/\s*\|\s*/);
|
||||
const selector = tokens
|
||||
.map(a => `${rawSelector}[${CSS.escape(a)}]`)
|
||||
.join(',');
|
||||
if ( safe.logLevel > 1 ) {
|
||||
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
|
||||
}
|
||||
const asap = /\basap\b/.test(behavior);
|
||||
let timerId;
|
||||
const rmattrAsync = ( ) => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
timerId = safe.onIdle(( ) => {
|
||||
timerId = undefined;
|
||||
rmattr();
|
||||
}, { timeout: 17 });
|
||||
};
|
||||
const rmattr = ( ) => {
|
||||
if ( timerId !== undefined ) {
|
||||
safe.offIdle(timerId);
|
||||
timerId = undefined;
|
||||
}
|
||||
try {
|
||||
const nodes = document.querySelectorAll(selector);
|
||||
for ( const node of nodes ) {
|
||||
for ( const attr of tokens ) {
|
||||
if ( node.hasAttribute(attr) === false ) { continue; }
|
||||
node.removeAttribute(attr);
|
||||
safe.uboLog(logPrefix, `Removed attribute '${attr}'`);
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
};
|
||||
const mutationHandler = mutations => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
let skip = true;
|
||||
for ( let i = 0; i < mutations.length && skip; i++ ) {
|
||||
const { type, addedNodes, removedNodes } = mutations[i];
|
||||
if ( type === 'attributes' ) { skip = false; }
|
||||
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
|
||||
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
|
||||
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
}
|
||||
if ( skip ) { return; }
|
||||
asap ? rmattr() : rmattrAsync();
|
||||
};
|
||||
const start = ( ) => {
|
||||
rmattr();
|
||||
if ( /\bstay\b/.test(behavior) === false ) { return; }
|
||||
const observer = new MutationObserver(mutationHandler);
|
||||
observer.observe(document, {
|
||||
attributes: true,
|
||||
attributeFilter: tokens,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
runAt(( ) => { start(); }, behavior.split(/\s+/));
|
||||
}
|
||||
removeAttr.details = {
|
||||
name: 'remove-attr.js',
|
||||
aliases: [
|
||||
'ra.js',
|
||||
],
|
||||
dependencies: [
|
||||
runAt,
|
||||
safeSelf,
|
||||
],
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
410
assets/resources/cookie.js
Normal file
410
assets/resources/cookie.js
Normal file
@ -0,0 +1,410 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
Copyright (C) 2019-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
|
||||
The scriptlets below are meant to be injected only into a
|
||||
web page context.
|
||||
*/
|
||||
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getSafeCookieValuesFn() {
|
||||
return [
|
||||
'accept', 'reject',
|
||||
'accepted', 'rejected', 'notaccepted',
|
||||
'allow', 'disallow', 'deny',
|
||||
'allowed', 'denied',
|
||||
'approved', 'disapproved',
|
||||
'checked', 'unchecked',
|
||||
'dismiss', 'dismissed',
|
||||
'enable', 'disable',
|
||||
'enabled', 'disabled',
|
||||
'essential', 'nonessential',
|
||||
'forbidden', 'forever',
|
||||
'hide', 'hidden',
|
||||
'necessary', 'required',
|
||||
'ok',
|
||||
'on', 'off',
|
||||
'true', 't', 'false', 'f',
|
||||
'yes', 'y', 'no', 'n',
|
||||
];
|
||||
}
|
||||
getSafeCookieValuesFn.details = {
|
||||
name: 'get-safe-cookie-values.fn',
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getAllCookiesFn() {
|
||||
return document.cookie.split(/\s*;\s*/).map(s => {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === 0 ) { return; }
|
||||
if ( pos === -1 ) { return `${s.trim()}=`; }
|
||||
const key = s.slice(0, pos).trim();
|
||||
const value = s.slice(pos+1).trim();
|
||||
return { key, value };
|
||||
}).filter(s => s !== undefined);
|
||||
}
|
||||
getAllCookiesFn.details = {
|
||||
name: 'get-all-cookies.fn',
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getCookieFn(
|
||||
name = ''
|
||||
) {
|
||||
for ( const s of document.cookie.split(/\s*;\s*/) ) {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === -1 ) { continue; }
|
||||
if ( s.slice(0, pos) !== name ) { continue; }
|
||||
return s.slice(pos+1).trim();
|
||||
}
|
||||
}
|
||||
getCookieFn.details = {
|
||||
name: 'get-cookie.fn',
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function setCookieFn(
|
||||
trusted = false,
|
||||
name = '',
|
||||
value = '',
|
||||
expires = '',
|
||||
path = '',
|
||||
options = {},
|
||||
) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/2777
|
||||
if ( trusted === false && /[^!#$%&'*+\-.0-9A-Z[\]^_`a-z|~]/.test(name) ) {
|
||||
name = encodeURIComponent(name);
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
|
||||
// The characters [",] are given a pass from the RFC requirements because
|
||||
// apparently browsers do not follow the RFC to the letter.
|
||||
if ( /[^ -:<-[\]-~]/.test(value) ) {
|
||||
value = encodeURIComponent(value);
|
||||
}
|
||||
|
||||
const cookieBefore = getCookieFn(name);
|
||||
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
|
||||
if ( cookieBefore === value && options.reload ) { return; }
|
||||
|
||||
const cookieParts = [ name, '=', value ];
|
||||
if ( expires !== '' ) {
|
||||
cookieParts.push('; expires=', expires);
|
||||
}
|
||||
|
||||
if ( path === '' ) { path = '/'; }
|
||||
else if ( path === 'none' ) { path = ''; }
|
||||
if ( path !== '' && path !== '/' ) { return; }
|
||||
if ( path === '/' ) {
|
||||
cookieParts.push('; path=/');
|
||||
}
|
||||
|
||||
if ( trusted ) {
|
||||
if ( options.domain ) {
|
||||
cookieParts.push(`; domain=${options.domain}`);
|
||||
}
|
||||
cookieParts.push('; Secure');
|
||||
} else if ( /^__(Host|Secure)-/.test(name) ) {
|
||||
cookieParts.push('; Secure');
|
||||
}
|
||||
|
||||
try {
|
||||
document.cookie = cookieParts.join('');
|
||||
} catch(_) {
|
||||
}
|
||||
|
||||
const done = getCookieFn(name) === value;
|
||||
if ( done && options.reload ) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
setCookieFn.details = {
|
||||
name: 'set-cookie.fn',
|
||||
dependencies: [
|
||||
'get-cookie.fn',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @scriptlet set-cookie
|
||||
*
|
||||
* @description
|
||||
* Set a cookie to a safe value.
|
||||
*
|
||||
* @param name
|
||||
* The name of the cookie to set.
|
||||
*
|
||||
* @param value
|
||||
* The value of the cookie to set. Must be a safe value. Unsafe values will be
|
||||
* ignored and no cookie will be set. See getSafeCookieValuesFn() helper above.
|
||||
*
|
||||
* @param [path]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
||||
* */
|
||||
|
||||
export function setCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const normalized = value.toLowerCase();
|
||||
const match = /^("?)(.+)\1$/.exec(normalized);
|
||||
const unquoted = match && match[2] || normalized;
|
||||
const validValues = getSafeCookieValuesFn();
|
||||
if ( validValues.includes(unquoted) === false ) {
|
||||
if ( /^-?\d+$/.test(unquoted) === false ) { return; }
|
||||
const n = parseInt(value, 10) || 0;
|
||||
if ( n < -32767 || n > 32767 ) { return; }
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
false,
|
||||
name,
|
||||
value,
|
||||
'',
|
||||
path,
|
||||
safe.getExtraArgs(Array.from(arguments), 3)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
setCookie.details = {
|
||||
name: 'set-cookie.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'get-safe-cookie-values.fn',
|
||||
'safe-self.fn',
|
||||
'set-cookie.fn',
|
||||
],
|
||||
};
|
||||
|
||||
// For compatibility with AdGuard
|
||||
export function setCookieReload(name, value, path, ...args) {
|
||||
setCookie(name, value, path, 'reload', '1', ...args);
|
||||
}
|
||||
setCookieReload.details = {
|
||||
name: 'set-cookie-reload.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'set-cookie.js',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @trustedScriptlet trusted-set-cookie
|
||||
*
|
||||
* @description
|
||||
* Set a cookie to any value. This scriptlet can be used only from a trusted
|
||||
* source.
|
||||
*
|
||||
* @param name
|
||||
* The name of the cookie to set.
|
||||
*
|
||||
* @param value
|
||||
* The value of the cookie to set. Must be a safe value. Unsafe values will be
|
||||
* ignored and no cookie will be set. See getSafeCookieValuesFn() helper above.
|
||||
*
|
||||
* @param [offsetExpiresSec]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* @param [path]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
||||
* */
|
||||
|
||||
export function trustedSetCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
offsetExpiresSec = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const time = new Date();
|
||||
|
||||
if ( value.includes('$now$') ) {
|
||||
value = value.replaceAll('$now$', time.getTime());
|
||||
}
|
||||
if ( value.includes('$currentDate$') ) {
|
||||
value = value.replaceAll('$currentDate$', time.toUTCString());
|
||||
}
|
||||
if ( value.includes('$currentISODate$') ) {
|
||||
value = value.replaceAll('$currentISODate$', time.toISOString());
|
||||
}
|
||||
|
||||
let expires = '';
|
||||
if ( offsetExpiresSec !== '' ) {
|
||||
if ( offsetExpiresSec === '1day' ) {
|
||||
time.setDate(time.getDate() + 1);
|
||||
} else if ( offsetExpiresSec === '1year' ) {
|
||||
time.setFullYear(time.getFullYear() + 1);
|
||||
} else {
|
||||
if ( /^\d+$/.test(offsetExpiresSec) === false ) { return; }
|
||||
time.setSeconds(time.getSeconds() + parseInt(offsetExpiresSec, 10));
|
||||
}
|
||||
expires = time.toUTCString();
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
true,
|
||||
name,
|
||||
value,
|
||||
expires,
|
||||
path,
|
||||
safeSelf().getExtraArgs(Array.from(arguments), 4)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
trustedSetCookie.details = {
|
||||
name: 'trusted-set-cookie.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'safe-self.fn',
|
||||
'set-cookie.fn',
|
||||
],
|
||||
};
|
||||
|
||||
// For compatibility with AdGuard
|
||||
export function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) {
|
||||
trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args);
|
||||
}
|
||||
trustedSetCookieReload.details = {
|
||||
name: 'trusted-set-cookie-reload.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'trusted-set-cookie.js',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @scriptlet remove-cookie
|
||||
*
|
||||
* @description
|
||||
* Removes current site cookies specified by name. The removal operation occurs
|
||||
* immediately when the scriptlet is injected, then when the page is unloaded.
|
||||
*
|
||||
* @param needle
|
||||
* A string or a regex matching the name of the cookie(s) to remove.
|
||||
*
|
||||
* @param ['when', token]
|
||||
* Vararg, optional. The parameter following 'when' tells when extra removal
|
||||
* operations should take place.
|
||||
* - `scroll`: when the page is scrolled
|
||||
* - `keydown`: when a keyboard touch is pressed
|
||||
*
|
||||
* */
|
||||
|
||||
export function removeCookie(
|
||||
needle = ''
|
||||
) {
|
||||
if ( typeof needle !== 'string' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const reName = safe.patternToRegex(needle);
|
||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 1);
|
||||
const throttle = (fn, ms = 500) => {
|
||||
if ( throttle.timer !== undefined ) { return; }
|
||||
throttle.timer = setTimeout(( ) => {
|
||||
throttle.timer = undefined;
|
||||
fn();
|
||||
}, ms);
|
||||
};
|
||||
const remove = ( ) => {
|
||||
document.cookie.split(';').forEach(cookieStr => {
|
||||
const pos = cookieStr.indexOf('=');
|
||||
if ( pos === -1 ) { return; }
|
||||
const cookieName = cookieStr.slice(0, pos).trim();
|
||||
if ( reName.test(cookieName) === false ) { return; }
|
||||
const part1 = cookieName + '=';
|
||||
const part2a = '; domain=' + document.location.hostname;
|
||||
const part2b = '; domain=.' + document.location.hostname;
|
||||
let part2c, part2d;
|
||||
const domain = document.domain;
|
||||
if ( domain ) {
|
||||
if ( domain !== document.location.hostname ) {
|
||||
part2c = '; domain=.' + domain;
|
||||
}
|
||||
if ( domain.startsWith('www.') ) {
|
||||
part2d = '; domain=' + domain.replace('www', '');
|
||||
}
|
||||
}
|
||||
const part3 = '; path=/';
|
||||
const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
document.cookie = part1 + part4;
|
||||
document.cookie = part1 + part2a + part4;
|
||||
document.cookie = part1 + part2b + part4;
|
||||
document.cookie = part1 + part3 + part4;
|
||||
document.cookie = part1 + part2a + part3 + part4;
|
||||
document.cookie = part1 + part2b + part3 + part4;
|
||||
if ( part2c !== undefined ) {
|
||||
document.cookie = part1 + part2c + part3 + part4;
|
||||
}
|
||||
if ( part2d !== undefined ) {
|
||||
document.cookie = part1 + part2d + part3 + part4;
|
||||
}
|
||||
});
|
||||
};
|
||||
remove();
|
||||
window.addEventListener('beforeunload', remove);
|
||||
if ( typeof extraArgs.when !== 'string' ) { return; }
|
||||
const supportedEventTypes = [ 'scroll', 'keydown' ];
|
||||
const eventTypes = extraArgs.when.split(/\s/);
|
||||
for ( const type of eventTypes ) {
|
||||
if ( supportedEventTypes.includes(type) === false ) { continue; }
|
||||
document.addEventListener(type, ( ) => {
|
||||
throttle(remove);
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
||||
removeCookie.details = {
|
||||
name: 'remove-cookie.js',
|
||||
aliases: [
|
||||
'cookie-remover.js',
|
||||
],
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'safe-self.fn',
|
||||
],
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
@ -22,7 +22,25 @@
|
||||
web page context.
|
||||
*/
|
||||
|
||||
import { setAttr, setAttrFn, trustedSetAttr } from './set-attr.js';
|
||||
import {
|
||||
getAllCookiesFn,
|
||||
getCookieFn,
|
||||
getSafeCookieValuesFn,
|
||||
removeCookie,
|
||||
setCookie,
|
||||
setCookieFn,
|
||||
setCookieReload,
|
||||
trustedSetCookie,
|
||||
trustedSetCookieReload,
|
||||
} from './cookie.js';
|
||||
|
||||
import {
|
||||
removeAttr,
|
||||
setAttr,
|
||||
setAttrFn,
|
||||
trustedSetAttr,
|
||||
} from './attribute.js';
|
||||
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
@ -53,10 +71,22 @@ const registerScriptlet = fn => {
|
||||
};
|
||||
|
||||
registerScriptlet(safeSelf);
|
||||
|
||||
registerScriptlet(removeAttr);
|
||||
registerScriptlet(setAttrFn);
|
||||
registerScriptlet(setAttr);
|
||||
registerScriptlet(trustedSetAttr);
|
||||
|
||||
registerScriptlet(getAllCookiesFn);
|
||||
registerScriptlet(getCookieFn);
|
||||
registerScriptlet(getSafeCookieValuesFn);
|
||||
registerScriptlet(removeCookie);
|
||||
registerScriptlet(setCookie);
|
||||
registerScriptlet(setCookieFn);
|
||||
registerScriptlet(setCookieReload);
|
||||
registerScriptlet(trustedSetCookie);
|
||||
registerScriptlet(trustedSetCookieReload);
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
Helper functions
|
||||
@ -793,51 +823,6 @@ function objectFindOwnerFn(
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'get-safe-cookie-values.fn',
|
||||
fn: getSafeCookieValuesFn,
|
||||
});
|
||||
function getSafeCookieValuesFn() {
|
||||
return [
|
||||
'accept', 'reject',
|
||||
'accepted', 'rejected', 'notaccepted',
|
||||
'allow', 'disallow', 'deny',
|
||||
'allowed', 'denied',
|
||||
'approved', 'disapproved',
|
||||
'checked', 'unchecked',
|
||||
'dismiss', 'dismissed',
|
||||
'enable', 'disable',
|
||||
'enabled', 'disabled',
|
||||
'essential', 'nonessential',
|
||||
'forbidden', 'forever',
|
||||
'hide', 'hidden',
|
||||
'necessary', 'required',
|
||||
'ok',
|
||||
'on', 'off',
|
||||
'true', 't', 'false', 'f',
|
||||
'yes', 'y', 'no', 'n',
|
||||
];
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'get-all-cookies.fn',
|
||||
fn: getAllCookiesFn,
|
||||
});
|
||||
function getAllCookiesFn() {
|
||||
return document.cookie.split(/\s*;\s*/).map(s => {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === 0 ) { return; }
|
||||
if ( pos === -1 ) { return `${s.trim()}=`; }
|
||||
const key = s.slice(0, pos).trim();
|
||||
const value = s.slice(pos+1).trim();
|
||||
return { key, value };
|
||||
}).filter(s => s !== undefined);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'get-all-local-storage.fn',
|
||||
fn: getAllLocalStorageFn,
|
||||
@ -855,90 +840,6 @@ function getAllLocalStorageFn(which = 'localStorage') {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'get-cookie.fn',
|
||||
fn: getCookieFn,
|
||||
});
|
||||
function getCookieFn(
|
||||
name = ''
|
||||
) {
|
||||
for ( const s of document.cookie.split(/\s*;\s*/) ) {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === -1 ) { continue; }
|
||||
if ( s.slice(0, pos) !== name ) { continue; }
|
||||
return s.slice(pos+1).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'set-cookie.fn',
|
||||
fn: setCookieFn,
|
||||
dependencies: [
|
||||
'get-cookie.fn',
|
||||
],
|
||||
});
|
||||
function setCookieFn(
|
||||
trusted = false,
|
||||
name = '',
|
||||
value = '',
|
||||
expires = '',
|
||||
path = '',
|
||||
options = {},
|
||||
) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/2777
|
||||
if ( trusted === false && /[^!#$%&'*+\-.0-9A-Z[\]^_`a-z|~]/.test(name) ) {
|
||||
name = encodeURIComponent(name);
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
|
||||
// The characters [",] are given a pass from the RFC requirements because
|
||||
// apparently browsers do not follow the RFC to the letter.
|
||||
if ( /[^ -:<-[\]-~]/.test(value) ) {
|
||||
value = encodeURIComponent(value);
|
||||
}
|
||||
|
||||
const cookieBefore = getCookieFn(name);
|
||||
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
|
||||
if ( cookieBefore === value && options.reload ) { return; }
|
||||
|
||||
const cookieParts = [ name, '=', value ];
|
||||
if ( expires !== '' ) {
|
||||
cookieParts.push('; expires=', expires);
|
||||
}
|
||||
|
||||
if ( path === '' ) { path = '/'; }
|
||||
else if ( path === 'none' ) { path = ''; }
|
||||
if ( path !== '' && path !== '/' ) { return; }
|
||||
if ( path === '/' ) {
|
||||
cookieParts.push('; path=/');
|
||||
}
|
||||
|
||||
if ( trusted ) {
|
||||
if ( options.domain ) {
|
||||
cookieParts.push(`; domain=${options.domain}`);
|
||||
}
|
||||
cookieParts.push('; Secure');
|
||||
} else if ( /^__(Host|Secure)-/.test(name) ) {
|
||||
cookieParts.push('; Secure');
|
||||
}
|
||||
|
||||
try {
|
||||
document.cookie = cookieParts.join('');
|
||||
} catch(_) {
|
||||
}
|
||||
|
||||
const done = getCookieFn(name) === value;
|
||||
if ( done && options.reload ) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'set-local-storage-item.fn',
|
||||
fn: setLocalStorageItemFn,
|
||||
@ -2347,91 +2248,6 @@ function preventRefresh(
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'remove-attr.js',
|
||||
aliases: [
|
||||
'ra.js',
|
||||
],
|
||||
fn: removeAttr,
|
||||
dependencies: [
|
||||
'run-at.fn',
|
||||
'safe-self.fn',
|
||||
],
|
||||
});
|
||||
function removeAttr(
|
||||
rawToken = '',
|
||||
rawSelector = '',
|
||||
behavior = ''
|
||||
) {
|
||||
if ( typeof rawToken !== 'string' ) { return; }
|
||||
if ( rawToken === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
|
||||
const tokens = rawToken.split(/\s*\|\s*/);
|
||||
const selector = tokens
|
||||
.map(a => `${rawSelector}[${CSS.escape(a)}]`)
|
||||
.join(',');
|
||||
if ( safe.logLevel > 1 ) {
|
||||
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
|
||||
}
|
||||
const asap = /\basap\b/.test(behavior);
|
||||
let timerId;
|
||||
const rmattrAsync = ( ) => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
timerId = safe.onIdle(( ) => {
|
||||
timerId = undefined;
|
||||
rmattr();
|
||||
}, { timeout: 17 });
|
||||
};
|
||||
const rmattr = ( ) => {
|
||||
if ( timerId !== undefined ) {
|
||||
safe.offIdle(timerId);
|
||||
timerId = undefined;
|
||||
}
|
||||
try {
|
||||
const nodes = document.querySelectorAll(selector);
|
||||
for ( const node of nodes ) {
|
||||
for ( const attr of tokens ) {
|
||||
if ( node.hasAttribute(attr) === false ) { continue; }
|
||||
node.removeAttribute(attr);
|
||||
safe.uboLog(logPrefix, `Removed attribute '${attr}'`);
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
};
|
||||
const mutationHandler = mutations => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
let skip = true;
|
||||
for ( let i = 0; i < mutations.length && skip; i++ ) {
|
||||
const { type, addedNodes, removedNodes } = mutations[i];
|
||||
if ( type === 'attributes' ) { skip = false; }
|
||||
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
|
||||
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
|
||||
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
}
|
||||
if ( skip ) { return; }
|
||||
asap ? rmattr() : rmattrAsync();
|
||||
};
|
||||
const start = ( ) => {
|
||||
rmattr();
|
||||
if ( /\bstay\b/.test(behavior) === false ) { return; }
|
||||
const observer = new MutationObserver(mutationHandler);
|
||||
observer.observe(document, {
|
||||
attributes: true,
|
||||
attributeFilter: tokens,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
runAt(( ) => { start(); }, behavior.split(/\s+/));
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'remove-class.js',
|
||||
aliases: [
|
||||
@ -3090,82 +2906,6 @@ function disableNewtabLinks() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'remove-cookie.js',
|
||||
aliases: [
|
||||
'cookie-remover.js',
|
||||
],
|
||||
fn: cookieRemover,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'safe-self.fn',
|
||||
],
|
||||
});
|
||||
// https://github.com/NanoAdblocker/NanoFilters/issues/149
|
||||
function cookieRemover(
|
||||
needle = ''
|
||||
) {
|
||||
if ( typeof needle !== 'string' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const reName = safe.patternToRegex(needle);
|
||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 1);
|
||||
const throttle = (fn, ms = 500) => {
|
||||
if ( throttle.timer !== undefined ) { return; }
|
||||
throttle.timer = setTimeout(( ) => {
|
||||
throttle.timer = undefined;
|
||||
fn();
|
||||
}, ms);
|
||||
};
|
||||
const removeCookie = ( ) => {
|
||||
document.cookie.split(';').forEach(cookieStr => {
|
||||
const pos = cookieStr.indexOf('=');
|
||||
if ( pos === -1 ) { return; }
|
||||
const cookieName = cookieStr.slice(0, pos).trim();
|
||||
if ( reName.test(cookieName) === false ) { return; }
|
||||
const part1 = cookieName + '=';
|
||||
const part2a = '; domain=' + document.location.hostname;
|
||||
const part2b = '; domain=.' + document.location.hostname;
|
||||
let part2c, part2d;
|
||||
const domain = document.domain;
|
||||
if ( domain ) {
|
||||
if ( domain !== document.location.hostname ) {
|
||||
part2c = '; domain=.' + domain;
|
||||
}
|
||||
if ( domain.startsWith('www.') ) {
|
||||
part2d = '; domain=' + domain.replace('www', '');
|
||||
}
|
||||
}
|
||||
const part3 = '; path=/';
|
||||
const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
document.cookie = part1 + part4;
|
||||
document.cookie = part1 + part2a + part4;
|
||||
document.cookie = part1 + part2b + part4;
|
||||
document.cookie = part1 + part3 + part4;
|
||||
document.cookie = part1 + part2a + part3 + part4;
|
||||
document.cookie = part1 + part2b + part3 + part4;
|
||||
if ( part2c !== undefined ) {
|
||||
document.cookie = part1 + part2c + part3 + part4;
|
||||
}
|
||||
if ( part2d !== undefined ) {
|
||||
document.cookie = part1 + part2d + part3 + part4;
|
||||
}
|
||||
});
|
||||
};
|
||||
removeCookie();
|
||||
window.addEventListener('beforeunload', removeCookie);
|
||||
if ( typeof extraArgs.when !== 'string' ) { return; }
|
||||
const supportedEventTypes = [ 'scroll', 'keydown' ];
|
||||
const eventTypes = extraArgs.when.split(/\s/);
|
||||
for ( const type of eventTypes ) {
|
||||
if ( supportedEventTypes.includes(type) === false ) { continue; }
|
||||
document.addEventListener(type, ( ) => {
|
||||
throttle(removeCookie);
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'xml-prune.js',
|
||||
fn: xmlPrune,
|
||||
@ -3827,72 +3567,6 @@ function removeNodeText(
|
||||
replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* set-cookie.js
|
||||
*
|
||||
* Set specified cookie to a specific value.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
||||
*
|
||||
**/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'set-cookie.js',
|
||||
fn: setCookie,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'get-safe-cookie-values.fn',
|
||||
'safe-self.fn',
|
||||
'set-cookie.fn',
|
||||
],
|
||||
});
|
||||
function setCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const normalized = value.toLowerCase();
|
||||
const match = /^("?)(.+)\1$/.exec(normalized);
|
||||
const unquoted = match && match[2] || normalized;
|
||||
const validValues = getSafeCookieValuesFn();
|
||||
if ( validValues.includes(unquoted) === false ) {
|
||||
if ( /^\d+$/.test(unquoted) === false ) { return; }
|
||||
const n = parseInt(value, 10);
|
||||
if ( n > 32767 ) { return; }
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
false,
|
||||
name,
|
||||
value,
|
||||
'',
|
||||
path,
|
||||
safe.getExtraArgs(Array.from(arguments), 3)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
|
||||
// For compatibility with AdGuard
|
||||
builtinScriptlets.push({
|
||||
name: 'set-cookie-reload.js',
|
||||
fn: setCookieReload,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'set-cookie.js',
|
||||
],
|
||||
});
|
||||
function setCookieReload(name, value, path, ...args) {
|
||||
setCookie(name, value, path, 'reload', '1', ...args);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* set-local-storage-item.js
|
||||
@ -4155,90 +3829,6 @@ function trustedSetConstant(
|
||||
setConstantFn(true, ...args);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* trusted-set-cookie.js
|
||||
*
|
||||
* Set specified cookie to an arbitrary value.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-cookie.js#L23
|
||||
*
|
||||
**/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'trusted-set-cookie.js',
|
||||
requiresTrust: true,
|
||||
fn: trustedSetCookie,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'safe-self.fn',
|
||||
'set-cookie.fn',
|
||||
],
|
||||
});
|
||||
function trustedSetCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
offsetExpiresSec = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const time = new Date();
|
||||
|
||||
if ( value.includes('$now$') ) {
|
||||
value = value.replaceAll('$now$', time.getTime());
|
||||
}
|
||||
if ( value.includes('$currentDate$') ) {
|
||||
value = value.replaceAll('$currentDate$', time.toUTCString());
|
||||
}
|
||||
if ( value.includes('$currentISODate$') ) {
|
||||
value = value.replaceAll('$currentISODate$', time.toISOString());
|
||||
}
|
||||
|
||||
let expires = '';
|
||||
if ( offsetExpiresSec !== '' ) {
|
||||
if ( offsetExpiresSec === '1day' ) {
|
||||
time.setDate(time.getDate() + 1);
|
||||
} else if ( offsetExpiresSec === '1year' ) {
|
||||
time.setFullYear(time.getFullYear() + 1);
|
||||
} else {
|
||||
if ( /^\d+$/.test(offsetExpiresSec) === false ) { return; }
|
||||
time.setSeconds(time.getSeconds() + parseInt(offsetExpiresSec, 10));
|
||||
}
|
||||
expires = time.toUTCString();
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
true,
|
||||
name,
|
||||
value,
|
||||
expires,
|
||||
path,
|
||||
safeSelf().getExtraArgs(Array.from(arguments), 4)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
|
||||
// For compatibility with AdGuard
|
||||
builtinScriptlets.push({
|
||||
name: 'trusted-set-cookie-reload.js',
|
||||
requiresTrust: true,
|
||||
fn: trustedSetCookieReload,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'trusted-set-cookie.js',
|
||||
],
|
||||
});
|
||||
function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) {
|
||||
trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* trusted-set-local-storage-item.js
|
||||
|
Loading…
x
Reference in New Issue
Block a user