This commit is contained in:
gorhill 2016-10-14 10:06:34 -04:00
parent da163bbe4b
commit cbefeb923c
5 changed files with 81 additions and 104 deletions

View File

@ -858,7 +858,8 @@ vAPI.net.registerListeners = function() {
// https://bugs.chromium.org/p/chromium/issues/detail?id=410382 // https://bugs.chromium.org/p/chromium/issues/detail?id=410382
// Between Chromium 38-48, plug-ins' network requests were reported as // Between Chromium 38-48, plug-ins' network requests were reported as
// type "other" instead of "object". // type "other" instead of "object".
var is_v38_48 = /\bChrom[a-z]+\/(?:3[89]|4[0-8])\.[\d.]+\b/.test(navigator.userAgent); var is_v38_48 = /\bChrom[a-z]+\/(?:3[89]|4[0-8])\.[\d.]+\b/.test(navigator.userAgent),
is_v49_55 = /\bChrom[a-z]+\/(?:49|5[012345])\b/.test(navigator.userAgent);
// Chromium-based browsers understand only these network request types. // Chromium-based browsers understand only these network request types.
var validTypes = { var validTypes = {
@ -1003,9 +1004,20 @@ vAPI.net.registerListeners = function() {
return onBeforeRequestClient(details); return onBeforeRequestClient(details);
}; };
var onHeadersReceivedClient = this.onHeadersReceived.callback; // This is needed for Chromium 49-55.
var onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0); var onBeforeSendHeaders = function(details) {
var onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); if ( details.type !== 'ping' || details.method !== 'POST' ) { return; }
var type = headerValue(details.requestHeaders, 'content-type');
if ( type === '' ) { return; }
if ( type.endsWith('/csp-report') ) {
details.type = 'csp_report';
return onBeforeRequestClient(details);
}
};
var onHeadersReceivedClient = this.onHeadersReceived.callback,
onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0),
onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes);
var onHeadersReceived = function(details) { var onHeadersReceived = function(details) {
normalizeRequestDetails(details); normalizeRequestDetails(details);
// Hack to work around Chromium API limitations, where requests of // Hack to work around Chromium API limitations, where requests of
@ -1033,19 +1045,17 @@ vAPI.net.registerListeners = function() {
}; };
var installListeners = (function() { var installListeners = (function() {
var listener;
var crapi = chrome.webRequest; var crapi = chrome.webRequest;
listener = onBeforeRequest;
//listener = function(details) { //listener = function(details) {
// quickProfiler.start('onBeforeRequest'); // quickProfiler.start('onBeforeRequest');
// var r = onBeforeRequest(details); // var r = onBeforeRequest(details);
// quickProfiler.stop(); // quickProfiler.stop();
// return r; // return r;
//}; //};
if ( crapi.onBeforeRequest.hasListener(listener) === false ) { if ( crapi.onBeforeRequest.hasListener(onBeforeRequest) === false ) {
crapi.onBeforeRequest.addListener( crapi.onBeforeRequest.addListener(
listener, onBeforeRequest,
{ {
'urls': this.onBeforeRequest.urls || ['<all_urls>'], 'urls': this.onBeforeRequest.urls || ['<all_urls>'],
'types': this.onBeforeRequest.types || undefined 'types': this.onBeforeRequest.types || undefined
@ -1054,10 +1064,22 @@ vAPI.net.registerListeners = function() {
); );
} }
listener = onHeadersReceived; // Chromium 48 and lower does not support `ping` type.
if ( crapi.onHeadersReceived.hasListener(listener) === false ) { // Chromium 56 and higher does support `csp_report` stype.
if ( is_v49_55 && crapi.onBeforeSendHeaders.hasListener(onBeforeSendHeaders) === false ) {
crapi.onBeforeSendHeaders.addListener(
onBeforeSendHeaders,
{
'urls': [ '<all_urls>' ],
'types': [ 'ping' ]
},
[ 'blocking', 'requestHeaders' ]
);
}
if ( crapi.onHeadersReceived.hasListener(onHeadersReceived) === false ) {
crapi.onHeadersReceived.addListener( crapi.onHeadersReceived.addListener(
listener, onHeadersReceived,
{ {
'urls': this.onHeadersReceived.urls || ['<all_urls>'], 'urls': this.onHeadersReceived.urls || ['<all_urls>'],
'types': onHeadersReceivedTypes 'types': onHeadersReceivedTypes

View File

@ -260,15 +260,12 @@ vAPI.browserSettings = {
case 'hyperlinkAuditing': case 'hyperlinkAuditing':
this.rememberOriginalValue('browser', 'send_pings'); this.rememberOriginalValue('browser', 'send_pings');
this.rememberOriginalValue('beacon', 'enabled');
// https://github.com/gorhill/uBlock/issues/292 // https://github.com/gorhill/uBlock/issues/292
// "true" means "do not disable", i.e. leave entry alone // "true" means "do not disable", i.e. leave entry alone
if ( settingVal ) { if ( settingVal ) {
this.clear('browser', 'send_pings'); this.clear('browser', 'send_pings');
this.clear('beacon', 'enabled');
} else { } else {
this.setValue('browser', 'send_pings', false); this.setValue('browser', 'send_pings', false);
this.setValue('beacon', 'enabled', false);
} }
break; break;
@ -1892,6 +1889,7 @@ var httpObserver = {
14: 'font', 14: 'font',
15: 'media', 15: 'media',
16: 'websocket', 16: 'websocket',
17: 'csp_report',
19: 'beacon', 19: 'beacon',
21: 'image' 21: 'image'
}, },
@ -2156,8 +2154,8 @@ var httpObserver = {
// 'Content-Security-Policy' MUST come last in the array. Need to // 'Content-Security-Policy' MUST come last in the array. Need to
// revised this eventually. // revised this eventually.
var responseHeaders = []; var responseHeaders = [],
var value = channel.contentLength; value = channel.contentLength;
if ( value !== -1 ) { if ( value !== -1 ) {
responseHeaders.push({ name: 'Content-Length', value: value }); responseHeaders.push({ name: 'Content-Length', value: value });
} }
@ -2339,9 +2337,6 @@ vAPI.net = {};
/******************************************************************************/ /******************************************************************************/
vAPI.net.registerListeners = function() { vAPI.net.registerListeners = function() {
// Since it's not used
this.onBeforeSendHeaders = null;
if ( typeof this.onBeforeRequest.callback === 'function' ) { if ( typeof this.onBeforeRequest.callback === 'function' ) {
httpObserver.onBeforeRequest = this.onBeforeRequest.callback; httpObserver.onBeforeRequest = this.onBeforeRequest.callback;
httpObserver.onBeforeRequestTypes = this.onBeforeRequest.types ? httpObserver.onBeforeRequestTypes = this.onBeforeRequest.types ?

View File

@ -212,7 +212,7 @@
"description":"English: " "description":"English: "
}, },
"settingsHyperlinkAuditingDisabledPrompt":{ "settingsHyperlinkAuditingDisabledPrompt":{
"message":"Disable hyperlink auditing/beacon", "message":"Disable hyperlink auditing",
"description":"English: " "description":"English: "
}, },
"settingsWebRTCIPAddressHiddenPrompt":{ "settingsWebRTCIPAddressHiddenPrompt":{

View File

@ -313,6 +313,7 @@ PageStore.prototype.init = function(tabId) {
this.largeMediaCount = 0; this.largeMediaCount = 0;
this.largeMediaTimer = null; this.largeMediaTimer = null;
this.netFilteringCache = NetFilteringResultCache.factory(); this.netFilteringCache = NetFilteringResultCache.factory();
this.internalRedirectionCount = 0;
this.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) === true; this.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) === true;
if ( µb.logger.isEnabled() && this.noCosmeticFiltering ) { if ( µb.logger.isEnabled() && this.noCosmeticFiltering ) {
@ -614,10 +615,15 @@ PageStore.prototype.journalProcess = function(fromTimer) {
PageStore.prototype.filterRequest = function(context) { PageStore.prototype.filterRequest = function(context) {
var requestType = context.requestType; var requestType = context.requestType;
// We want to short-term cache filtering results of collapsible types,
// because they are likely to be reused, from network request handler and
// from content script handler.
if ( 'image sub_frame object'.indexOf(requestType) === -1 ) {
return this.filterRequestNoCache(context);
}
if ( this.getNetFilteringSwitch() === false ) { if ( this.getNetFilteringSwitch() === false ) {
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 ) { this.netFilteringCache.add(context, '');
this.netFilteringCache.add(context, '');
}
return ''; return '';
} }
@ -629,23 +635,13 @@ PageStore.prototype.filterRequest = function(context) {
var result = ''; var result = '';
if ( requestType === 'font' ) { // Dynamic URL filtering.
if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) {
result = µb.hnSwitches.toResultString();
}
this.remoteFontCount += 1;
}
if ( result === '' ) { if ( result === '' ) {
µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType); µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
result = µb.sessionURLFiltering.toFilterString(); result = µb.sessionURLFiltering.toFilterString();
} }
// Given that: // Dynamic hostname/type filtering.
// - Dynamic filtering override static filtering
// - Evaluating dynamic filtering is much faster than static filtering
// We evaluate dynamic filtering first, and hopefully we can skip
// evaluation of static filtering.
if ( result === '' && µb.userSettings.advancedUserEnabled ) { if ( result === '' && µb.userSettings.advancedUserEnabled ) {
µb.sessionFirewall.evaluateCellZY( context.rootHostname, context.requestHostname, requestType); µb.sessionFirewall.evaluateCellZY( context.rootHostname, context.requestHostname, requestType);
if ( µb.sessionFirewall.mustBlockOrAllow() ) { if ( µb.sessionFirewall.mustBlockOrAllow() ) {
@ -653,7 +649,7 @@ PageStore.prototype.filterRequest = function(context) {
} }
} }
// Static filtering never override dynamic filtering // Static filtering has lowest precedence.
if ( result === '' || result.charAt(1) === 'n' ) { if ( result === '' || result.charAt(1) === 'n' ) {
if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) { if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) {
result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled()); result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled());
@ -661,18 +657,11 @@ PageStore.prototype.filterRequest = function(context) {
} }
//console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL); //console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL);
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 ) { this.netFilteringCache.add(context, result);
this.netFilteringCache.add(context, result);
}
// console.debug('[%s, %s] = "%s"', context.requestHostname, requestType, result);
return result; return result;
}; };
// http://jsperf.com/string-indexof-vs-object
var collapsibleRequestTypes = 'image sub_frame object';
/******************************************************************************/ /******************************************************************************/
PageStore.prototype.filterRequestNoCache = function(context) { PageStore.prototype.filterRequestNoCache = function(context) {
@ -680,26 +669,27 @@ PageStore.prototype.filterRequestNoCache = function(context) {
return ''; return '';
} }
var requestType = context.requestType; var requestType = context.requestType,
var result = ''; result = '';
if ( requestType === 'font' ) { if ( requestType === 'csp_report' ) {
if ( this.internalRedirectionCount !== 0 ) {
result = 'gb:no-spurious-csp-report';
}
} else if ( requestType === 'font' ) {
if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) { if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) {
result = µb.hnSwitches.toResultString(); result = µb.hnSwitches.toResultString();
} }
this.remoteFontCount += 1; this.remoteFontCount += 1;
} }
// Dynamic URL filtering.
if ( result === '' ) { if ( result === '' ) {
µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType); µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
result = µb.sessionURLFiltering.toFilterString(); result = µb.sessionURLFiltering.toFilterString();
} }
// Given that: // Dynamic hostname/type filtering.
// - Dynamic filtering override static filtering
// - Evaluating dynamic filtering is much faster than static filtering
// We evaluate dynamic filtering first, and hopefully we can skip
// evaluation of static filtering.
if ( result === '' && µb.userSettings.advancedUserEnabled ) { if ( result === '' && µb.userSettings.advancedUserEnabled ) {
µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, requestType); µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, requestType);
if ( µb.sessionFirewall.mustBlockOrAllow() ) { if ( µb.sessionFirewall.mustBlockOrAllow() ) {
@ -707,7 +697,7 @@ PageStore.prototype.filterRequestNoCache = function(context) {
} }
} }
// Static filtering never override dynamic filtering // Static filtering has lowest precedence.
if ( result === '' || result.charAt(1) === 'n' ) { if ( result === '' || result.charAt(1) === 'n' ) {
if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) { if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) {
result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled()); result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled());

View File

@ -45,12 +45,6 @@ var onBeforeRequest = function(details) {
return onBeforeRootFrameRequest(details); return onBeforeRootFrameRequest(details);
} }
// https://github.com/gorhill/uBlock/issues/870
// This work for Chromium 49+.
if ( requestType === 'beacon' ) {
return onBeforeBeacon(details);
}
// Special treatment: behind-the-scene requests // Special treatment: behind-the-scene requests
var tabId = details.tabId; var tabId = details.tabId;
if ( vAPI.isBehindTheSceneTabId(tabId) ) { if ( vAPI.isBehindTheSceneTabId(tabId) ) {
@ -58,8 +52,8 @@ var onBeforeRequest = function(details) {
} }
// Lookup the page store associated with this tab id. // Lookup the page store associated with this tab id.
var µb = µBlock; var µb = µBlock,
var pageStore = µb.pageStoreFromTabId(tabId); pageStore = µb.pageStoreFromTabId(tabId);
if ( !pageStore ) { if ( !pageStore ) {
var tabContext = µb.tabContextManager.mustLookup(tabId); var tabContext = µb.tabContextManager.mustLookup(tabId);
if ( vAPI.isBehindTheSceneTabId(tabContext.tabId) ) { if ( vAPI.isBehindTheSceneTabId(tabContext.tabId) ) {
@ -119,6 +113,7 @@ var onBeforeRequest = function(details) {
// Redirect blocked request? // Redirect blocked request?
var url = µb.redirectEngine.toURL(requestContext); var url = µb.redirectEngine.toURL(requestContext);
if ( url !== undefined ) { if ( url !== undefined ) {
pageStore.internalRedirectionCount += 1;
if ( µb.logger.isEnabled() ) { if ( µb.logger.isEnabled() ) {
µb.logger.writeOne( µb.logger.writeOne(
tabId, tabId,
@ -277,62 +272,37 @@ var toBlockDocResult = function(url, hostname, result) {
/******************************************************************************/ /******************************************************************************/
// Intercept and filter behind-the-scene requests.
// https://github.com/gorhill/uBlock/issues/870 // https://github.com/gorhill/uBlock/issues/870
// Finally, Chromium 49+ gained the ability to report network request of type // Finally, Chromium 49+ gained the ability to report network request of type
// `beacon`, so now we can block them according to the state of the // `beacon`, so now we can block them according to the state of the
// "Disable hyperlink auditing/beacon" setting. // "Disable hyperlink auditing/beacon" setting.
var onBeforeBeacon = function(details) {
var µb = µBlock;
var tabId = details.tabId;
var pageStore = µb.mustPageStoreFromTabId(tabId);
var context = pageStore.createContextFromPage();
context.requestURL = details.url;
context.requestHostname = µb.URI.hostnameFromURI(details.url);
context.requestType = details.type;
// "g" in "gb:" stands for "global setting"
var result = µb.userSettings.hyperlinkAuditingDisabled ? 'gb:' : '';
pageStore.journalAddRequest(context.requestHostname, result);
if ( µb.logger.isEnabled() ) {
µb.logger.writeOne(
tabId,
'net',
result,
details.type,
details.url,
context.rootHostname,
context.rootHostname
);
}
context.dispose();
if ( result !== '' ) {
return { cancel: true };
}
};
/******************************************************************************/
// Intercept and filter behind-the-scene requests.
var onBeforeBehindTheSceneRequest = function(details) { var onBeforeBehindTheSceneRequest = function(details) {
var µb = µBlock; var µb = µBlock,
var pageStore = µb.pageStoreFromTabId(vAPI.noTabId); pageStore = µb.pageStoreFromTabId(vAPI.noTabId);
if ( !pageStore ) { if ( !pageStore ) { return; }
return;
} var result = '',
context = pageStore.createContextFromPage(),
requestType = details.type,
requestURL = details.url;
var context = pageStore.createContextFromPage();
var requestURL = details.url;
context.requestURL = requestURL; context.requestURL = requestURL;
context.requestHostname = µb.URI.hostnameFromURI(requestURL); context.requestHostname = µb.URI.hostnameFromURI(requestURL);
context.requestType = details.type; context.requestType = requestType;
// "g" in "gb:" stands for "global setting"
if ( requestType === 'beacon' && µb.userSettings.hyperlinkAuditingDisabled ) {
result = 'gb:no-hyperlink-auditing';
}
// Blocking behind-the-scene requests can break a lot of stuff: prevent // Blocking behind-the-scene requests can break a lot of stuff: prevent
// browser updates, prevent extension updates, prevent extensions from // browser updates, prevent extension updates, prevent extensions from
// working properly, etc. // working properly, etc.
// So we filter if and only if the "advanced user" mode is selected // So we filter if and only if the "advanced user" mode is selected
var result = ''; if ( result === '' && µb.userSettings.advancedUserEnabled ) {
if ( µb.userSettings.advancedUserEnabled ) {
result = pageStore.filterRequestNoCache(context); result = pageStore.filterRequestNoCache(context);
} }
@ -343,7 +313,7 @@ var onBeforeBehindTheSceneRequest = function(details) {
vAPI.noTabId, vAPI.noTabId,
'net', 'net',
result, result,
details.type, requestType,
requestURL, requestURL,
context.rootHostname, context.rootHostname,
context.rootHostname context.rootHostname