1
0
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:
Raymond Hill 2024-11-07 10:23:34 -05:00
parent 652f178787
commit e613282698
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
3 changed files with 545 additions and 441 deletions

View File

@ -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
View 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',
],
};
/******************************************************************************/

View File

@ -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