diff --git a/assets/resources/set-attr.js b/assets/resources/attribute.js similarity index 62% rename from assets/resources/set-attr.js rename to assets/resources/attribute.js index cb90b51fa..511c8eafa 100644 --- a/assets/resources/set-attr.js +++ b/assets/resources/attribute.js @@ -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, + ], +}; + /******************************************************************************/ diff --git a/assets/resources/cookie.js b/assets/resources/cookie.js new file mode 100644 index 000000000..6d344ad04 --- /dev/null +++ b/assets/resources/cookie.js @@ -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', + ], +}; + +/******************************************************************************/ diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index ba0882491..8b70e48a5 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -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