author Santo Pfingsten
 *  see https import { BrowsingData , Cookies , browser } from "webextension-polyfill-ts" ; import { Cleaner } from "./cleaner" ; import { getAllCookieStoreIds , getFirstPartyCookieDomain } from "../backgroundHelpers" ; import { settings } from "../../lib/settings" ; import DelayedExecution from "../../lib/delayedExecution" ; import { TabWatcher } from "../tabWatcher" ; import { CleanupType } from "../../lib/settingsSignature" ; import { getDomain } from "tldjs" ; import { isNodeTest , isFirefox , browserInfo } from "../../lib/browserInfo" ; import { messageUtil } from "../../lib/messageUtil" ; import { IncognitoWatcher } from "../incognitoWatcher" ; const supportsFirstPartyIsolation = isNodeTest || isFirefox && browserInfo . versionAsNumber >= 59 ; function getCookieRemovalInfo ( cookie ) { if ( cookie . domain . length === 0 ) { return { url : `file://${ cookie . path }` , removedFrom : cookie . path } ; } const allowSubDomains = cookie . domain . startsWith ( "." ) ; const rawDomain = allowSubDomains ? cookie . domain . substr ( 1 ) : cookie . domain ; return { url : ( cookie . secure ? "https://" : "http://" ) + rawDomain + cookie . path , removedFrom : rawDomain } ; } export function removeCookie ( cookie ) { const removalInfo = getCookieRemovalInfo ( cookie ) ; const details = { name : cookie . name , url : removalInfo . url , storeId : cookie . storeId } ; if ( supportsFirstPartyIsolation ) details . firstPartyDomain = cookie . firstPartyDomain ; const promise = browser . cookies . remove ( details ) ; messageUtil . sendSelf ( "cookieRemoved" , removalInfo . removedFrom ) ; return promise ; } export class CookieCleaner extends Cleaner { private readonly snoozedThirdpartyCookies : Cookies . Cookie [ ] = [ ] ; private readonly snoozedInstantlyCookies : Cookies . Cookie [ ] = [ ] ; private readonly tabWatcher : TabWatcher ; private readonly incognitoWatcher : IncognitoWatcher ; public constructor ( tabWatcher , incognitoWatcher ) { super ( ) ; this . tabWatcher = tabWatcher ; this . incognitoWatcher = incognitoWatcher ; browser . cookies . onChanged . addListener ( this . onCookieChanged . bind ( this ) ) ; } public clean ( typeSet , startup ) { if ( typeSet . cookies && settings . get ( startup ? "startup.cookies.applyRules" : "cleanAll.cookies.applyRules" ) ) { typeSet . cookies = false ; const protectOpenDomains = startup || settings . get ( "cleanAll.protectOpenDomains" ) ; this . cleanCookiesWithRulesNow ( startup , protectOpenDomains ) ; } } public cleanDomainOnLeave ( storeId , domain ) { if ( settings . get ( "domainLeave.enabled" ) ) { if ( settings . get ( "domainLeave.cookies" ) ) this . cleanDomainInternal ( storeId , domain , false ) ; } } public cleanDomain ( storeId , domain ) { this . cleanDomainInternal ( storeId , domain , true ) ; } private cleanDomainInternal ( storeId , domain , ignoreRules ) { const domainFP = getDomain ( domain ) || domain ; this . removeCookies ( storeId , ( cookie ) => { if ( this . shouldPurgeExpiredCookie ( cookie ) ) return true ; const match = cookie . firstPartyDomain ? cookie . firstPartyDomain === domainFP : getFirstPartyCookieDomain ( cookie . domain ) === domainFP ; return match && ( ignoreRules || ! this . isCookieAllowed ( cookie , false , true , true ) ) ; } ) ; } public setSnoozing ( snoozing ) { super . setSnoozing ( snoozing ) ; if ( ! snoozing ) { for ( const cookie of this . snoozedThirdpartyCookies ) this . removeCookieIfThirdparty ( cookie ) ; this . snoozedThirdpartyCookies . length = 0 ; for ( const cookie of this . snoozedInstantlyCookies ) { if ( this . shouldRemoveCookieInstantly ( cookie ) || this . shouldRemoveThirdPartyCookieInstantly ( cookie ) ) removeCookie ( cookie ) ; } this . snoozedInstantlyCookies . length = 0 ; } } private cleanCookiesWithRulesNow ( ignoreStartupType , protectOpenDomains ) { getAllCookieStoreIds ( ) . then ( ( ids ) => { for ( const id of ids ) this . removeCookies ( id , ( cookie ) => this . shouldPurgeExpiredCookie ( cookie ) || ! this . isCookieAllowed ( cookie , ignoreStartupType , protectOpenDomains , true ) ) ; } ) ; } private shouldRemoveThirdPartyCookieInstantly ( cookie ) { return settings . get ( "cleanThirdPartyCookies.beforeCreation" ) && ! this . incognitoWatcher . hasCookieStore ( cookie . storeId ) && this . isThirdpartyCookie ( cookie ) ; } private shouldRemoveCookieInstantly ( cookie ) { if ( this . snoozing && this . shouldRemoveThirdPartyCookieInstantly ( cookie ) ) return true ; if ( ! settings . get ( "instantly.enabled" ) || ! settings . get ( "instantly.cookies" ) ) return false ; const allowSubDomains = cookie . domain . startsWith ( "." ) ; const rawDomain = allowSubDomains ? cookie . domain . substr ( 1 ) : cookie . domain ; return settings . getCleanupTypeForCookie ( rawDomain , cookie . name ) === CleanupType . INSTANTLY ; } private onCookieChanged ( changeInfo ) { if ( ! changeInfo . removed && ! this . incognitoWatcher . hasCookieStore ( changeInfo . cookie . storeId ) ) { if ( this . shouldRemoveCookieInstantly ( changeInfo . cookie ) ) { if ( this . snoozing ) this . snoozedInstantlyCookies . push ( changeInfo . cookie ) ; else removeCookie ( changeInfo . cookie ) ; } else if ( settings . get ( "cleanThirdPartyCookies.enabled" ) ) this . removeCookieIfThirdparty ( changeInfo . cookie ) ; } } private removeCookieIfThirdparty ( cookie ) { if ( ! this . incognitoWatcher . hasCookieStore ( cookie . storeId ) && this . isThirdpartyCookie ( cookie ) ) { if ( this . snoozing ) { this . snoozedThirdpartyCookies . push ( cookie ) ; return ; } const exec = new DelayedExecution ( ( ) => { if ( this . snoozing ) { this . snoozedThirdpartyCookies . push ( cookie ) ; return ; } if ( this . isThirdpartyCookie ( cookie ) && ! this . isCookieAllowed ( cookie , false , false , false ) ) removeCookie ( cookie ) ; } ) ; exec . restart ( settings . get ( "cleanThirdPartyCookies.delay" ) * 1000 ) ; } } private isThirdpartyCookie ( cookie ) { const firstPartyDomain = getFirstPartyCookieDomain ( cookie . domain ) ; if ( cookie . firstPartyDomain ) return cookie . firstPartyDomain !== firstPartyDomain ; return ! this . tabWatcher . cookieStoreContainsDomainFP ( cookie . storeId , firstPartyDomain , false ) ; } private removeCookies ( storeId , test ) { const details = { storeId } ; if ( supportsFirstPartyIsolation ) details . firstPartyDomain = null ; browser . cookies . getAll ( details ) . then ( ( cookies ) => { for ( const cookie of cookies ) { if ( test ( cookie ) ) removeCookie ( cookie ) ; } } ) ; } private shouldPurgeExpiredCookie ( cookie ) { return settings . get ( "purgeExpiredCookies" ) && cookie . expirationDate && cookie . expirationDate < Date . now ( ) / 1000 ; } public isCookieAllowed ( cookie , ignoreStartupType , protectOpenDomains , protectSubFrames ) { const allowSubDomains = cookie . domain . startsWith ( "." ) ; const rawDomain = allowSubDomains ? cookie . domain . substr ( 1 ) : cookie . domain ; const type = settings . getCleanupTypeForCookie ( rawDomain , cookie . name ) ; if ( type === CleanupType . NEVER || ( type === CleanupType . STARTUP && ! ignoreStartupType ) ) return true ; if ( type === CleanupType . INSTANTLY || ! protectOpenDomains ) return false ; if ( cookie . firstPartyDomain ) return this . tabWatcher . cookieStoreContainsDomainFP ( cookie . storeId , cookie . firstPartyDomain , protectSubFrames ) ; const firstPartyDomain = getDomain ( rawDomain ) || rawDomain ; return this . tabWatcher . cookieStoreContainsDomainFP ( cookie . storeId , firstPartyDomain , protectSubFrames ) ; } }