mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-20 13:03:56 +02:00
refactor static network filtering, add support for csp injection
This commit is contained in:
parent
8570b63bef
commit
0232382695
18 changed files with 1844 additions and 1771 deletions
|
@ -35,7 +35,13 @@ var chrome = self.chrome;
|
|||
var manifest = chrome.runtime.getManifest();
|
||||
|
||||
vAPI.chrome = true;
|
||||
vAPI.cantWebsocket = true;
|
||||
vAPI.chromiumVersion = (function(){
|
||||
var matches = /\bChrom(?:e|ium)\/(\d+)\b/.exec(navigator.userAgent);
|
||||
return matches !== null ? parseInt(matches[1], 10) : NaN;
|
||||
})();
|
||||
vAPI.cantWebsocket =
|
||||
chrome.webRequest.ResourceType instanceof Object === false ||
|
||||
chrome.webRequest.ResourceType.WEBSOCKET !== 'websocket';
|
||||
|
||||
var noopFunc = function(){};
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ body.colorBlind #netInspector tr.nooped {
|
|||
body.colorBlind #netInspector tr.allowed {
|
||||
background-color: rgba(255, 194, 57, 0.1)
|
||||
}
|
||||
#netInspector tr.cb, #netInspector tr.rr {
|
||||
#netInspector tr.cosmetic, #netInspector tr.redirect {
|
||||
background-color: rgba(255, 255, 0, 0.1);
|
||||
}
|
||||
#netInspector tr.maindoc {
|
||||
|
|
|
@ -439,40 +439,40 @@ body.advancedUser #firewallContainer > div > span:first-of-type ~ span {
|
|||
background-color: rgb(192, 160, 0);
|
||||
}
|
||||
/* Rule cells */
|
||||
body.advancedUser #firewallContainer > div > span.aRule {
|
||||
body.advancedUser #firewallContainer > div > span.allowRule {
|
||||
background-color: rgba(0, 160, 0, 0.3);
|
||||
}
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.aRule {
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.allowRule {
|
||||
background-color: rgba(255, 194, 57, 0.4);
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.bRule {
|
||||
body.advancedUser #firewallContainer > div > span.blockRule {
|
||||
background-color: rgba(192, 0, 0, 0.3);
|
||||
}
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.bRule {
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.blockRule {
|
||||
background-color: rgba(0, 19, 110, 0.4);
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.nRule {
|
||||
body.advancedUser #firewallContainer > div > span.noopRule {
|
||||
background-color: rgba(108, 108, 108, 0.3);
|
||||
}
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.nRule {
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.noopRule {
|
||||
background-color: rgba(96, 96, 96, 0.4);
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.ownRule {
|
||||
color: white;
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.aRule.ownRule {
|
||||
body.advancedUser #firewallContainer > div > span.allowRule.ownRule {
|
||||
background-color: rgba(0, 160, 0, 1);
|
||||
}
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.aRule.ownRule {
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.allowRule.ownRule {
|
||||
background-color: rgba(255, 194, 57, 1);
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.bRule.ownRule {
|
||||
body.advancedUser #firewallContainer > div > span.blockRule.ownRule {
|
||||
background-color: rgba(192, 0, 0, 1);
|
||||
}
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.bRule.ownRule {
|
||||
body.advancedUser #firewallContainer.colorBlind > div > span.blockRule.ownRule {
|
||||
background-color: rgba(0, 19, 110, 1);
|
||||
}
|
||||
body.advancedUser #firewallContainer > div > span.nRule.ownRule {
|
||||
body.advancedUser #firewallContainer > div > span.noopRule.ownRule {
|
||||
background-color: rgba(108, 108, 108, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -121,8 +121,8 @@ var µBlock = (function() { // jshint ignore:line
|
|||
|
||||
// read-only
|
||||
systemSettings: {
|
||||
compiledMagic: 'fxtcjjhbhyiw',
|
||||
selfieMagic: 'fxtcjjhbhyiw'
|
||||
compiledMagic: 'lcmfjiajoqwe',
|
||||
selfieMagic: 'lcmfjiajoqwe'
|
||||
},
|
||||
|
||||
restoreBackupSettings: {
|
||||
|
@ -170,4 +170,3 @@ var µBlock = (function() { // jshint ignore:line
|
|||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -719,6 +719,8 @@ FilterContainer.prototype.freeze = function() {
|
|||
this.highHighComplexGenericHideCount !== 0;
|
||||
|
||||
this.parser.reset();
|
||||
this.compileSelector.reset();
|
||||
this.compileProceduralSelector.reset();
|
||||
this.frozen = true;
|
||||
};
|
||||
|
||||
|
@ -746,7 +748,7 @@ FilterContainer.prototype.compileSelector = (function() {
|
|||
return true;
|
||||
};
|
||||
|
||||
return function(raw) {
|
||||
var entryPoint = function(raw) {
|
||||
if ( isValidCSSSelector(raw) && raw.indexOf('[-abp-properties=') === -1 ) {
|
||||
return raw;
|
||||
}
|
||||
|
@ -812,6 +814,11 @@ FilterContainer.prototype.compileSelector = (function() {
|
|||
|
||||
µb.logger.writeOne('', 'error', 'Cosmetic filtering – invalid filter: ' + raw);
|
||||
};
|
||||
|
||||
entryPoint.reset = function() {
|
||||
};
|
||||
|
||||
return entryPoint;
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -927,7 +934,7 @@ FilterContainer.prototype.compileProceduralSelector = (function() {
|
|||
return { selector: firstOperand, tasks: tasks };
|
||||
};
|
||||
|
||||
return function(raw) {
|
||||
var entryPoint = function(raw) {
|
||||
if ( raw === lastProceduralSelector ) {
|
||||
return lastProceduralSelectorCompiled;
|
||||
}
|
||||
|
@ -940,6 +947,13 @@ FilterContainer.prototype.compileProceduralSelector = (function() {
|
|||
lastProceduralSelectorCompiled = compiled;
|
||||
return compiled;
|
||||
};
|
||||
|
||||
entryPoint.reset = function() {
|
||||
lastProceduralSelector = '';
|
||||
lastProceduralSelectorCompiled = undefined;
|
||||
};
|
||||
|
||||
return entryPoint;
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -1038,12 +1052,12 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
|
|||
// is valid, the regex took care of this. Most generic selector falls
|
||||
// into that category.
|
||||
if ( key === selector ) {
|
||||
out.push('c\vlg\v' + key);
|
||||
out.push(4, 'lg\v' + key);
|
||||
return;
|
||||
}
|
||||
// Composite CSS rule.
|
||||
if ( this.compileSelector(selector) ) {
|
||||
out.push('c\vlg+\v' + key + '\v' + selector);
|
||||
out.push(4, 'lg+\v' + key + '\v' + selector);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1054,21 +1068,21 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
|
|||
|
||||
// ["title"] and ["alt"] will go in high-low generic bin.
|
||||
if ( this.reHighLow.test(selector) ) {
|
||||
out.push('c\vhlg0\v' + selector);
|
||||
out.push(4, 'hlg0\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
// [href^="..."] will go in high-medium generic bin.
|
||||
matches = this.reHighMedium.exec(selector);
|
||||
if ( matches && matches.length === 2 ) {
|
||||
out.push('c\vhmg0\v' + matches[1] + '\v' + selector);
|
||||
out.push(4, 'hmg0\v' + matches[1] + '\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
// script:contains(...)
|
||||
// script:inject(...)
|
||||
if ( this.reScriptSelector.test(selector) ) {
|
||||
out.push('c\vjs\v0\v\v' + selector);
|
||||
out.push(4, 'js\v0\v\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1077,16 +1091,16 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
|
|||
// as a low generic cosmetic filter.
|
||||
matches = this.rePlainSelectorEx.exec(selector);
|
||||
if ( matches && matches.length === 2 ) {
|
||||
out.push('c\vlg+\v' + matches[1] + '\v' + selector);
|
||||
out.push(4, 'lg+\v' + matches[1] + '\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
// All else: high-high generics.
|
||||
// Distinguish simple vs complex selectors.
|
||||
if ( selector.indexOf(' ') === -1 ) {
|
||||
out.push('c\vhhsg0\v' + selector);
|
||||
out.push(4, 'hhsg0\v' + selector);
|
||||
} else {
|
||||
out.push('c\vhhcg0\v' + selector);
|
||||
out.push(4, 'hhcg0\v' + selector);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1098,7 +1112,7 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
|
|||
// script:contains(...)
|
||||
// script:inject(...)
|
||||
if ( this.reScriptSelector.test(selector) ) {
|
||||
out.push('c\vjs\v1\v\v' + selector);
|
||||
out.push(4, 'js\v1\v\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1109,7 +1123,7 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
|
|||
// https://github.com/chrisaljoudi/uBlock/issues/497
|
||||
// All generic exception filters are put in the same bucket: they are
|
||||
// expected to be very rare.
|
||||
out.push('c\vg1\v' + compiled);
|
||||
out.push(4, 'g1\v' + compiled);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -1138,7 +1152,7 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
|
|||
if ( unhide ) {
|
||||
hash = '!' + hash;
|
||||
}
|
||||
out.push('c\vjs\v' + hash + '\v' + hostname + '\v' + selector);
|
||||
out.push(4, 'js\v' + hash + '\v' + hostname + '\v' + selector);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1156,12 +1170,16 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
|
|||
hash = '!' + hash;
|
||||
}
|
||||
|
||||
out.push('c\vh\v' + hash + '\v' + hostname + '\v' + compiled);
|
||||
out.push(4, 'h\v' + hash + '\v' + hostname + '\v' + compiled);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.fromCompiledContent = function(lineIter, skipGenericCosmetic, skipCosmetic) {
|
||||
FilterContainer.prototype.fromCompiledContent = function(
|
||||
lineIter,
|
||||
skipGenericCosmetic,
|
||||
skipCosmetic
|
||||
) {
|
||||
if ( skipCosmetic ) {
|
||||
this.skipCompiledContent(lineIter);
|
||||
return;
|
||||
|
@ -1171,15 +1189,19 @@ FilterContainer.prototype.fromCompiledContent = function(lineIter, skipGenericCo
|
|||
return;
|
||||
}
|
||||
|
||||
var line, field0, field1, field2, field3, filter, bucket,
|
||||
var lineBits, line, field0, field1, field2, field3, filter, bucket,
|
||||
aCharCode = 'a'.charCodeAt(0),
|
||||
fieldIter = new µb.FieldIterator('\v');
|
||||
|
||||
while ( lineIter.eot() === false ) {
|
||||
line = lineIter.next();
|
||||
if ( line.charCodeAt(0) !== 0x63 /* 'c' */ ) {
|
||||
lineIter.rewind();
|
||||
lineBits = lineIter.charCodeAt(0) - aCharCode;
|
||||
if ( (lineBits & 0x04) === 0 ) {
|
||||
return;
|
||||
}
|
||||
line = lineIter.next(1);
|
||||
if ( (lineBits & 0x02) !== 0 ) {
|
||||
line = decodeURIComponent(line);
|
||||
}
|
||||
|
||||
this.acceptedCount += 1;
|
||||
if ( this.duplicateBuster.has(line) ) {
|
||||
|
@ -1188,8 +1210,7 @@ FilterContainer.prototype.fromCompiledContent = function(lineIter, skipGenericCo
|
|||
}
|
||||
this.duplicateBuster.add(line);
|
||||
|
||||
fieldIter.first(line);
|
||||
field0 = fieldIter.next();
|
||||
field0 = fieldIter.first(line);
|
||||
field1 = fieldIter.next();
|
||||
|
||||
// h [\v] hash [\v] example.com [\v] .promoted-tweet
|
||||
|
@ -1298,15 +1319,19 @@ FilterContainer.prototype.fromCompiledContent = function(lineIter, skipGenericCo
|
|||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.skipGenericCompiledContent = function(lineIter) {
|
||||
var line, field0, field1, field2, field3, filter, bucket,
|
||||
var lineBits, line, field0, field1, field2, field3, filter, bucket,
|
||||
aCharCode = 'a'.charCodeAt(0),
|
||||
fieldIter = new µb.FieldIterator('\v');
|
||||
|
||||
while ( lineIter.eot() === false ) {
|
||||
line = lineIter.next();
|
||||
if ( line.charCodeAt(0) !== 0x63 /* 'c' */ ) {
|
||||
lineIter.rewind();
|
||||
lineBits = lineIter.charCodeAt(0) - aCharCode;
|
||||
if ( (lineBits & 0x04) === 0 ) {
|
||||
return;
|
||||
}
|
||||
line = lineIter.next(1);
|
||||
if ( (lineBits & 0x02) !== 0 ) {
|
||||
line = decodeURIComponent(line);
|
||||
}
|
||||
|
||||
this.acceptedCount += 1;
|
||||
if ( this.duplicateBuster.has(line) ) {
|
||||
|
@ -1361,15 +1386,19 @@ FilterContainer.prototype.skipGenericCompiledContent = function(lineIter) {
|
|||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.skipCompiledContent = function(lineIter) {
|
||||
var line, field0, field1, field2, field3,
|
||||
var lineBits, line, field0, field1, field2, field3,
|
||||
aCharCode = 'a'.charCodeAt(0),
|
||||
fieldIter = new µb.FieldIterator('\v');
|
||||
|
||||
while ( lineIter.eot() === false ) {
|
||||
line = lineIter.next();
|
||||
if ( line.charCodeAt(0) !== 0x63 /* 'c' */ ) {
|
||||
lineIter.rewind();
|
||||
lineBits = lineIter.charCodeAt(0) - aCharCode;
|
||||
if ( (lineBits & 0x04) === 0 ) {
|
||||
return;
|
||||
}
|
||||
line = lineIter.next(1);
|
||||
if ( (lineBits & 0x02) !== 0 ) {
|
||||
line = decodeURIComponent(line);
|
||||
}
|
||||
|
||||
this.acceptedCount += 1;
|
||||
if ( this.duplicateBuster.has(line) ) {
|
||||
|
|
|
@ -337,7 +337,7 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
|||
var d = desHostname;
|
||||
if ( d === '' ) {
|
||||
this.r = 0;
|
||||
return this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare broadening handlers -- depends on whether we are dealing with
|
||||
|
@ -350,7 +350,9 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
|||
// Specific-destination, any party, any type
|
||||
while ( d !== '*' ) {
|
||||
this.y = d;
|
||||
if ( this.evaluateCellZ(srcHostname, d, '*', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, d, '*', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
d = broadenDestination(d);
|
||||
}
|
||||
|
||||
|
@ -363,27 +365,39 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
|||
if ( thirdParty ) {
|
||||
// 3rd-party, specific type
|
||||
if ( type === 'script' ) {
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-script', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-script', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
} else if ( type === 'sub_frame' ) {
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-frame', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-frame', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
}
|
||||
// 3rd-party, any type
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '3p', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
} else if ( type === 'script' ) {
|
||||
// 1st party, specific type
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '1p-script', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '1p-script', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
}
|
||||
|
||||
// Any destination, any party, specific type
|
||||
if ( supportedDynamicTypes.hasOwnProperty(type) ) {
|
||||
if ( this.evaluateCellZ(srcHostname, '*', type, broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', type, broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
}
|
||||
|
||||
// Any destination, any party, any type
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '*', broadenSource) !== 0 ) { return this; }
|
||||
if ( this.evaluateCellZ(srcHostname, '*', '*', broadenSource) !== 0 ) {
|
||||
return this.r;
|
||||
}
|
||||
|
||||
this.type = '';
|
||||
return this;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// http://youtu.be/gSGk1bQ9rcU?t=25m6s
|
||||
|
@ -391,7 +405,7 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
|||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.mustAllowCellZY = function(srcHostname, desHostname, type) {
|
||||
return this.evaluateCellZY(srcHostname, desHostname, type).r === 2;
|
||||
return this.evaluateCellZY(srcHostname, desHostname, type) === 2;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -414,23 +428,44 @@ Matrix.prototype.mustAbort = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.toFilterString = function() {
|
||||
if ( this.r === 0 || this.type === '' ) {
|
||||
return '';
|
||||
Matrix.prototype.lookupRuleData = function(src, des, type) {
|
||||
var r = this.evaluateCellZY(src, des, type);
|
||||
if ( r === 0 ) {
|
||||
return null;
|
||||
}
|
||||
var body = this.z + ' ' + this.y + ' ' + this.type;
|
||||
if ( this.r === 1 ) {
|
||||
return 'db:' + body + ' block';
|
||||
}
|
||||
if ( this.r === 2 ) {
|
||||
return 'da:' + body + ' allow';
|
||||
}
|
||||
/* this.r === 3 */
|
||||
return 'dn:' + body + ' noop';
|
||||
return {
|
||||
src: this.z,
|
||||
des: this.y,
|
||||
type: this.type,
|
||||
action: r === 1 ? 'block' : (r === 2 ? 'allow' : 'noop')
|
||||
};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.toLogData = function() {
|
||||
if ( this.r === 0 || this.type === '' ) {
|
||||
return;
|
||||
}
|
||||
var logData = {
|
||||
source: 'dynamicHost',
|
||||
result: this.r,
|
||||
raw: this.z + ' ' +
|
||||
this.y + ' ' +
|
||||
this.type + ' ' +
|
||||
this.intToActionMap.get(this.r)
|
||||
};
|
||||
return logData;
|
||||
};
|
||||
|
||||
Matrix.prototype.intToActionMap = new Map([
|
||||
[ 1, ' block' ],
|
||||
[ 2, ' allow' ],
|
||||
[ 3, ' noop' ]
|
||||
]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.srcHostnameFromRule = function(rule) {
|
||||
return rule.slice(0, rule.indexOf(' '));
|
||||
};
|
||||
|
|
|
@ -249,10 +249,12 @@ HnSwitches.prototype.evaluateZ = function(switchName, hostname) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
HnSwitches.prototype.toResultString = function() {
|
||||
return this.r !== 1 ?
|
||||
'' :
|
||||
'ub:' + this.n + ': ' + this.z + ' true';
|
||||
HnSwitches.prototype.toLogData = function() {
|
||||
return {
|
||||
source: 'switch',
|
||||
result: this.r,
|
||||
raw: this.n + ': ' + this.z + ' true'
|
||||
};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2016 Raymond Hill
|
||||
Copyright (C) 2015-2017 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
|
||||
|
@ -92,9 +92,11 @@ var uglyRequestTypes = {
|
|||
};
|
||||
|
||||
var staticFilterTypes = {
|
||||
'beacon': 'other',
|
||||
'doc': 'document',
|
||||
'css': 'stylesheet',
|
||||
'frame': 'subdocument',
|
||||
'ping': 'other',
|
||||
'xhr': 'xmlhttprequest'
|
||||
};
|
||||
|
||||
|
@ -146,212 +148,6 @@ var renderedURLTemplate = document.querySelector('#renderedURLTemplate > span');
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// Pretty much same logic as found in:
|
||||
// µBlock.staticNetFilteringEngine.filterStringFromCompiled
|
||||
// µBlock.staticNetFilteringEngine.filterRegexFromCompiled
|
||||
|
||||
var filterDecompiler = (function() {
|
||||
var typeValToTypeName = {
|
||||
1: 'stylesheet',
|
||||
2: 'image',
|
||||
3: 'object',
|
||||
4: 'script',
|
||||
5: 'xmlhttprequest',
|
||||
6: 'subdocument',
|
||||
7: 'font',
|
||||
8: 'media',
|
||||
9: 'websocket',
|
||||
10: 'other',
|
||||
11: 'popunder',
|
||||
12: 'document',
|
||||
13: 'generichide',
|
||||
14: 'inline-script',
|
||||
15: 'popup'
|
||||
};
|
||||
|
||||
var toString = function(compiled) {
|
||||
var opts = [];
|
||||
var vfields = compiled.split('\v');
|
||||
var filter = '';
|
||||
var bits = parseInt(vfields[0], 16) | 0;
|
||||
|
||||
if ( bits & 0x01 ) {
|
||||
filter += '@@';
|
||||
}
|
||||
|
||||
var fid = vfields[1] === '.' ? '.' : vfields[2];
|
||||
var tfields = fid !== '.' ? vfields[3].split('\t') : [];
|
||||
var tfield0 = tfields[0];
|
||||
|
||||
// Filter options
|
||||
// Importance
|
||||
if ( bits & 0x02 ) {
|
||||
opts.push('important');
|
||||
}
|
||||
// Party
|
||||
if ( bits & 0x08 ) {
|
||||
opts.push('third-party');
|
||||
} else if ( bits & 0x04 ) {
|
||||
opts.push('first-party');
|
||||
}
|
||||
// Type
|
||||
var typeVal = bits >>> 4 & 0x0F;
|
||||
if ( typeVal ) {
|
||||
opts.push(typeValToTypeName[typeVal]);
|
||||
}
|
||||
|
||||
switch ( fid ) {
|
||||
case '.':
|
||||
filter += '||' + vfields[2] + '^';
|
||||
break;
|
||||
case 'a':
|
||||
case 'ah':
|
||||
case '0a':
|
||||
case '0ah':
|
||||
case '1a':
|
||||
case '1ah':
|
||||
case '_':
|
||||
case '_h':
|
||||
filter += tfield0;
|
||||
// If the filter resemble a regex, add a trailing `*` as is
|
||||
// customary to prevent ambiguity in logger.
|
||||
if ( tfield0.charAt(0) === '/' && tfield0.slice(-1) === '/' ) {
|
||||
filter += '*';
|
||||
}
|
||||
break;
|
||||
case '|a':
|
||||
case '|ah':
|
||||
filter += '|' + tfield0;
|
||||
break;
|
||||
case 'a|':
|
||||
case 'a|h':
|
||||
filter += tfield0 + '|';
|
||||
break;
|
||||
case '||a':
|
||||
case '||ah':
|
||||
filter += '||' + tfield0;
|
||||
break;
|
||||
case '||_':
|
||||
case '||_h':
|
||||
filter += '||' + tfield0;
|
||||
if ( tfields[1] === '1' ) { // left-anchored?
|
||||
filter += '|';
|
||||
}
|
||||
break;
|
||||
case '//':
|
||||
case '//h':
|
||||
filter += '/' + tfield0 + '/';
|
||||
break;
|
||||
// https://github.com/gorhill/uBlock/issues/465
|
||||
// Unexpected: return the raw compiled representation instead of a
|
||||
// blank string.
|
||||
default:
|
||||
return compiled.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
// Domain option?
|
||||
switch ( fid ) {
|
||||
case '0ah':
|
||||
case '1ah':
|
||||
case '|ah':
|
||||
case 'a|h':
|
||||
case '||ah':
|
||||
case '//h':
|
||||
opts.push('domain=' + tfields[1]);
|
||||
break;
|
||||
case 'ah':
|
||||
case '_h':
|
||||
case '||_h':
|
||||
opts.push('domain=' + tfields[2]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ( opts.length !== 0 ) {
|
||||
filter += '$' + opts.join(',');
|
||||
}
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
var reEscapeHostname = /[.[\]]/g;
|
||||
var reEscape = /[.+?${}()|[\]\\]/g;
|
||||
var reWildcards = /\*+/g;
|
||||
var reSeparator = /\^/g;
|
||||
|
||||
var toRegex = function(compiled) {
|
||||
var vfields = compiled.split('\v');
|
||||
var fid = vfields[1] === '.' ? '.' : vfields[2];
|
||||
var tfields = fid !== '.' ? vfields[3].split('\t') : [];
|
||||
var reStr;
|
||||
|
||||
switch ( fid ) {
|
||||
case '.':
|
||||
reStr = vfields[2].replace(reEscapeHostname, '\\$&') +
|
||||
'(?:[^%.0-9a-z_-]|$)';
|
||||
break;
|
||||
case 'a':
|
||||
case 'ah':
|
||||
case '0a':
|
||||
case '0ah':
|
||||
case '1a':
|
||||
case '1ah':
|
||||
case '|a':
|
||||
case '|ah':
|
||||
case 'a|':
|
||||
case 'a|h':
|
||||
case '_':
|
||||
case '_h':
|
||||
reStr = tfields[0]
|
||||
.replace(reEscape, '\\$&')
|
||||
.replace(reWildcards, '.*?')
|
||||
.replace(reSeparator, '(?:[^%.0-9a-z_-]|$)');
|
||||
break;
|
||||
case '||a':
|
||||
case '||ah':
|
||||
case '||_':
|
||||
case '||_h':
|
||||
reStr = '';
|
||||
if ( tfields[0].charCodeAt(0) === 0x2A ) {
|
||||
reStr = '[0-9a-z.-]*?';
|
||||
tfields[0] = tfields[0].slice(1);
|
||||
}
|
||||
reStr += tfields[0]
|
||||
.replace(reEscape, '\\$&')
|
||||
.replace(reWildcards, '.*?')
|
||||
.replace(reSeparator, '(?:[^%.0-9a-z_-]|$)');
|
||||
break;
|
||||
case '//':
|
||||
case '//h':
|
||||
reStr = tfields[0];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Anchored?
|
||||
var s = fid.slice(0, 2);
|
||||
if ( s === '|a' ) {
|
||||
reStr = '^' + reStr;
|
||||
} else if ( s === 'a|' ) {
|
||||
reStr += '$';
|
||||
}
|
||||
|
||||
if ( reStr === undefined) {
|
||||
return null;
|
||||
}
|
||||
return new RegExp(reStr, 'gi');
|
||||
};
|
||||
|
||||
return {
|
||||
toString: toString,
|
||||
toRegex: toRegex
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var createCellAt = function(tr, index) {
|
||||
var td = tr.cells[index];
|
||||
var mustAppend = !td;
|
||||
|
@ -442,7 +238,7 @@ var createGap = function(tabId, url) {
|
|||
|
||||
var renderNetLogEntry = function(tr, entry) {
|
||||
var trcl = tr.classList;
|
||||
var filter = entry.d0;
|
||||
var filter = entry.d0 || undefined;
|
||||
var type = entry.d1;
|
||||
var url = entry.d2;
|
||||
var td;
|
||||
|
@ -463,52 +259,50 @@ var renderNetLogEntry = function(tr, entry) {
|
|||
tr.setAttribute('data-hn-frame', entry.d4);
|
||||
}
|
||||
|
||||
var filterCat = filter.slice(0, 3);
|
||||
if ( filterCat.charAt(2) === ':' ) {
|
||||
trcl.add(filterCat.slice(0, 2));
|
||||
var filteringType;
|
||||
if ( filter !== undefined && typeof filter.source === 'string' ) {
|
||||
filteringType = filter.source;
|
||||
trcl.add(filteringType);
|
||||
}
|
||||
|
||||
var filteringType = filterCat.charAt(0);
|
||||
td = tr.cells[2];
|
||||
if ( filter !== '' ) {
|
||||
filter = filter.slice(3);
|
||||
if ( filteringType === 's' ) {
|
||||
td.textContent = filterDecompiler.toString(filter);
|
||||
if ( filter !== undefined ) {
|
||||
if ( filteringType === 'static' ) {
|
||||
td.textContent = filter.raw;
|
||||
trcl.add('canLookup');
|
||||
tr.setAttribute('data-filter', filter);
|
||||
} else if ( filteringType === 'c' ) {
|
||||
td.textContent = filter;
|
||||
tr.setAttribute('data-filter', filter.compiled);
|
||||
} else if ( filteringType === 'cosmetic' ) {
|
||||
td.textContent = filter.raw;
|
||||
trcl.add('canLookup');
|
||||
} else {
|
||||
td.textContent = filter;
|
||||
td.textContent = filter.raw;
|
||||
}
|
||||
}
|
||||
|
||||
td = tr.cells[3];
|
||||
var filteringOp = filterCat.charAt(1);
|
||||
if ( filteringOp === 'b' ) {
|
||||
trcl.add('blocked');
|
||||
td.textContent = '--';
|
||||
} else if ( filteringOp === 'a' ) {
|
||||
trcl.add('allowed');
|
||||
td.textContent = '++';
|
||||
} else if ( filteringOp === 'n' ) {
|
||||
trcl.add('nooped');
|
||||
td.textContent = '**';
|
||||
} else if ( filteringOp === 'r' ) {
|
||||
trcl.add('redirected');
|
||||
td.textContent = '<<';
|
||||
} else {
|
||||
td.textContent = '';
|
||||
if ( filter !== undefined ) {
|
||||
if ( filter.result === 1 ) {
|
||||
trcl.add('blocked');
|
||||
td.textContent = '--';
|
||||
} else if ( filter.result === 2 ) {
|
||||
trcl.add('allowed');
|
||||
td.textContent = '++';
|
||||
} else if ( filter.result === 3 ) {
|
||||
trcl.add('nooped');
|
||||
td.textContent = '**';
|
||||
} else if ( filter.source === 'redirect' ) {
|
||||
trcl.add('redirect');
|
||||
td.textContent = '<<';
|
||||
}
|
||||
}
|
||||
|
||||
tr.cells[4].textContent = (prettyRequestTypes[type] || type);
|
||||
|
||||
var re = null;
|
||||
if ( filteringType === 's' ) {
|
||||
re = filterDecompiler.toRegex(filter);
|
||||
} else if ( filteringType === 'l' ) {
|
||||
re = regexFromURLFilteringResult(filter);
|
||||
if ( filteringType === 'static' ) {
|
||||
re = new RegExp(filter.regex, 'gi');
|
||||
} else if ( filteringType === 'dynamicUrl' ) {
|
||||
re = regexFromURLFilteringResult(filter.rule.join(' '));
|
||||
}
|
||||
tr.cells[5].appendChild(nodeFromURL(url, re));
|
||||
};
|
||||
|
|
|
@ -258,28 +258,28 @@ var getHostnameDict = function(hostnameToCountMap) {
|
|||
var getFirewallRules = function(srcHostname, desHostnames) {
|
||||
var r = {};
|
||||
var df = µb.sessionFirewall;
|
||||
r['/ * *'] = df.evaluateCellZY('*', '*', '*').toFilterString();
|
||||
r['/ * image'] = df.evaluateCellZY('*', '*', 'image').toFilterString();
|
||||
r['/ * 3p'] = df.evaluateCellZY('*', '*', '3p').toFilterString();
|
||||
r['/ * inline-script'] = df.evaluateCellZY('*', '*', 'inline-script').toFilterString();
|
||||
r['/ * 1p-script'] = df.evaluateCellZY('*', '*', '1p-script').toFilterString();
|
||||
r['/ * 3p-script'] = df.evaluateCellZY('*', '*', '3p-script').toFilterString();
|
||||
r['/ * 3p-frame'] = df.evaluateCellZY('*', '*', '3p-frame').toFilterString();
|
||||
r['/ * *'] = df.lookupRuleData('*', '*', '*');
|
||||
r['/ * image'] = df.lookupRuleData('*', '*', 'image');
|
||||
r['/ * 3p'] = df.lookupRuleData('*', '*', '3p');
|
||||
r['/ * inline-script'] = df.lookupRuleData('*', '*', 'inline-script');
|
||||
r['/ * 1p-script'] = df.lookupRuleData('*', '*', '1p-script');
|
||||
r['/ * 3p-script'] = df.lookupRuleData('*', '*', '3p-script');
|
||||
r['/ * 3p-frame'] = df.lookupRuleData('*', '*', '3p-frame');
|
||||
if ( typeof srcHostname !== 'string' ) {
|
||||
return r;
|
||||
}
|
||||
|
||||
r['. * *'] = df.evaluateCellZY(srcHostname, '*', '*').toFilterString();
|
||||
r['. * image'] = df.evaluateCellZY(srcHostname, '*', 'image').toFilterString();
|
||||
r['. * 3p'] = df.evaluateCellZY(srcHostname, '*', '3p').toFilterString();
|
||||
r['. * inline-script'] = df.evaluateCellZY(srcHostname, '*', 'inline-script').toFilterString();
|
||||
r['. * 1p-script'] = df.evaluateCellZY(srcHostname, '*', '1p-script').toFilterString();
|
||||
r['. * 3p-script'] = df.evaluateCellZY(srcHostname, '*', '3p-script').toFilterString();
|
||||
r['. * 3p-frame'] = df.evaluateCellZY(srcHostname, '*', '3p-frame').toFilterString();
|
||||
r['. * *'] = df.lookupRuleData(srcHostname, '*', '*');
|
||||
r['. * image'] = df.lookupRuleData(srcHostname, '*', 'image');
|
||||
r['. * 3p'] = df.lookupRuleData(srcHostname, '*', '3p');
|
||||
r['. * inline-script'] = df.lookupRuleData(srcHostname, '*', 'inline-script');
|
||||
r['. * 1p-script'] = df.lookupRuleData(srcHostname, '*', '1p-script');
|
||||
r['. * 3p-script'] = df.lookupRuleData(srcHostname, '*', '3p-script');
|
||||
r['. * 3p-frame'] = df.lookupRuleData(srcHostname, '*', '3p-frame');
|
||||
|
||||
for ( var desHostname in desHostnames ) {
|
||||
r['/ ' + desHostname + ' *'] = df.evaluateCellZY('*', desHostname, '*').toFilterString();
|
||||
r['. ' + desHostname + ' *'] = df.evaluateCellZY(srcHostname, desHostname, '*').toFilterString();
|
||||
r['/ ' + desHostname + ' *'] = df.lookupRuleData('*', desHostname, '*');
|
||||
r['. ' + desHostname + ' *'] = df.lookupRuleData(srcHostname, desHostname, '*');
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
@ -1239,7 +1239,7 @@ var logCosmeticFilters = function(tabId, details) {
|
|||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'cosmetic',
|
||||
'cb:##' + selectors[i],
|
||||
{ source: 'cosmetic', raw: '##' + selectors[i] },
|
||||
'dom',
|
||||
details.frameURL,
|
||||
null,
|
||||
|
|
|
@ -48,16 +48,17 @@ var netFilteringResultCacheEntryJunkyardMax = 200;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var NetFilteringResultCacheEntry = function(result, type) {
|
||||
this.init(result, type);
|
||||
var NetFilteringResultCacheEntry = function(result, type, logData) {
|
||||
this.init(result, type, logData);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
NetFilteringResultCacheEntry.prototype.init = function(result, type) {
|
||||
NetFilteringResultCacheEntry.prototype.init = function(result, type, logData) {
|
||||
this.result = result;
|
||||
this.type = type;
|
||||
this.time = Date.now();
|
||||
this.logData = logData;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -65,6 +66,7 @@ NetFilteringResultCacheEntry.prototype.init = function(result, type) {
|
|||
|
||||
NetFilteringResultCacheEntry.prototype.dispose = function() {
|
||||
this.result = this.type = '';
|
||||
this.logData = undefined;
|
||||
if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) {
|
||||
netFilteringResultCacheEntryJunkyard.push(this);
|
||||
}
|
||||
|
@ -72,11 +74,11 @@ NetFilteringResultCacheEntry.prototype.dispose = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
NetFilteringResultCacheEntry.factory = function(result, type) {
|
||||
NetFilteringResultCacheEntry.factory = function(result, type, logData) {
|
||||
if ( netFilteringResultCacheEntryJunkyard.length ) {
|
||||
return netFilteringResultCacheEntryJunkyard.pop().init(result, type);
|
||||
return netFilteringResultCacheEntryJunkyard.pop().init(result, type, logData);
|
||||
}
|
||||
return new NetFilteringResultCacheEntry(result, type);
|
||||
return new NetFilteringResultCacheEntry(result, type, logData);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -127,7 +129,7 @@ NetFilteringResultCache.prototype.dispose = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
NetFilteringResultCache.prototype.add = function(context, result) {
|
||||
NetFilteringResultCache.prototype.add = function(context, result, logData) {
|
||||
var url = context.requestURL,
|
||||
type = context.requestType,
|
||||
key = type + ' ' + url,
|
||||
|
@ -136,9 +138,10 @@ NetFilteringResultCache.prototype.add = function(context, result) {
|
|||
entry.result = result;
|
||||
entry.type = type;
|
||||
entry.time = Date.now();
|
||||
entry.logData = logData;
|
||||
return;
|
||||
}
|
||||
this.urls[key] = NetFilteringResultCacheEntry.factory(result, type);
|
||||
this.urls[key] = NetFilteringResultCacheEntry.factory(result, type, logData);
|
||||
if ( this.count === 0 ) {
|
||||
this.pruneAsync();
|
||||
}
|
||||
|
@ -305,6 +308,7 @@ PageStore.prototype.init = function(tabId) {
|
|||
this.hostnameToCountMap = new Map();
|
||||
this.contentLastModified = 0;
|
||||
this.frames = Object.create(null);
|
||||
this.logData = undefined;
|
||||
this.perLoadBlockedRequestCount = 0;
|
||||
this.perLoadAllowedRequestCount = 0;
|
||||
this.hiddenElementCount = ''; // Empty string means "unknown"
|
||||
|
@ -320,7 +324,7 @@ PageStore.prototype.init = function(tabId) {
|
|||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'cosmetic',
|
||||
µb.hnSwitches.toResultString(),
|
||||
µb.hnSwitches.toLogData(),
|
||||
'dom',
|
||||
tabContext.rawURL,
|
||||
this.tabHostname,
|
||||
|
@ -336,12 +340,12 @@ PageStore.prototype.init = function(tabId) {
|
|||
tabContext.normalURL,
|
||||
'generichide'
|
||||
);
|
||||
this.noGenericCosmeticFiltering = result === false;
|
||||
if ( result !== undefined && µb.logger.isEnabled() ) {
|
||||
this.noGenericCosmeticFiltering = result === 2;
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
µb.staticNetFilteringEngine.toResultString(true),
|
||||
µb.staticNetFilteringEngine.toLogData(),
|
||||
'generichide',
|
||||
tabContext.rawURL,
|
||||
this.tabHostname,
|
||||
|
@ -525,7 +529,7 @@ PageStore.prototype.journalAddRequest = function(hostname, result) {
|
|||
if ( hostname === '' ) { return; }
|
||||
this.journal.push(
|
||||
hostname,
|
||||
result.charCodeAt(1) === 0x62 /* 'b' */ ? 0x00000001 : 0x00010000
|
||||
result === 1 ? 0x00000001 : 0x00010000
|
||||
);
|
||||
if ( this.journalTimer === null ) {
|
||||
this.journalTimer = vAPI.setTimeout(this.journalProcess.bind(this, true), 1000);
|
||||
|
@ -604,6 +608,8 @@ PageStore.prototype.journalProcess = function(fromTimer) {
|
|||
/******************************************************************************/
|
||||
|
||||
PageStore.prototype.filterRequest = function(context) {
|
||||
this.logData = undefined;
|
||||
|
||||
var requestType = context.requestType;
|
||||
|
||||
// We want to short-term cache filtering results of collapsible types,
|
||||
|
@ -614,35 +620,39 @@ PageStore.prototype.filterRequest = function(context) {
|
|||
}
|
||||
|
||||
if ( this.getNetFilteringSwitch() === false ) {
|
||||
this.netFilteringCache.add(context, '');
|
||||
return '';
|
||||
this.netFilteringCache.add(context, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var entry = this.netFilteringCache.lookup(context);
|
||||
if ( entry !== undefined ) {
|
||||
this.logData = entry.logData;
|
||||
return entry.result;
|
||||
}
|
||||
|
||||
// Dynamic URL filtering.
|
||||
µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
|
||||
var result = µb.sessionURLFiltering.toFilterString();
|
||||
var result = µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.sessionURLFiltering.toLogData();
|
||||
}
|
||||
|
||||
// Dynamic hostname/type filtering.
|
||||
if ( result === '' && µb.userSettings.advancedUserEnabled ) {
|
||||
µb.sessionFirewall.evaluateCellZY( context.rootHostname, context.requestHostname, requestType);
|
||||
if ( µb.sessionFirewall.mustBlockOrAllow() ) {
|
||||
result = µb.sessionFirewall.toFilterString();
|
||||
if ( result === 0 && µb.userSettings.advancedUserEnabled ) {
|
||||
result = µb.sessionFirewall.evaluateCellZY( context.rootHostname, context.requestHostname, requestType);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.sessionFirewall.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
// Static filtering: lowest filtering precedence.
|
||||
if ( result === '' || result.charCodeAt(1) === 110 /* 'n' */ ) {
|
||||
if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) {
|
||||
result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled());
|
||||
if ( result === 0 || result === 3 ) {
|
||||
result = µb.staticNetFilteringEngine.matchString(context);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.staticNetFilteringEngine.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
this.netFilteringCache.add(context, result);
|
||||
this.netFilteringCache.add(context, result, this.logData);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -652,14 +662,16 @@ PageStore.prototype.filterRequest = function(context) {
|
|||
// The caller is responsible to check whether filtering is enabled or not.
|
||||
|
||||
PageStore.prototype.filterLargeMediaElement = function(size) {
|
||||
this.logData = undefined;
|
||||
|
||||
if ( Date.now() < this.allowLargeMediaElementsUntil ) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if ( µb.hnSwitches.evaluateZ('no-large-media', this.tabHostname) !== true ) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if ( (size >>> 10) < µb.userSettings.largeMediaSize ) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
this.largeMediaCount += 1;
|
||||
|
@ -670,48 +682,66 @@ PageStore.prototype.filterLargeMediaElement = function(size) {
|
|||
);
|
||||
}
|
||||
|
||||
return µb.hnSwitches.toResultString();
|
||||
if ( µb.logger.isEnabled() ) {
|
||||
this.logData = µb.hnSwitches.toLogData();
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
PageStore.prototype.filterRequestNoCache = function(context) {
|
||||
this.logData = undefined;
|
||||
|
||||
if ( this.getNetFilteringSwitch() === false ) {
|
||||
return '';
|
||||
return 0;
|
||||
}
|
||||
|
||||
var requestType = context.requestType,
|
||||
result = '';
|
||||
var requestType = context.requestType;
|
||||
|
||||
if ( requestType === 'csp_report' ) {
|
||||
if ( this.internalRedirectionCount !== 0 ) {
|
||||
result = 'gb:no-spurious-csp-report';
|
||||
if ( µb.logger.isEnabled() ) {
|
||||
this.logData = { result: 1, source: 'global', raw: 'no-spurious-csp-report' };
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} else if ( requestType === 'font' ) {
|
||||
if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) {
|
||||
result = µb.hnSwitches.toResultString();
|
||||
}
|
||||
this.remoteFontCount += 1;
|
||||
}
|
||||
|
||||
if ( requestType === 'font' ) {
|
||||
this.remoteFontCount += 1;
|
||||
if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) {
|
||||
if ( µb.logger.isEnabled() ) {
|
||||
this.logData = µb.hnSwitches.toLogData();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
var result = 0;
|
||||
|
||||
// Dynamic URL filtering.
|
||||
if ( result === '' ) {
|
||||
µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
|
||||
result = µb.sessionURLFiltering.toFilterString();
|
||||
if ( result === 0 ) {
|
||||
result = µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.sessionURLFiltering.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic hostname/type filtering.
|
||||
if ( result === '' && µb.userSettings.advancedUserEnabled ) {
|
||||
µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, requestType);
|
||||
if ( µb.sessionFirewall.mustBlockOrAllow() ) {
|
||||
result = µb.sessionFirewall.toFilterString();
|
||||
if ( result === 0 && µb.userSettings.advancedUserEnabled ) {
|
||||
result = µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, requestType);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.sessionFirewall.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
// Static filtering has lowest precedence.
|
||||
if ( result === '' || result.charCodeAt(1) === 110 /* 'n' */ ) {
|
||||
if ( µb.staticNetFilteringEngine.matchString(context) !== undefined ) {
|
||||
result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled());
|
||||
if ( result === 0 || result === 3 ) {
|
||||
result = µb.staticNetFilteringEngine.matchString(context);
|
||||
if ( result !== 0 && µb.logger.isEnabled() ) {
|
||||
this.logData = µb.staticNetFilteringEngine.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@ var messaging = vAPI.messaging;
|
|||
var popupData = {};
|
||||
var dfPaneBuilt = false;
|
||||
var reIP = /^\d+(?:\.\d+){1,3}$/;
|
||||
var reSrcHostnameFromRule = /^d[abn]:([^ ]+) ([^ ]+) ([^ ]+)/;
|
||||
var scopeToSrcHostnameMap = {
|
||||
'/': '*',
|
||||
'.': ''
|
||||
|
@ -148,16 +147,12 @@ var hashFromPopupData = function(reset) {
|
|||
return;
|
||||
}
|
||||
|
||||
var hasher = [];
|
||||
var rules = popupData.firewallRules;
|
||||
var rule;
|
||||
var hasher = [],
|
||||
rules = popupData.firewallRules;
|
||||
for ( var key in rules ) {
|
||||
if ( rules.hasOwnProperty(key) === false ) {
|
||||
continue;
|
||||
}
|
||||
rule = rules[key];
|
||||
if ( rule !== '' ) {
|
||||
hasher.push(rule);
|
||||
var rule = rules[key];
|
||||
if ( rule !== null ) {
|
||||
hasher.push(rule.src + ' ' + rule.des + ' ' + rule.type + ' ' + rule.action);
|
||||
}
|
||||
}
|
||||
hasher.sort();
|
||||
|
@ -243,18 +238,16 @@ var updateFirewallCell = function(scope, des, type, rule) {
|
|||
}
|
||||
|
||||
cells.removeClass();
|
||||
var action = rule.charAt(1);
|
||||
if ( action !== '' ) {
|
||||
cells.toggleClass(action + 'Rule', true);
|
||||
if ( rule !== null ) {
|
||||
cells.toggleClass(rule.action + 'Rule', true);
|
||||
}
|
||||
|
||||
// Use dark shade visual cue if the rule is specific to the cell.
|
||||
var ownRule = false;
|
||||
var matches = reSrcHostnameFromRule.exec(rule);
|
||||
if ( matches !== null ) {
|
||||
ownRule = (matches[2] !== '*' || matches[3] === type) &&
|
||||
(matches[2] === des) &&
|
||||
(matches[1] === scopeToSrcHostnameMap[scope]);
|
||||
if ( rule !== null ) {
|
||||
ownRule = (rule.des !== '*' || rule.type === type) &&
|
||||
(rule.des === des) &&
|
||||
(rule.src === scopeToSrcHostnameMap[scope]);
|
||||
}
|
||||
cells.toggleClass('ownRule', ownRule);
|
||||
|
||||
|
|
|
@ -35,14 +35,14 @@ var reEscape = function(s) {
|
|||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
};
|
||||
|
||||
var reSpecialChars = /[\*\^\t\v\n]/;
|
||||
var reSpecialNetworkChars = /[a-d]/;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var fromNetFilter = function(details) {
|
||||
var lists = [];
|
||||
var compiledFilter = details.compiledFilter;
|
||||
var entry, content, pos, c;
|
||||
var entry, content, pos, notFound;
|
||||
for ( var assetKey in listEntries ) {
|
||||
entry = listEntries[assetKey];
|
||||
if ( entry === undefined ) {
|
||||
|
@ -52,23 +52,23 @@ var fromNetFilter = function(details) {
|
|||
pos = 0;
|
||||
for (;;) {
|
||||
pos = content.indexOf(compiledFilter, pos);
|
||||
if ( pos === -1 ) {
|
||||
break;
|
||||
}
|
||||
if ( pos === -1 ) { break; }
|
||||
// We need an exact match.
|
||||
// https://github.com/gorhill/uBlock/issues/1392
|
||||
// https://github.com/gorhill/uBlock/issues/835
|
||||
if ( pos === 0 || reSpecialChars.test(content.charAt(pos - 1)) ) {
|
||||
c = content.charAt(pos + compiledFilter.length);
|
||||
if ( c === '' || reSpecialChars.test(c) ) {
|
||||
lists.push({
|
||||
title: entry.title,
|
||||
supportURL: entry.supportURL
|
||||
});
|
||||
break;
|
||||
}
|
||||
pos -= 1;
|
||||
notFound =
|
||||
reSpecialNetworkChars.test(content.charAt(pos)) === false ||
|
||||
pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A /* '\n' */;
|
||||
pos += 1 + compiledFilter.length;
|
||||
if ( notFound ) { continue; }
|
||||
if ( pos === content.length || content.charCodeAt(pos) === 0x0A ) {
|
||||
lists.push({
|
||||
title: entry.title,
|
||||
supportURL: entry.supportURL
|
||||
});
|
||||
break;
|
||||
}
|
||||
pos += compiledFilter.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,18 +119,18 @@ var fromCosmeticFilter = function(details) {
|
|||
var matches = rePlainSelector.exec(filter);
|
||||
if ( matches ) {
|
||||
if ( matches[0] === filter ) { // simple CSS selector
|
||||
reStr.push('c', 'lg', reEscape(filter));
|
||||
reStr.push('[e-h]lg', reEscape(filter));
|
||||
} else { // complex CSS selector
|
||||
reStr.push('c', reEscape('lg+'), reEscape(matches[0]), reEscape(filter));
|
||||
reStr.push('[e-h]lg\\+', reEscape(matches[0]), reEscape(filter));
|
||||
}
|
||||
} else if ( reHighLow.test(filter) ) { // [alt] or [title]
|
||||
reStr.push('c', 'hlg0', reEscape(filter));
|
||||
reStr.push('[e-h]hlg0', reEscape(filter));
|
||||
} else if ( reHighMedium.test(filter) ) { // [href^="..."]
|
||||
reStr.push('c', 'hmg0', '[^"]{8}', '[a-z]*' + reEscape(filter));
|
||||
reStr.push('[e-h]hmg0', '[^"]{8}', '[a-z]*' + reEscape(filter));
|
||||
} else if ( filter.indexOf(' ') === -1 ) { // high-high-simple selector
|
||||
reStr.push('c', 'hhsg0', reEscape(filter));
|
||||
reStr.push('[e-h]hhsg0', reEscape(filter));
|
||||
} else { // high-high-complex selector
|
||||
reStr.push('c', 'hhcg0', reEscape(filter));
|
||||
reStr.push('[e-h]hhcg0', reEscape(filter));
|
||||
}
|
||||
candidates[details.rawFilter] = new RegExp(reStr.join('\\v') + '(?:\\n|$)');
|
||||
|
||||
|
@ -150,7 +150,7 @@ var fromCosmeticFilter = function(details) {
|
|||
if ( hostname !== '' ) {
|
||||
for ( ;; ) {
|
||||
candidates[hostname + '##' + filter] = new RegExp(
|
||||
['c', 'h', '[^\\v]+', reEscape(hostname), filterEx].join('\\v') +
|
||||
['[e-h]h', '[^\\v]+', reEscape(hostname), filterEx].join('\\v') +
|
||||
'(?:\\n|$)'
|
||||
);
|
||||
pos = hostname.indexOf('.');
|
||||
|
@ -168,7 +168,7 @@ var fromCosmeticFilter = function(details) {
|
|||
if ( pos !== -1 ) {
|
||||
var entity = domain.slice(0, pos) + '.*';
|
||||
candidates[entity + '##' + filter] = new RegExp(
|
||||
['c', 'h', '[^\\v]+', reEscape(entity), filterEx].join('\\v') +
|
||||
['[e-h]h', '[^\\v]+', reEscape(entity), filterEx].join('\\v') +
|
||||
'(?:\\n|$)'
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -680,6 +680,9 @@
|
|||
µb.assets.get(assetKey, onRawListLoaded);
|
||||
return;
|
||||
}
|
||||
if ( /[^\x00-\x7F]/.test(details.content) ) {
|
||||
console.log(assetKey, 'has Unicode characters');
|
||||
}
|
||||
details.assetKey = assetKey;
|
||||
callback(details);
|
||||
};
|
||||
|
@ -727,7 +730,7 @@
|
|||
/******************************************************************************/
|
||||
|
||||
µBlock.compileFilters = function(rawText) {
|
||||
var compiledFilters = [];
|
||||
var compiledFilters = new this.CompiledOutput();
|
||||
|
||||
// Useful references:
|
||||
// https://adblockplus.org/en/filter-cheatsheet
|
||||
|
@ -793,7 +796,7 @@
|
|||
staticNetFilteringEngine.compile(line, compiledFilters);
|
||||
}
|
||||
|
||||
return compiledFilters.join('\n');
|
||||
return compiledFilters.toString();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
120
src/js/tab.js
120
src/js/tab.js
|
@ -525,7 +525,8 @@ vAPI.tabs.onClosed = function(tabId) {
|
|||
vAPI.tabs.onPopupUpdated = (function() {
|
||||
// The same context object will be reused everytime. This also allows to
|
||||
// remember whether a popup or popunder was matched.
|
||||
var context = {};
|
||||
var context = {},
|
||||
logData;
|
||||
|
||||
// https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764
|
||||
// See if two URLs are different, disregarding scheme -- because the scheme
|
||||
|
@ -549,8 +550,9 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
};
|
||||
|
||||
var popupMatch = function(openerURL, targetURL, clickedURL, popupType) {
|
||||
var openerHostname = µb.URI.hostnameFromURI(openerURL);
|
||||
var openerDomain = µb.URI.domainFromHostname(openerHostname);
|
||||
var openerHostname = µb.URI.hostnameFromURI(openerURL),
|
||||
openerDomain = µb.URI.domainFromHostname(openerHostname),
|
||||
result;
|
||||
|
||||
context.pageHostname = openerHostname;
|
||||
context.pageDomain = openerDomain;
|
||||
|
@ -579,90 +581,90 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
// URL.
|
||||
if ( openerHostname !== '' && targetURL !== 'about:blank' ) {
|
||||
// Check per-site switch first
|
||||
if ( µb.hnSwitches.evaluateZ('no-popups', openerHostname) ) {
|
||||
if ( typeof clickedURL === 'string' && areDifferentURLs(targetURL, clickedURL) ) {
|
||||
return 'ub:no-popups: ' + µb.hnSwitches.z + ' true';
|
||||
if ( µb.hnSwitches.evaluateZ('no-popups', openerHostname) === true ) {
|
||||
if (
|
||||
typeof clickedURL === 'string' &&
|
||||
areDifferentURLs(targetURL, clickedURL)
|
||||
) {
|
||||
logData = {
|
||||
source: 'switch',
|
||||
raw: 'no-popups: ' + µb.hnSwitches.z + ' true'
|
||||
};
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/581
|
||||
// Take into account popup-specific rules in dynamic URL filtering, OR
|
||||
// generic allow rules.
|
||||
µb.sessionURLFiltering.evaluateZ(openerHostname, targetURL, popupType);
|
||||
result = µb.sessionURLFiltering.evaluateZ(openerHostname, targetURL, popupType);
|
||||
if (
|
||||
µb.sessionURLFiltering.r === 1 && µb.sessionURLFiltering.type === popupType ||
|
||||
µb.sessionURLFiltering.r === 2
|
||||
result === 1 && µb.sessionURLFiltering.type === popupType ||
|
||||
result === 2
|
||||
) {
|
||||
return µb.sessionURLFiltering.toFilterString();
|
||||
logData = µb.sessionURLFiltering.toLogData();
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/581
|
||||
// Take into account `allow` rules in dynamic filtering: `block` rules
|
||||
// are ignored, as block rules are not meant to block specific types
|
||||
// like `popup` (just like with static filters).
|
||||
µb.sessionFirewall.evaluateCellZY(openerHostname, context.requestHostname, popupType);
|
||||
if ( µb.sessionFirewall.r === 2 ) {
|
||||
return µb.sessionFirewall.toFilterString();
|
||||
result = µb.sessionFirewall.evaluateCellZY(openerHostname, context.requestHostname, popupType);
|
||||
if ( result === 2 ) {
|
||||
logData = µb.sessionFirewall.toLogData();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/323
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/1142
|
||||
// Don't block if uBlock is turned off in popup's context
|
||||
var snfe = µb.staticNetFilteringEngine;
|
||||
if (
|
||||
µb.getNetFilteringSwitch(targetURL) &&
|
||||
snfe.matchStringExactType(context, targetURL, popupType) !== undefined
|
||||
) {
|
||||
return snfe.toResultString(µb.logger.isEnabled());
|
||||
if ( µb.getNetFilteringSwitch(targetURL) ) {
|
||||
result = µb.staticNetFilteringEngine.matchStringExactType(
|
||||
context,
|
||||
targetURL,
|
||||
popupType
|
||||
);
|
||||
if ( result !== 0 ) {
|
||||
logData = µb.staticNetFilteringEngine.toLogData();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return 0;
|
||||
};
|
||||
|
||||
var mapPopunderResult = function(popunderURL, popunderHostname, result) {
|
||||
if ( result.startsWith('sb:') === false ) {
|
||||
return '';
|
||||
}
|
||||
var snfe = µb.staticNetFilteringEngine;
|
||||
var token = snfe.tokenRegister;
|
||||
if ( token === '*' ) {
|
||||
return '';
|
||||
}
|
||||
if ( token === '.' ) {
|
||||
return result;
|
||||
}
|
||||
result = snfe.toResultString(true);
|
||||
var re = snfe.filterRegexFromCompiled(result.slice(3));
|
||||
if ( re === null ) {
|
||||
return '';
|
||||
}
|
||||
var matches = re.exec(popunderURL);
|
||||
if ( matches === null ) {
|
||||
return '';
|
||||
if (
|
||||
logData === undefined ||
|
||||
logData.source !== 'static' ||
|
||||
logData.token === '*'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
if ( logData.token === '.' ) { return result; }
|
||||
var re = new RegExp(logData.regex),
|
||||
matches = re.exec(popunderURL);
|
||||
if ( matches === null ) { return ''; }
|
||||
var beg = matches.index,
|
||||
end = beg + matches[0].length,
|
||||
pos = popunderURL.indexOf(popunderHostname);
|
||||
if ( pos === -1 ) {
|
||||
return '';
|
||||
}
|
||||
if ( pos === -1 ) { return ''; }
|
||||
// https://github.com/gorhill/uBlock/issues/1471
|
||||
// We test whether the opener hostname as at least one character
|
||||
// within matched portion of URL.
|
||||
// https://github.com/gorhill/uBlock/issues/1903
|
||||
// Ignore filters which cause a match before the start of the
|
||||
// hostname in the URL.
|
||||
return beg >= pos &&
|
||||
beg < pos + popunderHostname.length &&
|
||||
end > pos ?
|
||||
result :
|
||||
'';
|
||||
return beg >= pos && beg < pos + popunderHostname.length && end > pos
|
||||
? result
|
||||
: 0;
|
||||
};
|
||||
|
||||
var popunderMatch = function(openerURL, targetURL) {
|
||||
var result = popupMatch(targetURL, openerURL, null, 'popunder');
|
||||
if ( µb.isBlockResult(result) ) {
|
||||
if ( result === 1 ) {
|
||||
return result;
|
||||
}
|
||||
// https://github.com/gorhill/uBlock/issues/1010#issuecomment-186824878
|
||||
|
@ -671,24 +673,24 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
// a broad one, we will consider the opener tab to be a popunder tab.
|
||||
// For now, a "broad" filter is one which does not touch any part of
|
||||
// the hostname part of the opener URL.
|
||||
var popunderURL = openerURL;
|
||||
var popunderHostname = µb.URI.hostnameFromURI(popunderURL);
|
||||
var popunderURL = openerURL,
|
||||
popunderHostname = µb.URI.hostnameFromURI(popunderURL);
|
||||
if ( popunderHostname === '' ) {
|
||||
return '';
|
||||
return 0;
|
||||
}
|
||||
result = mapPopunderResult(
|
||||
popunderURL,
|
||||
popunderHostname,
|
||||
popupMatch(targetURL, popunderURL, null, 'popup')
|
||||
);
|
||||
if ( result !== '' ) {
|
||||
if ( result !== 0 ) {
|
||||
return result;
|
||||
}
|
||||
// https://github.com/gorhill/uBlock/issues/1598
|
||||
// Try to find a match against origin part of the opener URL.
|
||||
popunderURL = µb.URI.originFromURI(popunderURL);
|
||||
if ( popunderURL === '' ) {
|
||||
return '';
|
||||
return 0;
|
||||
}
|
||||
return mapPopunderResult(
|
||||
popunderURL,
|
||||
|
@ -731,13 +733,13 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
}
|
||||
|
||||
// Popup test.
|
||||
var popupType = 'popup';
|
||||
var result = popupMatch(openerURL, targetURL, µb.mouseURL, 'popup');
|
||||
var popupType = 'popup',
|
||||
result = popupMatch(openerURL, targetURL, µb.mouseURL, 'popup');
|
||||
|
||||
// Popunder test.
|
||||
if ( result === '' ) {
|
||||
if ( result === 0 ) {
|
||||
result = popunderMatch(openerURL, targetURL);
|
||||
if ( µb.isBlockResult(result) ) {
|
||||
if ( result === 1 ) {
|
||||
popupType = 'popunder';
|
||||
}
|
||||
}
|
||||
|
@ -747,7 +749,7 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
µb.logger.writeOne(
|
||||
popupType === 'popup' ? openerTabId : targetTabId,
|
||||
'net',
|
||||
result,
|
||||
logData,
|
||||
popupType,
|
||||
popupType === 'popup' ? targetURL : openerURL,
|
||||
µb.URI.hostnameFromURI(context.rootURL),
|
||||
|
@ -756,7 +758,7 @@ vAPI.tabs.onPopupUpdated = (function() {
|
|||
}
|
||||
|
||||
// Not blocked
|
||||
if ( µb.isAllowResult(result) ) {
|
||||
if ( result !== 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,9 @@ var onBeforeRequest = function(details) {
|
|||
var isFrame = requestType === 'sub_frame';
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/114
|
||||
var requestContext = pageStore.createContextFromFrameId(isFrame ? details.parentFrameId : details.frameId);
|
||||
var requestContext = pageStore.createContextFromFrameId(
|
||||
isFrame ? details.parentFrameId : details.frameId
|
||||
);
|
||||
|
||||
// Setup context and evaluate
|
||||
var requestURL = details.url;
|
||||
|
@ -132,7 +134,7 @@ var onBeforeRequest = function(details) {
|
|||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
result,
|
||||
pageStore.logData,
|
||||
requestType,
|
||||
requestURL,
|
||||
requestContext.rootHostname,
|
||||
|
@ -141,7 +143,7 @@ var onBeforeRequest = function(details) {
|
|||
}
|
||||
|
||||
// Not blocked
|
||||
if ( µb.isAllowResult(result) ) {
|
||||
if ( result !== 1 ) {
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/114
|
||||
if ( details.parentFrameId !== -1 && isFrame ) {
|
||||
pageStore.setFrame(details.frameId, requestURL);
|
||||
|
@ -162,7 +164,7 @@ var onBeforeRequest = function(details) {
|
|||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'redirect',
|
||||
'rr:' + µb.redirectEngine.resourceNameRegister,
|
||||
{ source: 'redirect', raw: µb.redirectEngine.resourceNameRegister },
|
||||
requestType,
|
||||
requestURL,
|
||||
requestContext.rootHostname,
|
||||
|
@ -193,8 +195,7 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
// behind-the-scene
|
||||
var µburi = µb.URI,
|
||||
requestHostname = µburi.hostnameFromURI(requestURL),
|
||||
requestDomain = µburi.domainFromHostname(requestHostname) || requestHostname,
|
||||
result = '';
|
||||
requestDomain = µburi.domainFromHostname(requestHostname) || requestHostname;
|
||||
var context = {
|
||||
rootHostname: requestHostname,
|
||||
rootDomain: requestDomain,
|
||||
|
@ -204,22 +205,31 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
requestHostname: requestHostname,
|
||||
requestType: 'main_frame'
|
||||
};
|
||||
var result = 0,
|
||||
logData,
|
||||
logEnabled = µb.logger.isEnabled();
|
||||
|
||||
// If the site is whitelisted, disregard strict blocking
|
||||
if ( µb.getNetFilteringSwitch(requestURL) === false ) {
|
||||
result = 'ua:whitelisted';
|
||||
result = 2;
|
||||
if ( logEnabled === true ) {
|
||||
logData = { engine: 'u', result: 2, raw: 'whitelisted' };
|
||||
}
|
||||
}
|
||||
|
||||
// Permanently unrestricted?
|
||||
if ( result === '' && µb.hnSwitches.evaluateZ('no-strict-blocking', requestHostname) ) {
|
||||
result = 'ua:no-strict-blocking: ' + µb.hnSwitches.z + ' true';
|
||||
if ( result === 0 && µb.hnSwitches.evaluateZ('no-strict-blocking', requestHostname) ) {
|
||||
result = 2;
|
||||
if ( logEnabled === true ) {
|
||||
logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: ' + µb.hnSwitches.z + ' true' };
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily whitelisted?
|
||||
if ( result === '' ) {
|
||||
if ( result === 0 ) {
|
||||
result = isTemporarilyWhitelisted(result, requestHostname);
|
||||
if ( result.charAt(1) === 'a' ) {
|
||||
result = 'ua:no-strict-blocking true (temporary)';
|
||||
if ( result === 2 && logEnabled === true ) {
|
||||
logData = { engine: 'u', result: 2, raw: 'no-strict-blocking true (temporary)' };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,26 +237,31 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
var snfe = µb.staticNetFilteringEngine;
|
||||
|
||||
// Check for specific block
|
||||
if (
|
||||
result === '' &&
|
||||
snfe.matchStringExactType(context, requestURL, 'main_frame') !== undefined
|
||||
) {
|
||||
result = snfe.toResultString(true);
|
||||
if ( result === 0 ) {
|
||||
result = snfe.matchStringExactType(context, requestURL, 'main_frame');
|
||||
if ( result !== 0 && logEnabled === true ) {
|
||||
logData = snfe.toLogData();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for generic block
|
||||
if (
|
||||
result === '' &&
|
||||
snfe.matchStringExactType(context, requestURL, 'no_type') !== undefined
|
||||
) {
|
||||
result = snfe.toResultString(true);
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/1128
|
||||
// Do not block if the match begins after the hostname, except when
|
||||
// the filter is specifically of type `other`.
|
||||
// https://github.com/gorhill/uBlock/issues/490
|
||||
// Removing this for the time being, will need a new, dedicated type.
|
||||
if ( result.charAt(1) === 'b' ) {
|
||||
result = toBlockDocResult(requestURL, requestHostname, result);
|
||||
if ( result === 0 ) {
|
||||
result = snfe.matchStringExactType(context, requestURL, 'no_type');
|
||||
if ( result !== 0 ) {
|
||||
if ( result === 1 || logEnabled === true ) {
|
||||
logData = snfe.toLogData();
|
||||
}
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/1128
|
||||
// Do not block if the match begins after the hostname, except when
|
||||
// the filter is specifically of type `other`.
|
||||
// https://github.com/gorhill/uBlock/issues/490
|
||||
// Removing this for the time being, will need a new, dedicated type.
|
||||
if (
|
||||
result === 1 &&
|
||||
toBlockDocResult(requestURL, requestHostname, logData) === false
|
||||
) {
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,11 +272,11 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
pageStore.journalAddRequest(requestHostname, result);
|
||||
}
|
||||
|
||||
if ( µb.logger.isEnabled() ) {
|
||||
if ( logEnabled ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
result,
|
||||
logData,
|
||||
'main_frame',
|
||||
requestURL,
|
||||
requestHostname,
|
||||
|
@ -270,19 +285,19 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
}
|
||||
|
||||
// Not blocked
|
||||
if ( µb.isAllowResult(result) ) {
|
||||
return;
|
||||
}
|
||||
if ( result !== 1 ) { return; }
|
||||
|
||||
var compiled = result.slice(3);
|
||||
// No log data means no strict blocking (because we need to report why
|
||||
// the blocking occurs.
|
||||
if ( logData === undefined ) { return; }
|
||||
|
||||
// Blocked
|
||||
var query = btoa(JSON.stringify({
|
||||
url: requestURL,
|
||||
hn: requestHostname,
|
||||
dn: requestDomain,
|
||||
fc: compiled,
|
||||
fs: snfe.filterStringFromCompiled(compiled)
|
||||
fc: logData.compiled,
|
||||
fs: logData.raw
|
||||
}));
|
||||
|
||||
vAPI.tabs.replace(tabId, vAPI.getURL('document-blocked.html?details=') + query);
|
||||
|
@ -292,26 +307,23 @@ var onBeforeRootFrameRequest = function(details) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var toBlockDocResult = function(url, hostname, result) {
|
||||
// Make a regex out of the result
|
||||
var re = µBlock.staticNetFilteringEngine
|
||||
.filterRegexFromCompiled(result.slice(3), 'gi');
|
||||
if ( re === null ) {
|
||||
return '';
|
||||
}
|
||||
var matches = re.exec(url);
|
||||
if ( matches === null ) {
|
||||
return '';
|
||||
}
|
||||
var toBlockDocResult = function(url, hostname, logData) {
|
||||
if ( typeof logData.regex !== 'string' ) { return; }
|
||||
var re = new RegExp(logData.regex),
|
||||
match = re.exec(url.toLowerCase());
|
||||
if ( match === null ) { return ''; }
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/1128
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/1212
|
||||
// Relax the rule: verify that the match is completely before the path part
|
||||
if ( re.lastIndex <= url.indexOf(hostname) + hostname.length + 1 ) {
|
||||
return result;
|
||||
if (
|
||||
(match.index + match.length) <=
|
||||
(url.indexOf(hostname) + hostname.length + 1)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return '';
|
||||
return false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -410,18 +422,20 @@ var onHeadersReceived = function(details) {
|
|||
// Turns out scripts must also be considered as potential embedded
|
||||
// contexts (as workers) and as such we may need to inject content
|
||||
// security policy directives.
|
||||
if ( requestType === 'script' || requestType === 'main_frame' || requestType === 'sub_frame' ) {
|
||||
return processCSP(pageStore, details);
|
||||
if ( requestType === 'main_frame' || requestType === 'sub_frame' ) {
|
||||
return injectCSP(pageStore, details);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var processCSP = function(pageStore, details) {
|
||||
var injectCSP = function(pageStore, details) {
|
||||
var µb = µBlock,
|
||||
tabId = details.tabId,
|
||||
requestURL = details.url,
|
||||
loggerEnabled = µb.logger.isEnabled();
|
||||
loggerEnabled = µb.logger.isEnabled(),
|
||||
logger = µb.logger,
|
||||
cspSubsets = [];
|
||||
|
||||
var context = pageStore.createContextFromPage();
|
||||
context.requestHostname = µb.URI.hostnameFromURI(requestURL);
|
||||
|
@ -429,79 +443,125 @@ var processCSP = function(pageStore, details) {
|
|||
context.pageHostname = context.pageDomain = context.requestHostname;
|
||||
}
|
||||
|
||||
var inlineScriptResult, blockInlineScript,
|
||||
workerResult, blockWorker;
|
||||
if ( details.type !== 'script' ) {
|
||||
context.requestType = 'inline-script';
|
||||
context.requestURL = requestURL;
|
||||
inlineScriptResult = pageStore.filterRequestNoCache(context);
|
||||
blockInlineScript = µb.isBlockResult(inlineScriptResult);
|
||||
// https://github.com/gorhill/uBlock/issues/2360
|
||||
// https://github.com/gorhill/uBlock/issues/2440
|
||||
context.requestType = 'script';
|
||||
context.requestURL = 'blob:';
|
||||
µb.staticNetFilteringEngine.matchString(context);
|
||||
workerResult = µb.staticNetFilteringEngine.toResultString(loggerEnabled);
|
||||
blockWorker = µb.isBlockResult(workerResult);
|
||||
// Start collecting policies >>>>>>>>
|
||||
|
||||
// ======== built-in policies
|
||||
|
||||
context.requestType = 'inline-script';
|
||||
context.requestURL = requestURL;
|
||||
if ( pageStore.filterRequestNoCache(context) === 1 ) {
|
||||
cspSubsets[0] = "script-src 'unsafe-eval' * blob: data:";
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=669086
|
||||
// TODO: remove when most users are beyond Chromium v56
|
||||
if ( vAPI.chromiumVersion < 57 ) {
|
||||
cspSubsets[0] += '; frame-src *';
|
||||
}
|
||||
}
|
||||
|
||||
µb.staticNetFilteringEngine.matchStringExactType(context, requestURL, 'websocket');
|
||||
var websocketResult = µb.staticNetFilteringEngine.toResultString(loggerEnabled),
|
||||
blockWebsocket = µb.isBlockResult(websocketResult);
|
||||
|
||||
var headersChanged;
|
||||
if ( blockInlineScript || blockWebsocket || blockWorker ) {
|
||||
headersChanged = foilWithCSP(
|
||||
details.responseHeaders,
|
||||
blockInlineScript,
|
||||
blockWebsocket,
|
||||
blockWorker
|
||||
if ( loggerEnabled === true ) {
|
||||
logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
pageStore.logData,
|
||||
'inline-script',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
|
||||
if ( loggerEnabled && details.type !== 'script' ) {
|
||||
if ( blockInlineScript !== undefined ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
inlineScriptResult,
|
||||
'inline-script',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
if ( websocketResult ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
websocketResult,
|
||||
'websocket',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
if ( workerResult ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
workerResult,
|
||||
'worker',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
}
|
||||
// ======== filter-based policies
|
||||
|
||||
// Static filtering.
|
||||
|
||||
var logData = [];
|
||||
|
||||
µb.staticNetFilteringEngine.matchAndFetchData(
|
||||
'csp',
|
||||
requestURL,
|
||||
cspSubsets,
|
||||
loggerEnabled === true ? logData : undefined
|
||||
);
|
||||
|
||||
// <<<<<<<< All policies have been collected
|
||||
|
||||
context.dispose();
|
||||
|
||||
if ( headersChanged !== true ) { return; }
|
||||
// URL filtering `allow` rules override static filtering.
|
||||
if (
|
||||
cspSubsets.length !== 0 &&
|
||||
µb.sessionURLFiltering.evaluateZ(context.rootHostname, requestURL, 'csp') === 2
|
||||
) {
|
||||
if ( loggerEnabled === true ) {
|
||||
logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
µb.sessionURLFiltering.toLogData(),
|
||||
'csp',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic filtering rules override static filtering.
|
||||
if (
|
||||
cspSubsets.length !== 0 &&
|
||||
µb.userSettings.advancedUserEnabled &&
|
||||
µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.rootHostname, '*') === 2
|
||||
) {
|
||||
if ( loggerEnabled === true ) {
|
||||
logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
µb.sessionFirewall.toLogData(),
|
||||
'csp',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Static CSP policies will be applied.
|
||||
var i = logData.length;
|
||||
while ( i-- ) {
|
||||
logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
logData[i],
|
||||
'csp',
|
||||
requestURL,
|
||||
context.rootHostname,
|
||||
context.pageHostname
|
||||
);
|
||||
}
|
||||
|
||||
if ( cspSubsets.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
µb.updateBadgeAsync(tabId);
|
||||
|
||||
return { 'responseHeaders': details.responseHeaders };
|
||||
var csp, headers = details.responseHeaders;
|
||||
i = headerIndexFromName('content-security-policy', headers);
|
||||
if ( i !== -1 ) {
|
||||
csp = headers[i].value.trim();
|
||||
headers.splice(i, 1);
|
||||
}
|
||||
cspSubsets = cspSubsets.join(', ');
|
||||
// Use comma to add a new subset to potentially existing one(s). This new
|
||||
// subset has its own reporting options and won't cause spurious CSP
|
||||
// reports to outside world.
|
||||
// Ref.: https://www.w3.org/TR/CSP2/#implementation-considerations
|
||||
headers.push({
|
||||
name: 'Content-Security-Policy',
|
||||
value: csp === undefined ? cspSubsets : csp + ', ' + cspSubsets
|
||||
});
|
||||
|
||||
return { 'responseHeaders': headers };
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -518,13 +578,13 @@ var foilLargeMediaElement = function(pageStore, details) {
|
|||
var tabId = details.tabId,
|
||||
size = parseInt(details.responseHeaders[i].value, 10) || 0,
|
||||
result = pageStore.filterLargeMediaElement(size);
|
||||
if ( result === undefined ) { return; }
|
||||
if ( result === 0 ) { return; }
|
||||
|
||||
if ( µb.logger.isEnabled() ) {
|
||||
µb.logger.writeOne(
|
||||
tabId,
|
||||
'net',
|
||||
result,
|
||||
pageStore.logData,
|
||||
details.type,
|
||||
details.url,
|
||||
pageStore.tabHostname,
|
||||
|
@ -537,57 +597,6 @@ var foilLargeMediaElement = function(pageStore, details) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var foilWithCSP = function(headers, noInlineScript, noWebsocket, noBlobWorker) {
|
||||
var i = headerIndexFromName('content-security-policy', headers),
|
||||
cspSubset = [];
|
||||
|
||||
if ( noInlineScript ) {
|
||||
cspSubset.push("script-src 'unsafe-eval' *");
|
||||
}
|
||||
|
||||
if ( noWebsocket ) {
|
||||
cspSubset.push('connect-src http: https:');
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/CSP2/#directive-child-src
|
||||
// https://www.w3.org/TR/CSP3/#directive-worker-src
|
||||
if ( noBlobWorker ) {
|
||||
cspSubset.push('child-src http: https:');
|
||||
}
|
||||
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=513860
|
||||
// Bad Chromium bug: web pages can work around CSP directives by
|
||||
// creating data:- or blob:-based URI. So if we must restrict using CSP,
|
||||
// we have no choice but to also prevent the creation of nested browsing
|
||||
// contexts based on data:- or blob:-based URIs.
|
||||
if ( vAPI.chrome && (noInlineScript || noWebsocket) ) {
|
||||
// https://w3c.github.io/webappsec-csp/#directive-frame-src
|
||||
cspSubset.push('frame-src http: https:');
|
||||
}
|
||||
|
||||
if ( cspSubset.length === 0 ) { return; }
|
||||
|
||||
var csp;
|
||||
if ( i !== -1 ) {
|
||||
csp = headers[i].value.trim();
|
||||
headers.splice(i, 1);
|
||||
}
|
||||
|
||||
// Use comma to add a new subset to potentially existing one(s). This new
|
||||
// subset has its own reporting options and won't cause spurious CSP
|
||||
// reports to outside world.
|
||||
// Ref.: https://www.w3.org/TR/CSP2/#implementation-considerations
|
||||
cspSubset = cspSubset.join('; ');
|
||||
headers.push({
|
||||
name: 'Content-Security-Policy',
|
||||
value: csp === undefined ? cspSubset : csp + ', ' + cspSubset
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Caller must ensure headerName is normalized to lower case.
|
||||
|
||||
var headerIndexFromName = function(headerName, headers) {
|
||||
|
@ -620,8 +629,7 @@ vAPI.net.onHeadersReceived = {
|
|||
'main_frame',
|
||||
'sub_frame',
|
||||
'image',
|
||||
'media',
|
||||
'script'
|
||||
'media'
|
||||
],
|
||||
extra: [ 'blocking', 'responseHeaders' ],
|
||||
callback: onHeadersReceived
|
||||
|
@ -629,8 +637,6 @@ vAPI.net.onHeadersReceived = {
|
|||
|
||||
vAPI.net.registerListeners();
|
||||
|
||||
//console.log('traffic.js > Beginning to intercept net requests at %s', (new Date()).toISOString());
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var isTemporarilyWhitelisted = function(result, hostname) {
|
||||
|
@ -640,17 +646,15 @@ var isTemporarilyWhitelisted = function(result, hostname) {
|
|||
obsolete = documentWhitelists[hostname];
|
||||
if ( obsolete !== undefined ) {
|
||||
if ( obsolete > Date.now() ) {
|
||||
if ( result === '' ) {
|
||||
return 'ua:*' + ' ' + hostname + ' doc allow';
|
||||
if ( result === 0 ) {
|
||||
return 2;
|
||||
}
|
||||
} else {
|
||||
delete documentWhitelists[hostname];
|
||||
}
|
||||
}
|
||||
pos = hostname.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
break;
|
||||
}
|
||||
if ( pos === -1 ) { break; }
|
||||
hostname = hostname.slice(pos + 1);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -210,7 +210,7 @@ URLNetFiltering.prototype.removeRule = function(srcHostname, url, type) {
|
|||
URLNetFiltering.prototype.evaluateZ = function(context, target, type) {
|
||||
this.r = 0;
|
||||
if ( this.rules.size === 0 ) {
|
||||
return this;
|
||||
return 0;
|
||||
}
|
||||
var entries, pos, i, entry;
|
||||
for (;;) {
|
||||
|
@ -222,7 +222,7 @@ URLNetFiltering.prototype.evaluateZ = function(context, target, type) {
|
|||
this.url = entry.url;
|
||||
this.type = type;
|
||||
this.r = entry.action;
|
||||
return this;
|
||||
return this.r;
|
||||
}
|
||||
}
|
||||
if ( (entries = this.rules.get(context + ' *')) ) {
|
||||
|
@ -232,14 +232,20 @@ URLNetFiltering.prototype.evaluateZ = function(context, target, type) {
|
|||
this.url = entry.url;
|
||||
this.type = '*';
|
||||
this.r = entry.action;
|
||||
return this;
|
||||
return this.r;
|
||||
}
|
||||
}
|
||||
if ( context === '*' ) { break; }
|
||||
pos = context.indexOf('.');
|
||||
context = pos !== -1 ? context.slice(pos + 1) : '*';
|
||||
}
|
||||
return this;
|
||||
return 0;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
URLNetFiltering.prototype.mustAllowCellZ = function(context, target, type) {
|
||||
return this.evaluateZ(context, target, type).r === 2;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -250,21 +256,30 @@ URLNetFiltering.prototype.mustBlockOrAllow = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
URLNetFiltering.prototype.toFilterString = function() {
|
||||
if ( this.r === 0 ) {
|
||||
return '';
|
||||
}
|
||||
var body = this.context + ' ' + this.url + ' ' + this.type;
|
||||
if ( this.r === 1 ) {
|
||||
return 'lb:' + body + ' block';
|
||||
}
|
||||
if ( this.r === 2 ) {
|
||||
return 'la:' + body + ' allow';
|
||||
}
|
||||
/* this.r === 3 */
|
||||
return 'ln:' + body + ' noop';
|
||||
URLNetFiltering.prototype.toLogData = function() {
|
||||
if ( this.r === 0 ) { return; }
|
||||
return {
|
||||
source: 'dynamicUrl',
|
||||
result: this.r,
|
||||
rule: [
|
||||
this.context,
|
||||
this.url,
|
||||
this.type,
|
||||
this.intToActionMap.get(this.r)
|
||||
],
|
||||
raw: this.context + ' ' +
|
||||
this.url + ' ' +
|
||||
this.type + ' ' +
|
||||
this.intToActionMap.get(this.r)
|
||||
};
|
||||
};
|
||||
|
||||
URLNetFiltering.prototype.intToActionMap = new Map([
|
||||
[ 1, ' block' ],
|
||||
[ 2, ' allow' ],
|
||||
[ 3, ' noop' ]
|
||||
]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
URLNetFiltering.prototype.copyRules = function(other, context, urls, type) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2014-2016 Raymond Hill
|
||||
Copyright (C) 2014-2017 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
|
||||
|
@ -150,7 +150,10 @@
|
|||
this.offset = offset || 0;
|
||||
};
|
||||
|
||||
µBlock.LineIterator.prototype.next = function() {
|
||||
µBlock.LineIterator.prototype.next = function(offset) {
|
||||
if ( offset !== undefined ) {
|
||||
this.offset += offset;
|
||||
}
|
||||
var lineEnd = this.text.indexOf('\n', this.offset);
|
||||
if ( lineEnd === -1 ) {
|
||||
lineEnd = this.text.indexOf('\r', this.offset);
|
||||
|
@ -163,18 +166,8 @@
|
|||
return line;
|
||||
};
|
||||
|
||||
µBlock.LineIterator.prototype.rewind = function() {
|
||||
if ( this.offset <= 1 ) {
|
||||
this.offset = 0;
|
||||
return;
|
||||
}
|
||||
var lineEnd = this.text.lastIndexOf('\n', this.offset - 2);
|
||||
if ( lineEnd !== -1 ) {
|
||||
this.offset = lineEnd + 1;
|
||||
} else {
|
||||
lineEnd = this.text.lastIndexOf('\r', this.offset - 2);
|
||||
this.offset = lineEnd !== -1 ? lineEnd + 1 : 0;
|
||||
}
|
||||
µBlock.LineIterator.prototype.charCodeAt = function(offset) {
|
||||
return this.text.charCodeAt(this.offset + offset);
|
||||
};
|
||||
|
||||
µBlock.LineIterator.prototype.eot = function() {
|
||||
|
@ -209,6 +202,59 @@
|
|||
return field;
|
||||
};
|
||||
|
||||
µBlock.FieldIterator.prototype.remainder = function() {
|
||||
return this.text.slice(this.offset);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.CompiledOutput = function() {
|
||||
this.bufferLen = 8192;
|
||||
this.buffer = new Uint8Array(this.bufferLen);
|
||||
this.offset = 0;
|
||||
};
|
||||
|
||||
µBlock.CompiledOutput.prototype.push = function(lineBits, line) {
|
||||
var lineLen = line.length,
|
||||
offset = this.offset,
|
||||
need = offset + 2 + lineLen; // lineBits, line, \n
|
||||
if ( need > this.bufferLen ) {
|
||||
this.grow(need);
|
||||
}
|
||||
var buffer = this.buffer;
|
||||
if ( offset !== 0 ) {
|
||||
buffer[offset++] = 0x0A /* '\n' */;
|
||||
}
|
||||
buffer[offset++] = 0x61 /* 'a' */ + lineBits;
|
||||
for ( var i = 0, c; i < lineLen; i++ ) {
|
||||
c = line.charCodeAt(i);
|
||||
if ( c > 0x7F ) {
|
||||
return this.push(lineBits | 0x02, encodeURIComponent(line));
|
||||
}
|
||||
buffer[offset++] = c;
|
||||
}
|
||||
this.offset = offset;
|
||||
};
|
||||
|
||||
µBlock.CompiledOutput.prototype.grow = function(need) {
|
||||
var newBufferLen = Math.min(
|
||||
2097152,
|
||||
1 << Math.ceil(Math.log(need) / Math.log(2))
|
||||
);
|
||||
while ( newBufferLen < need ) {
|
||||
newBufferLen += 1048576;
|
||||
}
|
||||
var newBuffer = new Uint8Array(newBufferLen);
|
||||
newBuffer.set(this.buffer);
|
||||
this.buffer = newBuffer;
|
||||
this.bufferLen = newBufferLen;
|
||||
};
|
||||
|
||||
µBlock.CompiledOutput.prototype.toString = function() {
|
||||
var decoder = new TextDecoder();
|
||||
return decoder.decode(new Uint8Array(this.buffer.buffer, 0, this.offset));
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.mapToArray = typeof Array.from === 'function'
|
||||
|
|
Loading…
Reference in a new issue