summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js441
1 files changed, 441 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js b/data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js
new file mode 100644
index 0000000..21300bc
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/resources/cookie.js
@@ -0,0 +1,441 @@
+/*******************************************************************************
+
+ 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
+
+*/
+
+import { registerScriptlet } from './base.js';
+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',
+ 'all', 'none', 'functional',
+ 'granted', 'done',
+ 'decline', 'declined',
+ 'closed', 'next', 'mandatory',
+ 'disagree', 'agree',
+ ];
+}
+registerScriptlet(getSafeCookieValuesFn, {
+ name: 'get-safe-cookie-values.fn',
+});
+
+/******************************************************************************/
+
+export function getAllCookiesFn() {
+ const safe = safeSelf();
+ return safe.String_split.call(document.cookie, /\s*;\s*/).map(s => {
+ const pos = s.indexOf('=');
+ if ( pos === 0 ) { return; }
+ if ( pos === -1 ) { return `${s.trim()}=`; }
+ const key = s.slice(0, pos).trim();
+ const value = s.slice(pos+1).trim();
+ return { key, value };
+ }).filter(s => s !== undefined);
+}
+registerScriptlet(getAllCookiesFn, {
+ name: 'get-all-cookies.fn',
+ dependencies: [
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+export function getCookieFn(
+ name = ''
+) {
+ const safe = safeSelf();
+ for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) {
+ const pos = s.indexOf('=');
+ if ( pos === -1 ) { continue; }
+ if ( s.slice(0, pos) !== name ) { continue; }
+ return s.slice(pos+1).trim();
+ }
+}
+registerScriptlet(getCookieFn, {
+ name: 'get-cookie.fn',
+ dependencies: [
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/
+
+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 ) {
+ let domain = options.domain;
+ if ( /^\/.+\//.test(domain) ) {
+ const baseURL = new URL(document.baseURI);
+ const reDomain = new RegExp(domain.slice(1, -1));
+ const match = reDomain.exec(baseURL.hostname);
+ domain = match ? match[0] : undefined;
+ }
+ if ( domain ) {
+ cookieParts.push(`; domain=${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;
+}
+registerScriptlet(setCookieFn, {
+ name: 'set-cookie.fn',
+ dependencies: [
+ getCookieFn,
+ ],
+});
+
+/**
+ * @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');
+ }
+}
+registerScriptlet(setCookie, {
+ name: 'set-cookie.js',
+ world: 'ISOLATED',
+ dependencies: [
+ getSafeCookieValuesFn,
+ safeSelf,
+ setCookieFn,
+ ],
+});
+
+// For compatibility with AdGuard
+export function setCookieReload(name, value, path, ...args) {
+ setCookie(name, value, path, 'reload', '1', ...args);
+}
+registerScriptlet(setCookieReload, {
+ name: 'set-cookie-reload.js',
+ world: 'ISOLATED',
+ dependencies: [
+ setCookie,
+ ],
+});
+
+/**
+ * @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');
+ }
+}
+registerScriptlet(trustedSetCookie, {
+ name: 'trusted-set-cookie.js',
+ requiresTrust: true,
+ world: 'ISOLATED',
+ dependencies: [
+ safeSelf,
+ setCookieFn,
+ ],
+});
+
+// For compatibility with AdGuard
+export function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) {
+ trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args);
+}
+registerScriptlet(trustedSetCookieReload, {
+ name: 'trusted-set-cookie-reload.js',
+ requiresTrust: true,
+ world: 'ISOLATED',
+ dependencies: [
+ trustedSetCookie,
+ ],
+});
+
+/**
+ * @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 baseURL = new URL(document.baseURI);
+ let targetDomain = extraArgs.domain;
+ if ( targetDomain && /^\/.+\//.test(targetDomain) ) {
+ const reDomain = new RegExp(targetDomain.slice(1, -1));
+ const match = reDomain.exec(baseURL.hostname);
+ targetDomain = match ? match[0] : undefined;
+ }
+ const remove = ( ) => {
+ safe.String_split.call(document.cookie, ';').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=${baseURL.hostname}`;
+ const part2b = `; domain=.${baseURL.hostname}`;
+ let part2c, part2d;
+ if ( targetDomain ) {
+ part2c = `; domain=${targetDomain}`;
+ part2d = `; domain=.${targetDomain}`;
+ } else if ( document.domain ) {
+ const domain = document.domain;
+ if ( domain !== baseURL.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 = safe.String_split.call(extraArgs.when, /\s/);
+ for ( const type of eventTypes ) {
+ if ( supportedEventTypes.includes(type) === false ) { continue; }
+ document.addEventListener(type, ( ) => {
+ throttle(remove);
+ }, { passive: true });
+ }
+}
+registerScriptlet(removeCookie, {
+ name: 'remove-cookie.js',
+ aliases: [
+ 'cookie-remover.js',
+ ],
+ world: 'ISOLATED',
+ dependencies: [
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/