diff options
Diffstat (limited to 'js/cookies.js')
-rw-r--r-- | js/cookies.js | 464 |
1 files changed, 163 insertions, 301 deletions
diff --git a/js/cookies.js b/js/cookies.js index ee271ca..2e15e03 100644 --- a/js/cookies.js +++ b/js/cookies.js @@ -28,181 +28,43 @@ "use strict"; -/******************************************************************************/ - // Isolate from global namespace // Use cached-context approach rather than object-based approach, as details // of the implementation do not need to be visible -ηMatrix.cookieHunter = (function() { - - /******************************************************************************/ - - var ηm = ηMatrix; +ηMatrix.cookieHunter = (function () { + Cu.import('chrome://ematrix/content/lib/UriTools.jsm'); + Cu.import('chrome://ematrix/content/lib/CookieCache.jsm'); - var recordPageCookiesQueue = new Map(); - var removePageCookiesQueue = new Map(); - var removeCookieQueue = new Set(); - var cookieDict = new Map(); - var cookieEntryJunkyard = []; - var processRemoveQueuePeriod = 2 * 60 * 1000; - var processCleanPeriod = 10 * 60 * 1000; - var processPageRecordQueueTimer = null; - var processPageRemoveQueueTimer = null; - - /******************************************************************************/ - - var CookieEntry = function(cookie) { - this.usedOn = new Set(); - this.init(cookie); - }; - - CookieEntry.prototype.init = function(cookie) { - this.secure = cookie.secure; - this.session = cookie.session; - this.anySubdomain = cookie.domain.charAt(0) === '.'; - this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain; - this.domain = ηm.URI.domainFromHostname(this.hostname) || this.hostname; - this.path = cookie.path; - this.name = cookie.name; - this.value = cookie.value; - this.tstamp = Date.now(); - this.usedOn.clear(); - return this; - }; - - // Release anything which may consume too much memory - - CookieEntry.prototype.dispose = function() { - this.hostname = ''; - this.domain = ''; - this.path = ''; - this.name = ''; - this.value = ''; - this.usedOn.clear(); - return this; - }; - - /******************************************************************************/ - - var addCookieToDict = function(cookie) { - var cookieKey = cookieKeyFromCookie(cookie), - cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { - cookieEntry = cookieEntryJunkyard.pop(); - if ( cookieEntry ) { - cookieEntry.init(cookie); - } else { - cookieEntry = new CookieEntry(cookie); - } - cookieDict.set(cookieKey, cookieEntry); - } - return cookieEntry; - }; - - /******************************************************************************/ - - var addCookiesToDict = function(cookies) { - var i = cookies.length; - while ( i-- ) { - addCookieToDict(cookies[i]); - } - }; + let ηm = ηMatrix; - /******************************************************************************/ - - var removeCookieFromDict = function(cookieKey) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } - cookieDict.delete(cookieKey); - if ( cookieEntryJunkyard.length < 25 ) { - cookieEntryJunkyard.push(cookieEntry.dispose()); - } - return true; - }; - - /******************************************************************************/ - - var cookieKeyBuilder = [ - '', // 0 = scheme - '://', - '', // 2 = domain - '', // 3 = path - '{', - '', // 5 = persistent or session - '-cookie:', - '', // 7 = name - '}' - ]; - - var cookieKeyFromCookie = function(cookie) { - var cb = cookieKeyBuilder; - cb[0] = cookie.secure ? 'https' : 'http'; - cb[2] = cookie.domain.charAt(0) === '.' ? cookie.domain.slice(1) : cookie.domain; - cb[3] = cookie.path; - cb[5] = cookie.session ? 'session' : 'persistent'; - cb[7] = cookie.name; - return cb.join(''); - }; - - var cookieKeyFromCookieURL = function(url, type, name) { - var ηmuri = ηm.URI.set(url); - var cb = cookieKeyBuilder; - cb[0] = ηmuri.scheme; - cb[2] = ηmuri.hostname; - cb[3] = ηmuri.path; - cb[5] = type; - cb[7] = name; - return cb.join(''); - }; - - /******************************************************************************/ - - var cookieURLFromCookieEntry = function(entry) { - if ( !entry ) { - return ''; - } - return (entry.secure ? 'https://' : 'http://') + entry.hostname + entry.path; - }; - - /******************************************************************************/ - - var cookieMatchDomains = function(cookieKey, allHostnamesString) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } - if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) { - if ( !cookieEntry.anySubdomain ) { - return false; - } - if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) { - return false; - } - } - return true; - }; - - /******************************************************************************/ + let recordPageCookiesQueue = new Map(); + let removePageCookiesQueue = new Map(); + let removeCookieQueue = new Set(); + let processRemoveQueuePeriod = 2 * 60 * 1000; + let processCleanPeriod = 10 * 60 * 1000; + let processPageRecordQueueTimer = null; + let processPageRemoveQueueTimer = null; // Look for cookies to record for a specific web page - var recordPageCookiesAsync = function(pageStats) { + let recordPageCookiesAsync = function (pageStats) { // Store the page stats objects so that it doesn't go away // before we handle the job. // rhill 2013-10-19: pageStats could be nil, for example, this can // happens if a file:// ... makes an xmlHttpRequest - if ( !pageStats ) { + if (!pageStats) { return; } recordPageCookiesQueue.set(pageStats.pageUrl, pageStats); - if ( processPageRecordQueueTimer === null ) { - processPageRecordQueueTimer = vAPI.setTimeout(processPageRecordQueue, 1000); + if (processPageRecordQueueTimer === null) { + processPageRecordQueueTimer = + vAPI.setTimeout(processPageRecordQueue, 1000); } }; - /******************************************************************************/ - - var cookieLogEntryBuilder = [ + let cookieLogEntryBuilder = [ '', '{', '', @@ -211,168 +73,176 @@ '}' ]; - var recordPageCookie = function(pageStore, cookieKey) { - if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) { return; } + let recordPageCookie = function (pageStore, key) { + if (vAPI.isBehindTheSceneTabId(pageStore.tabId)) { + return; + } - var cookieEntry = cookieDict.get(cookieKey); - var pageHostname = pageStore.pageHostname; - var block = ηm.mustBlock(pageHostname, cookieEntry.hostname, 'cookie'); + let entry = CookieCache.get(key); + let pageHostname = pageStore.pageHostname; + let block = ηm.mustBlock(pageHostname, entry.hostname, 'cookie'); - cookieLogEntryBuilder[0] = cookieURLFromCookieEntry(cookieEntry); - cookieLogEntryBuilder[2] = cookieEntry.session ? 'session' : 'persistent'; - cookieLogEntryBuilder[4] = encodeURIComponent(cookieEntry.name); + cookieLogEntryBuilder[0] = CookieUtils.urlFromEntry(entry); + cookieLogEntryBuilder[2] = entry.session ? 'session' : 'persistent'; + cookieLogEntryBuilder[4] = encodeURIComponent(entry.name); - var cookieURL = cookieLogEntryBuilder.join(''); + let cookieURL = cookieLogEntryBuilder.join(''); // rhill 2013-11-20: // https://github.com/gorhill/httpswitchboard/issues/60 // Need to URL-encode cookie name pageStore.recordRequest('cookie', cookieURL, block); - ηm.logger.writeOne(pageStore.tabId, 'net', pageHostname, cookieURL, 'cookie', block); + ηm.logger.writeOne(pageStore.tabId, 'net', + pageHostname, cookieURL, 'cookie', block); - cookieEntry.usedOn.add(pageHostname); + entry.usedOn.add(pageHostname); // rhill 2013-11-21: // https://github.com/gorhill/httpswitchboard/issues/65 // Leave alone cookies from behind-the-scene requests if // behind-the-scene processing is disabled. - if ( !block ) { + if (!block) { return; } - if ( !ηm.userSettings.deleteCookies ) { + if (!ηm.userSettings.deleteCookies) { return; } - removeCookieAsync(cookieKey); + removeCookieAsync(key); }; - /******************************************************************************/ - // Look for cookies to potentially remove for a specific web page - var removePageCookiesAsync = function(pageStats) { + let removePageCookiesAsync = function (pageStats) { // Hold onto pageStats objects so that it doesn't go away // before we handle the job. // rhill 2013-10-19: pageStats could be nil, for example, this can // happens if a file:// ... makes an xmlHttpRequest - if ( !pageStats ) { + if (!pageStats) { return; } removePageCookiesQueue.set(pageStats.pageUrl, pageStats); - if ( processPageRemoveQueueTimer === null ) { - processPageRemoveQueueTimer = vAPI.setTimeout(processPageRemoveQueue, 15 * 1000); + if (processPageRemoveQueueTimer === null) { + processPageRemoveQueueTimer = + vAPI.setTimeout(processPageRemoveQueue, 15 * 1000); } }; - /******************************************************************************/ - // Candidate for removal - var removeCookieAsync = function(cookieKey) { - removeCookieQueue.add(cookieKey); + let removeCookieAsync = function (key) { + removeCookieQueue.add(key); }; - /******************************************************************************/ - - var chromeCookieRemove = function(cookieEntry, name) { - var url = cookieURLFromCookieEntry(cookieEntry); - if ( url === '' ) { + let chromeCookieRemove = function (entry, name) { + let url = CookieUtils.urlFromEntry(entry); + if (url === '') { return; } - var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name); - var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name); - var callback = function(details) { - var success = !!details; - var template = success ? i18nCookieDeleteSuccess : i18nCookieDeleteFailure; - if ( removeCookieFromDict(sessionCookieKey) ) { - if ( success ) { + let sessionKey = CookieUtils.keyFromURL(UriTools.set(url), + 'session', name); + let persistKey = CookieUtils.keyFromURL(UriTools.set(url), + 'persistent', name); + + let callback = function(details) { + let success = !!details; + let template = success ? + i18nCookieDeleteSuccess : + i18nCookieDeleteFailure; + + if (CookieCache.remove(sessionKey)) { + if (success) { ηm.cookieRemovedCounter += 1; } - ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', sessionCookieKey)); + ηm.logger.writeOne('', 'info', 'cookie', + template.replace('{{value}}', + sessionKey)); } - if ( removeCookieFromDict(persistCookieKey) ) { - if ( success ) { + if (CookieCache.remove(persistKey)) { + if (success) { ηm.cookieRemovedCounter += 1; } - ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', persistCookieKey)); + ηm.logger.writeOne('', 'info', 'cookie', + template.replace('{{value}}', + persistKey)); } }; - vAPI.cookies.remove({ url: url, name: name }, callback); + vAPI.cookies.remove({ + url: url, name: name + }, callback); }; - var i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted'); - var i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError'); - - /******************************************************************************/ + let i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted'); + let i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError'); - var processPageRecordQueue = function() { + let processPageRecordQueue = function () { processPageRecordQueueTimer = null; - for ( var pageStore of recordPageCookiesQueue.values() ) { + for (let pageStore of recordPageCookiesQueue.values()) { findAndRecordPageCookies(pageStore); } recordPageCookiesQueue.clear(); }; - /******************************************************************************/ - - var processPageRemoveQueue = function() { + let processPageRemoveQueue = function () { processPageRemoveQueueTimer = null; - for ( var pageStore of removePageCookiesQueue.values() ) { + for (let pageStore of removePageCookiesQueue.values()) { findAndRemovePageCookies(pageStore); } removePageCookiesQueue.clear(); }; - /******************************************************************************/ - // Effectively remove cookies. - var processRemoveQueue = function() { - var userSettings = ηm.userSettings; - var deleteCookies = userSettings.deleteCookies; + let processRemoveQueue = function () { + let userSettings = ηm.userSettings; + let deleteCookies = userSettings.deleteCookies; // Session cookies which timestamp is *after* tstampObsolete will // be left untouched // https://github.com/gorhill/httpswitchboard/issues/257 - var tstampObsolete = userSettings.deleteUnusedSessionCookies ? - Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 : + let dusc = userSettings.deleteUnusedSessionCookies; + let dusca = userSettings.deleteUnusedSessionCookiesAfter; + let tstampObsolete = dusc ? + Date.now() - dusca * 60 * 1000 : 0; - var srcHostnames; - var cookieEntry; + let srcHostnames; + let entry; - for ( var cookieKey of removeCookieQueue ) { + for (let key of removeCookieQueue) { // rhill 2014-05-12: Apparently this can happen. I have to // investigate how (A session cookie has same name as a // persistent cookie?) - cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { continue; } + entry = CookieCache.get(key); + if (entry === undefined) { + continue; + } // Delete obsolete session cookies: enabled. - if ( tstampObsolete !== 0 && cookieEntry.session ) { - if ( cookieEntry.tstamp < tstampObsolete ) { - chromeCookieRemove(cookieEntry, cookieEntry.name); + if (tstampObsolete !== 0 && entry.session) { + if (entry.tstamp < tstampObsolete) { + chromeCookieRemove(entry, entry.name); continue; } } // Delete all blocked cookies: disabled. - if ( deleteCookies === false ) { + if (deleteCookies === false) { continue; } // Query scopes only if we are going to use them - if ( srcHostnames === undefined ) { + if (srcHostnames === undefined) { srcHostnames = ηm.tMatrix.extractAllSourceHostnames(); } // Ensure cookie is not allowed on ALL current web pages: It can // happen that a cookie is blacklisted on one web page while // being whitelisted on another (because of per-page permissions). - if ( canRemoveCookie(cookieKey, srcHostnames) ) { - chromeCookieRemove(cookieEntry, cookieEntry.name); + if (canRemoveCookie(key, srcHostnames)) { + chromeCookieRemove(entry, entry.name); } } @@ -381,21 +251,21 @@ vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); }; - /******************************************************************************/ - // Once in a while, we go ahead and clean everything that might have been // left behind. // Remove only some of the cookies which are candidate for removal: who knows, // maybe a user has 1000s of cookies sitting in his browser... - var processClean = function() { - var us = ηm.userSettings; - if ( us.deleteCookies || us.deleteUnusedSessionCookies ) { - var cookieKeys = Array.from(cookieDict.keys()), - len = cookieKeys.length, - step, offset, n; - if ( len > 25 ) { + let processClean = function () { + let us = ηm.userSettings; + + if (us.deleteCookies || us.deleteUnusedSessionCookies) { + let keys = Array.from(CookieCache.keys()); + let len = keys.length; + let step, offset, n; + + if (len > 25) { step = len / 25; offset = Math.floor(Math.random() * len); n = 25; @@ -404,9 +274,10 @@ offset = 0; n = len; } - var i = offset; - while ( n-- ) { - removeCookieAsync(cookieKeys[Math.floor(i % len)]); + + let i = offset; + while (n--) { + removeCookieAsync(keys[Math.floor(i % len)]); i += step; } } @@ -414,57 +285,56 @@ vAPI.setTimeout(processClean, processCleanPeriod); }; - /******************************************************************************/ - - var findAndRecordPageCookies = function(pageStore) { - for ( var cookieKey of cookieDict.keys() ) { - if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - recordPageCookie(pageStore, cookieKey); + let findAndRecordPageCookies = function (pageStore) { + for (let key of CookieCache.keys()) { + if (CookieUtils.matchDomains(key, pageStore.allHostnamesString)) { + recordPageCookie(pageStore, key); } } }; - /******************************************************************************/ - - var findAndRemovePageCookies = function(pageStore) { - for ( var cookieKey of cookieDict.keys() ) { - if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - removeCookieAsync(cookieKey); + let findAndRemovePageCookies = function (pageStore) { + for (let key of CookieCache.keys()) { + if (CookieUtils.matchDomains(key, pageStore.allHostnamesString)) { + removeCookieAsync(key); } } }; - /******************************************************************************/ - - var canRemoveCookie = function(cookieKey, srcHostnames) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } + let canRemoveCookie = function (key, srcHostnames) { + let entry = CookieCache.get(key); + if (entry === undefined) { + return false; + } - var cookieHostname = cookieEntry.hostname; - var srcHostname; + let cookieHostname = entry.hostname; + let srcHostname; - for ( srcHostname of cookieEntry.usedOn ) { - if ( ηm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { + for (srcHostname of entry.usedOn) { + if (ηm.mustAllow(srcHostname, cookieHostname, 'cookie')) { return false; } } + // Maybe there is a scope in which the cookie is 1st-party-allowed. // For example, if I am logged in into `github.com`, I do not want to be // logged out just because I did not yet open a `github.com` page after // re-starting the browser. srcHostname = cookieHostname; - var pos; + + let pos; + for (;;) { - if ( srcHostnames.has(srcHostname) ) { - if ( ηm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { + if (srcHostnames.has(srcHostname)) { + if (ηm.mustAllow(srcHostname, cookieHostname, 'cookie')) { return false; } } - if ( srcHostname === cookieEntry.domain ) { + if (srcHostname === entry.domain) { break; } pos = srcHostname.indexOf('.'); - if ( pos === -1 ) { + if (pos === -1) { break; } srcHostname = srcHostname.slice(pos + 1); @@ -472,81 +342,73 @@ return true; }; - /******************************************************************************/ - // Listen to any change in cookieland, we will update page stats accordingly. - vAPI.cookies.onChanged = function(cookie) { + vAPI.cookies.onChanged = function (cookie) { // rhill 2013-12-11: If cookie value didn't change, no need to record. // https://github.com/gorhill/httpswitchboard/issues/79 - var cookieKey = cookieKeyFromCookie(cookie); - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { - cookieEntry = addCookieToDict(cookie); + let key = CookieUtils.keyFromCookie(cookie); + let entry = CookieCache.get(key); + + if (entry === undefined) { + entry = CookieCache.add(cookie); } else { - cookieEntry.tstamp = Date.now(); - if ( cookie.value === cookieEntry.value ) { return; } - cookieEntry.value = cookie.value; + entry.tstamp = Date.now(); + if (cookie.value === entry.value) { + return; + } + entry.value = cookie.value; } // Go through all pages and update if needed, as one cookie can be used // by many web pages, so they need to be recorded for all these pages. - var pageStores = ηm.pageStores; - var pageStore; - for ( var tabId in pageStores ) { - if ( pageStores.hasOwnProperty(tabId) === false ) { + let pageStores = ηm.pageStores; + let pageStore; + for (let tabId in pageStores) { + if (pageStores.hasOwnProperty(tabId) === false) { continue; } pageStore = pageStores[tabId]; - if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + if (!CookieUtils.matchDomains(key, pageStore.allHostnamesString)) { continue; } - recordPageCookie(pageStore, cookieKey); + recordPageCookie(pageStore, key); } }; - /******************************************************************************/ - // Listen to any change in cookieland, we will update page stats accordingly. - vAPI.cookies.onRemoved = function(cookie) { - var cookieKey = cookieKeyFromCookie(cookie); - if ( removeCookieFromDict(cookieKey) ) { - ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); + vAPI.cookies.onRemoved = function (cookie) { + let key = CookieUtils.keyFromCookie(cookie); + if (CookieCache.remove(key)) { + ηm.logger.writeOne('', 'info', 'cookie', + i18nCookieDeleteSuccess.replace('{{value}}', + key)); } }; - /******************************************************************************/ - // Listen to any change in cookieland, we will update page stats accordingly. - vAPI.cookies.onAllRemoved = function() { - for ( var cookieKey of cookieDict.keys() ) { - if ( removeCookieFromDict(cookieKey) ) { - ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); + vAPI.cookies.onAllRemoved = function () { + for (let key of CookieCache.keys()) { + if (CookieCache.remove(key)) { + ηm.logger.writeOne('', 'info', 'cookie', + i18nCookieDeleteSuccess.replace('{{value}}', + key)); } } }; - /******************************************************************************/ - - vAPI.cookies.getAll(addCookiesToDict); + vAPI.cookies.getAll(CookieCache.addVector); vAPI.cookies.start(); vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); vAPI.setTimeout(processClean, processCleanPeriod); - /******************************************************************************/ - // Expose only what is necessary return { recordPageCookies: recordPageCookiesAsync, removePageCookies: removePageCookiesAsync }; - - /******************************************************************************/ - })(); - -/******************************************************************************/ |