this fixes #17

pull/2/head 0.8.0.0-alpha.9
gorhill 10 years ago
parent e187161b84
commit 118306ab51

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global µMatrix */
// rhill 2013-12-14: the whole cookie management has been rewritten so as // rhill 2013-12-14: the whole cookie management has been rewritten so as
// to avoid having to call chrome API whenever a single cookie changes, and // to avoid having to call chrome API whenever a single cookie changes, and
// to record cookie for a web page *only* when its value changes. // to record cookie for a web page *only* when its value changes.
@ -35,6 +37,8 @@
/******************************************************************************/ /******************************************************************************/
var µm = µMatrix;
var recordPageCookiesQueue = {}; var recordPageCookiesQueue = {};
var removePageCookiesQueue = {}; var removePageCookiesQueue = {};
var removeCookieQueue = {}; var removeCookieQueue = {};
@ -51,21 +55,24 @@ CookieEntry.prototype.set = function(cookie) {
this.secure = cookie.secure; this.secure = cookie.secure;
this.session = cookie.session; this.session = cookie.session;
this.anySubdomain = cookie.domain.charAt(0) === '.'; this.anySubdomain = cookie.domain.charAt(0) === '.';
this.domain = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain; this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain;
this.domain = µm.URI.domainFromHostname(this.hostname) || this.hostname;
this.path = cookie.path; this.path = cookie.path;
this.name = cookie.name; this.name = cookie.name;
this.value = cookie.value; this.value = cookie.value;
this.tstamp = Date.now(); this.tstamp = Date.now();
this.usedOn = {};
return this; return this;
}; };
// Release anything which may consume too much memory // Release anything which may consume too much memory
CookieEntry.prototype.unset = function() { CookieEntry.prototype.unset = function() {
this.domain = ''; this.hostname = '';
this.path = ''; this.path = '';
this.name = ''; this.name = '';
this.value = ''; this.value = '';
this.usedOn = {};
return this; return this;
}; };
@ -134,7 +141,7 @@ var cookieKeyFromCookie = function(cookie) {
}; };
var cookieKeyFromCookieURL = function(url, type, name) { var cookieKeyFromCookieURL = function(url, type, name) {
var µmuri = µMatrix.URI.set(url); var µmuri = µm.URI.set(url);
var cb = cookieKeyBuilder; var cb = cookieKeyBuilder;
cb[0] = µmuri.scheme; cb[0] = µmuri.scheme;
cb[2] = µmuri.hostname; cb[2] = µmuri.hostname;
@ -156,21 +163,21 @@ var cookieURLFromCookieEntry = function(entry) {
if ( !entry ) { if ( !entry ) {
return ''; return '';
} }
return (entry.secure ? 'https://' : 'http://') + entry.domain + entry.path; return (entry.secure ? 'https://' : 'http://') + entry.hostname + entry.path;
}; };
/******************************************************************************/ /******************************************************************************/
var cookieMatchDomains = function(cookieKey, domains) { var cookieMatchDomains = function(cookieKey, allHostnamesString) {
var cookieEntry = cookieDict[cookieKey]; var cookieEntry = cookieDict[cookieKey];
if ( !cookieEntry ) { if ( !cookieEntry ) {
return false; return false;
} }
if ( domains.indexOf(' ' + cookieEntry.domain + ' ') < 0 ) { if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) {
if ( !cookieEntry.anySubdomain ) { if ( !cookieEntry.anySubdomain ) {
return false; return false;
} }
if ( domains.indexOf('.' + cookieEntry.domain + ' ') < 0 ) { if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) {
return false; return false;
} }
} }
@ -189,9 +196,9 @@ var recordPageCookiesAsync = function(pageStats) {
if ( !pageStats ) { if ( !pageStats ) {
return; return;
} }
var pageURL = µMatrix.pageUrlFromPageStats(pageStats); var pageURL = µm.pageUrlFromPageStats(pageStats);
recordPageCookiesQueue[pageURL] = pageStats; recordPageCookiesQueue[pageURL] = pageStats;
µMatrix.asyncJobs.add( µm.asyncJobs.add(
'cookieHunterPageRecord', 'cookieHunterPageRecord',
null, null,
processPageRecordQueue, processPageRecordQueue,
@ -211,11 +218,9 @@ var cookieLogEntryBuilder = [
'}' '}'
]; ];
var recordPageCookie = function(pageStats, cookieKey) { var recordPageCookie = function(pageStore, cookieKey) {
var µm = µMatrix;
var cookieEntry = cookieDict[cookieKey]; var cookieEntry = cookieDict[cookieKey];
var pageURL = pageStats.pageUrl; var block = µm.mustBlock(pageStore.pageHostname, cookieEntry.hostname, 'cookie');
var block = µm.mustBlock(µm.scopeFromURL(pageURL), cookieEntry.domain, 'cookie');
cookieLogEntryBuilder[0] = cookieURLFromCookieEntry(cookieEntry); cookieLogEntryBuilder[0] = cookieURLFromCookieEntry(cookieEntry);
cookieLogEntryBuilder[2] = cookieEntry.session ? 'session' : 'persistent'; cookieLogEntryBuilder[2] = cookieEntry.session ? 'session' : 'persistent';
@ -224,12 +229,14 @@ var recordPageCookie = function(pageStats, cookieKey) {
// rhill 2013-11-20: // rhill 2013-11-20:
// https://github.com/gorhill/httpswitchboard/issues/60 // https://github.com/gorhill/httpswitchboard/issues/60
// Need to URL-encode cookie name // Need to URL-encode cookie name
pageStats.recordRequest( pageStore.recordRequest(
'cookie', 'cookie',
cookieLogEntryBuilder.join(''), cookieLogEntryBuilder.join(''),
block block
); );
cookieEntry.usedOn[pageStore.pageHostname] = true;
// rhill 2013-11-21: // rhill 2013-11-21:
// https://github.com/gorhill/httpswitchboard/issues/65 // https://github.com/gorhill/httpswitchboard/issues/65
// Leave alone cookies from behind-the-scene requests if // Leave alone cookies from behind-the-scene requests if
@ -255,9 +262,9 @@ var removePageCookiesAsync = function(pageStats) {
if ( !pageStats ) { if ( !pageStats ) {
return; return;
} }
var pageURL = µMatrix.pageUrlFromPageStats(pageStats); var pageURL = µm.pageUrlFromPageStats(pageStats);
removePageCookiesQueue[pageURL] = pageStats; removePageCookiesQueue[pageURL] = pageStats;
µMatrix.asyncJobs.add( µm.asyncJobs.add(
'cookieHunterPageRemove', 'cookieHunterPageRemove',
null, null,
processPageRemoveQueue, processPageRemoveQueue,
@ -284,12 +291,12 @@ var chromeCookieRemove = function(url, name) {
} }
var cookieKey = cookieKeyFromCookieURL(details.url, 'session', details.name); var cookieKey = cookieKeyFromCookieURL(details.url, 'session', details.name);
if ( removeCookieFromDict(cookieKey) ) { if ( removeCookieFromDict(cookieKey) ) {
µMatrix.cookieRemovedCounter += 1; µm.cookieRemovedCounter += 1;
return; return;
} }
cookieKey = cookieKeyFromCookieURL(details.url, 'persistent', details.name); cookieKey = cookieKeyFromCookieURL(details.url, 'persistent', details.name);
if ( removeCookieFromDict(cookieKey) ) { if ( removeCookieFromDict(cookieKey) ) {
µMatrix.cookieRemovedCounter += 1; µm.cookieRemovedCounter += 1;
} }
}; };
@ -325,7 +332,7 @@ var processPageRemoveQueue = function() {
// Effectively remove cookies. // Effectively remove cookies.
var processRemoveQueue = function() { var processRemoveQueue = function() {
var userSettings = µMatrix.userSettings; var userSettings = µm.userSettings;
var deleteCookies = userSettings.deleteCookies; var deleteCookies = userSettings.deleteCookies;
// Session cookies which timestamp is *after* tstampObsolete will // Session cookies which timestamp is *after* tstampObsolete will
@ -335,7 +342,9 @@ var processRemoveQueue = function() {
Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 : Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 :
0; 0;
var srcHostnames;
var cookieEntry; var cookieEntry;
for ( var cookieKey in removeCookieQueue ) { for ( var cookieKey in removeCookieQueue ) {
if ( removeCookieQueue.hasOwnProperty(cookieKey) === false ) { if ( removeCookieQueue.hasOwnProperty(cookieKey) === false ) {
continue; continue;
@ -348,7 +357,7 @@ var processRemoveQueue = function() {
// investigate how (A session cookie has same name as a // investigate how (A session cookie has same name as a
// persistent cookie?) // persistent cookie?)
if ( !cookieEntry ) { if ( !cookieEntry ) {
console.error('HTTP Switchboard> cookies.js/processRemoveQueue(): no cookieEntry for "%s"', cookieKey); // console.error('cookies.js > processRemoveQueue(): no cookieEntry for "%s"', cookieKey);
continue; continue;
} }
@ -357,10 +366,15 @@ var processRemoveQueue = function() {
continue; continue;
} }
// Query scopes only if we are going to use them
if ( srcHostnames === undefined ) {
srcHostnames = µm.tMatrix.extractAllSourceHostnames();
}
// Ensure cookie is not allowed on ALL current web pages: It can // Ensure cookie is not allowed on ALL current web pages: It can
// happen that a cookie is blacklisted on one web page while // happen that a cookie is blacklisted on one web page while
// being whitelisted on another (because of per-page permissions). // being whitelisted on another (because of per-page permissions).
if ( canRemoveCookie(cookieKey) === false ) { if ( canRemoveCookie(cookieKey, srcHostnames) === false ) {
// Exception: session cookie may have to be removed even though // Exception: session cookie may have to be removed even though
// they are seen as being whitelisted. // they are seen as being whitelisted.
if ( cookieEntry.session === false || cookieEntry.tstamp > tstampObsolete ) { if ( cookieEntry.session === false || cookieEntry.tstamp > tstampObsolete ) {
@ -373,7 +387,7 @@ var processRemoveQueue = function() {
continue; continue;
} }
console.debug('µMatrix> cookies.js/processRemoveQueue(): removing "%s" (age=%s min)', cookieKey, ((Date.now() - cookieEntry.tstamp) / 60000).toFixed(1)); // console.debug('cookies.js > processRemoveQueue(): removing "%s" (age=%s min)', cookieKey, ((Date.now() - cookieEntry.tstamp) / 60000).toFixed(1));
chromeCookieRemove(url, cookieEntry.name); chromeCookieRemove(url, cookieEntry.name);
} }
}; };
@ -399,12 +413,11 @@ var processClean = function() {
/******************************************************************************/ /******************************************************************************/
var findAndRecordPageCookies = function(pageStats) { var findAndRecordPageCookies = function(pageStats) {
var domains = ' ' + Object.keys(pageStats.domains).join(' ') + ' ';
for ( var cookieKey in cookieDict ) { for ( var cookieKey in cookieDict ) {
if ( !cookieDict.hasOwnProperty(cookieKey) ) { if ( !cookieDict.hasOwnProperty(cookieKey) ) {
continue; continue;
} }
if ( !cookieMatchDomains(cookieKey, domains) ) { if ( cookieMatchDomains(cookieKey, pageStats.allHostnamesString) === false ) {
continue; continue;
} }
recordPageCookie(pageStats, cookieKey); recordPageCookie(pageStats, cookieKey);
@ -414,12 +427,11 @@ var findAndRecordPageCookies = function(pageStats) {
/******************************************************************************/ /******************************************************************************/
var findAndRemovePageCookies = function(pageStats) { var findAndRemovePageCookies = function(pageStats) {
var domains = ' ' + Object.keys(pageStats.domains).join(' ') + ' ';
for ( var cookieKey in cookieDict ) { for ( var cookieKey in cookieDict ) {
if ( !cookieDict.hasOwnProperty(cookieKey, domains) ) { if ( !cookieDict.hasOwnProperty(cookieKey) ) {
continue; continue;
} }
if ( !cookieMatchDomains(cookieKey, domains) ) { if ( !cookieMatchDomains(cookieKey, pageStats.allHostnamesString) ) {
continue; continue;
} }
removeCookieAsync(cookieKey); removeCookieAsync(cookieKey);
@ -428,69 +440,44 @@ var findAndRemovePageCookies = function(pageStats) {
/******************************************************************************/ /******************************************************************************/
// Check all scopes to ensure none of them fulfill the following var canRemoveCookie = function(cookieKey, srcHostnames) {
// conditions: var cookieEntry = cookieDict[cookieKey];
// - The hostname of the target cookie matches the hostname of the scope if ( !cookieEntry ) {
// - The target cookie is allowed in the scope
// Check all pages to ensure none of them fulfill both following
// conditions:
// - refers to the target cookie
// - the target cookie is is allowed
// If one of the above set of conditions is fulfilled at least once,
// the cookie can NOT be removed.
// TODO: cache the joining of hostnames into a single string for search
// purpose.
var canRemoveCookie = function(cookieKey) {
var entry = cookieDict[cookieKey];
if ( !entry ) {
return false; return false;
} }
var µm = µMatrix; var cookieHostname = cookieEntry.hostname;
var cookieHostname = entry.domain;
var cookieDomain = µm.URI.domainFromHostname(cookieHostname);
// rhill 2014-01-11: Do not delete cookies which are whitelisted
// in at least one scope. Limitation: this can be done only
// for cookies which domain matches domain of scope. This is
// because a scope with whitelist *|* would cause all cookies to not
// be removable.
// https://github.com/gorhill/httpswitchboard/issues/126
var srcHostnames = µm.tMatrix.extractAllSourceHostnames();
var i = srcHostnames.length;
var srcHostname; var srcHostname;
while ( i-- ) {
// Cookie related to scope domain? for ( srcHostname in cookieEntry.usedOn ) {
srcHostname = µm.URI.domainFromHostname(srcHostnames[i]); if ( cookieEntry.usedOn.hasOwnProperty(srcHostname) === false ) {
if ( srcHostname === '' || srcHostname !== cookieDomain ) {
continue; continue;
} }
if ( µm.mustBlock(srcHostname, cookieHostname, 'cookie') === false ) { if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) {
// console.log('cookies.js/canRemoveCookie()> can NOT remove "%s" because of scope "%s"', cookieKey, scopeKey);
return false; return false;
} }
} }
// Maybe there is a scope in which the cookie is 1st-party-allowed.
// If we reach this point, we will check whether the cookie is actually // For example, if I am logged in into `github.com`, I do not want to be
// in use for a currently opened web page. This is necessary to // logged out just because I did not yet open a `github.com` page after
// prevent the deletion of 3rd-party cookies which might be whitelisted // re-starting the browser.
// for a currently opened web page. srcHostname = cookieHostname;
var pageStats = µm.pageStats; var pos;
for ( var pageURL in pageStats ) { for (;;) {
if ( pageStats.hasOwnProperty(pageURL) === false ) { if ( srcHostnames.hasOwnProperty(srcHostname) ) {
continue; if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) {
return false;
}
} }
if ( !cookieMatchDomains(cookieKey, ' ' + Object.keys(pageStats[pageURL].domains).join(' ') + ' ') ) { if ( srcHostname === cookieEntry.domain ) {
continue; break;
} }
if ( µm.mustAllow(µm.scopeFromURL(pageURL), cookieHostname, 'cookie') ) { pos = srcHostname.indexOf('.');
// console.log('cookies.js/canRemoveCookie()> can NOT remove "%s" because of scope "%s"', cookieKey, scopeKey); if ( pos === -1 ) {
return false; break;
} }
srcHostname = srcHostname.slice(pos + 1);
} }
return true;
// console.log('cookies.js/canRemoveCookie()> can remove "%s"', cookieKey);
return true;
}; };
/******************************************************************************/ /******************************************************************************/
@ -520,17 +507,17 @@ var onChromeCookieChanged = function(changeInfo) {
// Go through all pages and update if needed, as one cookie can be used // 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. // by many web pages, so they need to be recorded for all these pages.
var allPageStats = µMatrix.pageStats; var pageStores = µm.pageStats;
var pageStats; var pageStore;
for ( var pageURL in allPageStats ) { for ( var pageURL in pageStores ) {
if ( !allPageStats.hasOwnProperty(pageURL) ) { if ( pageStores.hasOwnProperty(pageURL) === false ) {
continue; continue;
} }
pageStats = allPageStats[pageURL]; pageStore = pageStores[pageURL];
if ( !cookieMatchDomains(cookieKey, ' ' + Object.keys(pageStats.domains).join(' ') + ' ') ) { if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) {
continue; continue;
} }
recordPageCookie(pageStats, cookieKey); recordPageCookie(pageStore, cookieKey);
} }
}; };
@ -539,8 +526,8 @@ var onChromeCookieChanged = function(changeInfo) {
chrome.cookies.getAll({}, addCookiesToDict); chrome.cookies.getAll({}, addCookiesToDict);
chrome.cookies.onChanged.addListener(onChromeCookieChanged); chrome.cookies.onChanged.addListener(onChromeCookieChanged);
// µMatrix.asyncJobs.add('cookieHunterRemove', null, processRemoveQueue, 2 * 60 * 1000, true); µm.asyncJobs.add('cookieHunterRemove', null, processRemoveQueue, 2 * 60 * 1000, true);
// µMatrix.asyncJobs.add('cookieHunterClean', null, processClean, 10 * 60 * 1000, true); µm.asyncJobs.add('cookieHunterClean', null, processClean, 10 * 60 * 1000, true);
/******************************************************************************/ /******************************************************************************/

@ -498,7 +498,24 @@ Matrix.prototype.extractAllSourceHostnames = function() {
} }
srcHostnames[rule.slice(0, rule.indexOf(' '))] = true; srcHostnames[rule.slice(0, rule.indexOf(' '))] = true;
} }
return Object.keys(srcHostnames); return srcHostnames;
};
/******************************************************************************/
// TODO: In all likelyhood, will have to optmize here, i.e. keeping an
// up-to-date collection of src hostnames with reference count etc.
Matrix.prototype.extractAllDestinationHostnames = function() {
var desHostnames = {};
var rules = this.rules;
for ( var rule in rules ) {
if ( rules.hasOwnProperty(rule) === false ) {
continue;
}
desHostnames[this.desHostnameFromRule(rule)] = true;
}
return desHostnames;
}; };
/******************************************************************************/ /******************************************************************************/

@ -435,21 +435,9 @@ var pageStoreFactory = function(pageUrl) {
/******************************************************************************/ /******************************************************************************/
function PageStore(pageUrl) { function PageStore(pageUrl) {
this.pageUrl = '';
this.pageHostname = '';
this.pageDomain = '';
this.pageScriptBlocked = false;
this.thirdpartyScript = false;
this.requests = µm.PageRequestStats.factory();
this.domains = {};
this.state = {};
this.visible = false; this.visible = false;
this.requestStats = new WebRequestStats(); this.requestStats = new WebRequestStats();
this.distinctRequestCount = 0;
this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0;
this.off = false; this.off = false;
this.abpBlockCount = 0;
this.init(pageUrl); this.init(pageUrl);
} }
@ -458,17 +446,17 @@ function PageStore(pageUrl) {
PageStore.prototype.init = function(pageUrl) { PageStore.prototype.init = function(pageUrl) {
this.pageUrl = pageUrl; this.pageUrl = pageUrl;
this.pageHostname = µm.URI.hostnameFromURI(pageUrl); this.pageHostname = µm.URI.hostnameFromURI(pageUrl);
this.pageDomain = µm.URI.domainFromHostname(this.pageHostname) || this.pageHostname; this.pageDomain = µm.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
this.pageScriptBlocked = false; this.pageScriptBlocked = false;
this.thirdpartyScript = false; this.thirdpartyScript = false;
this.requests = µm.PageRequestStats.factory(); this.requests = µm.PageRequestStats.factory();
this.domains = {}; this.domains = {};
this.allHostnamesString = ' ';
this.state = {}; this.state = {};
this.requestStats.reset(); this.requestStats.reset();
this.distinctRequestCount = 0; this.distinctRequestCount = 0;
this.perLoadAllowedRequestCount = 0; this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0; this.perLoadBlockedRequestCount = 0;
this.abpBlockCount = 0;
return this; return this;
}; };
@ -485,6 +473,7 @@ PageStore.prototype.dispose = function() {
this.pageHostname = ''; this.pageHostname = '';
this.pageDomain = ''; this.pageDomain = '';
this.domains = {}; this.domains = {};
this.allHostnamesString = ' ';
this.state = {}; this.state = {};
if ( pageStoreJunkyard.length < 8 ) { if ( pageStoreJunkyard.length < 8 ) {
@ -545,7 +534,10 @@ PageStore.prototype.recordRequest = function(type, url, block) {
} }
this.distinctRequestCount++; this.distinctRequestCount++;
this.domains[hostname] = true; if ( this.domains.hasOwnProperty(hostname) === false ) {
this.domains[hostname] = true;
this.allHostnamesString += hostname + ' ';
}
µm.urlStatsChanged(this.pageUrl); µm.urlStatsChanged(this.pageUrl);
// console.debug("HTTP Switchboard> PageStore.recordRequest(): %o: %s @ %s", this, type, url); // console.debug("HTTP Switchboard> PageStore.recordRequest(): %o: %s @ %s", this, type, url);

Loading…
Cancel
Save