refactor static network filtering, add support for csp injection

This commit is contained in:
gorhill 2017-05-12 10:35:11 -04:00
parent 8570b63bef
commit 0232382695
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
18 changed files with 1844 additions and 1771 deletions

View file

@ -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(){};

View file

@ -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 {

View file

@ -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);
}

View file

@ -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
})();
/******************************************************************************/

View file

@ -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) ) {

View file

@ -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(' '));
};

View file

@ -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'
};
};
/******************************************************************************/

View file

@ -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));
};

View file

@ -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,

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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

View file

@ -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();
};
/******************************************************************************/

View file

@ -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;
}

View file

@ -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;

View file

@ -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) {

View file

@ -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'