mirror of https://github.com/gorhill/uBlock.git
279 lines
9.4 KiB
JavaScript
279 lines
9.4 KiB
JavaScript
/*******************************************************************************
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
|
Copyright (C) 2017-2018 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
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
*/
|
|
|
|
// For background page
|
|
|
|
'use strict';
|
|
|
|
/******************************************************************************/
|
|
|
|
vAPI.net = {
|
|
onBeforeRequest: {},
|
|
onBeforeMaybeSpuriousCSPReport: {},
|
|
onHeadersReceived: {},
|
|
nativeCSPReportFiltering: true,
|
|
webRequest: browser.webRequest,
|
|
canFilterResponseBody:
|
|
typeof browser.webRequest === 'object' &&
|
|
typeof browser.webRequest.filterResponseData === 'function'
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
vAPI.net.registerListeners = function() {
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2950
|
|
// Firefox 55 does not normalize URLs to ASCII, uBO must do this itself.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=945240
|
|
let mustPunycode = false;
|
|
(function() {
|
|
if (
|
|
typeof browser === 'object' &&
|
|
browser !== null &&
|
|
browser.runtime instanceof Object &&
|
|
typeof browser.runtime.getBrowserInfo === 'function'
|
|
) {
|
|
browser.runtime.getBrowserInfo().then(info => {
|
|
mustPunycode = info.name === 'Firefox' &&
|
|
/^5[0-6]\./.test(info.version);
|
|
});
|
|
}
|
|
})();
|
|
|
|
let wrApi = browser.webRequest;
|
|
|
|
// legacy Chromium understands only these network request types.
|
|
let validTypes = new Set([
|
|
'image',
|
|
'main_frame',
|
|
'object',
|
|
'other',
|
|
'script',
|
|
'stylesheet',
|
|
'sub_frame',
|
|
'xmlhttprequest',
|
|
]);
|
|
// modern Chromium/WebExtensions: more types available.
|
|
if ( wrApi.ResourceType ) {
|
|
for ( let typeKey in wrApi.ResourceType ) {
|
|
if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) {
|
|
validTypes.add(wrApi.ResourceType[typeKey]);
|
|
}
|
|
}
|
|
}
|
|
|
|
let denormalizeTypes = function(aa) {
|
|
if ( aa.length === 0 ) {
|
|
return Array.from(validTypes);
|
|
}
|
|
let out = new Set(),
|
|
i = aa.length;
|
|
while ( i-- ) {
|
|
let type = aa[i];
|
|
if ( validTypes.has(type) ) {
|
|
out.add(type);
|
|
}
|
|
if ( type === 'image' && validTypes.has('imageset') ) {
|
|
out.add('imageset');
|
|
}
|
|
}
|
|
return Array.from(out);
|
|
};
|
|
|
|
let punycode = self.punycode;
|
|
let reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/;
|
|
let reNetworkURI = /^(?:ftps?|https?|wss?)/;
|
|
let parsedURL = new URL('about:blank');
|
|
|
|
let normalizeRequestDetails = function(details) {
|
|
if (
|
|
details.tabId === vAPI.noTabId &&
|
|
reNetworkURI.test(details.documentUrl)
|
|
) {
|
|
details.tabId = vAPI.anyTabId;
|
|
}
|
|
|
|
if ( mustPunycode && !reAsciiHostname.test(details.url) ) {
|
|
parsedURL.href = details.url;
|
|
details.url = details.url.replace(
|
|
parsedURL.hostname,
|
|
punycode.toASCII(parsedURL.hostname)
|
|
);
|
|
}
|
|
|
|
let type = details.type;
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1493
|
|
// Chromium 49+/WebExtensions support a new request type: `ping`,
|
|
// which is fired as a result of using `navigator.sendBeacon`.
|
|
if ( type === 'ping' ) {
|
|
details.type = 'beacon';
|
|
return;
|
|
}
|
|
|
|
if ( type === 'imageset' ) {
|
|
details.type = 'image';
|
|
return;
|
|
}
|
|
};
|
|
|
|
// This is to work around Firefox's inability to redirect xmlhttprequest
|
|
// to data: URI.
|
|
let pseudoRedirector = {
|
|
filters: new Map(),
|
|
reDataURI: /^data:\w+\/\w+;base64,/,
|
|
dec: null,
|
|
init: function() {
|
|
this.dec = new Uint8Array(128);
|
|
let s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
for ( let i = 0, n = s.length; i < n; i++ ) {
|
|
this.dec[s.charCodeAt(i)] = i;
|
|
}
|
|
},
|
|
start: function(requestId, redirectUrl) {
|
|
if ( this.dec === null ) { this.init(); }
|
|
let match = this.reDataURI.exec(redirectUrl);
|
|
if ( match === null ) { return redirectUrl; }
|
|
let s = redirectUrl.slice(match[0].length).replace(/=*$/, '');
|
|
let f = browser.webRequest.filterResponseData(requestId);
|
|
f.onstop = this.done;
|
|
f.onerror = this.disconnect;
|
|
this.filters.set(f, s);
|
|
},
|
|
done: function() {
|
|
let pr = pseudoRedirector;
|
|
let bufIn = pr.filters.get(this);
|
|
if ( bufIn === undefined ) { return pr.disconnect(this); }
|
|
let dec = pr.dec;
|
|
let sizeIn = bufIn.length;
|
|
let iIn = 0;
|
|
let sizeOut = sizeIn * 6 >>> 3;
|
|
let bufOut = new Uint8Array(sizeOut);
|
|
let iOut = 0;
|
|
let n = sizeIn & ~3;
|
|
while ( iIn < n ) {
|
|
let b0 = dec[bufIn.charCodeAt(iIn++)];
|
|
let b1 = dec[bufIn.charCodeAt(iIn++)];
|
|
let b2 = dec[bufIn.charCodeAt(iIn++)];
|
|
let b3 = dec[bufIn.charCodeAt(iIn++)];
|
|
bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4);
|
|
bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2);
|
|
bufOut[iOut++] = (b2 << 6) & 0xC0 | b3;
|
|
}
|
|
if ( n < sizeIn ) {
|
|
let b0 = dec[bufIn.charCodeAt(iIn++)];
|
|
let b1 = dec[bufIn.charCodeAt(iIn++)];
|
|
if ( (sizeIn & 3) === 2 ) {
|
|
let b2 = dec[bufIn.charCodeAt(iIn++)];
|
|
bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4);
|
|
bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2);
|
|
} else {
|
|
bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4);
|
|
}
|
|
}
|
|
this.write(bufOut);
|
|
pr.disconnect(this);
|
|
},
|
|
disconnect: function(f) {
|
|
let pr = pseudoRedirector;
|
|
pr.filters.delete(f);
|
|
f.disconnect();
|
|
}
|
|
};
|
|
|
|
let onBeforeRequestClient = this.onBeforeRequest.callback;
|
|
let onBeforeRequest = function(details) {
|
|
normalizeRequestDetails(details);
|
|
let r = onBeforeRequestClient(details);
|
|
if (
|
|
r !== undefined &&
|
|
r.redirectUrl !== undefined &&
|
|
details.type === 'xmlhttprequest'
|
|
) {
|
|
r.redirectUrl = pseudoRedirector.start(
|
|
details.requestId,
|
|
r.redirectUrl
|
|
);
|
|
}
|
|
return r;
|
|
};
|
|
|
|
if ( onBeforeRequest ) {
|
|
let urls = this.onBeforeRequest.urls || ['<all_urls>'];
|
|
let types = this.onBeforeRequest.types || undefined;
|
|
if (
|
|
(validTypes.has('websocket')) &&
|
|
(types === undefined || types.indexOf('websocket') !== -1) &&
|
|
(urls.indexOf('<all_urls>') === -1)
|
|
) {
|
|
if ( urls.indexOf('ws://*/*') === -1 ) {
|
|
urls.push('ws://*/*');
|
|
}
|
|
if ( urls.indexOf('wss://*/*') === -1 ) {
|
|
urls.push('wss://*/*');
|
|
}
|
|
}
|
|
wrApi.onBeforeRequest.addListener(
|
|
onBeforeRequest,
|
|
{ urls: urls, types: types },
|
|
this.onBeforeRequest.extra
|
|
);
|
|
}
|
|
|
|
// https://github.com/gorhill/uBlock/issues/3140
|
|
if ( typeof this.onBeforeMaybeSpuriousCSPReport.callback === 'function' ) {
|
|
wrApi.onBeforeRequest.addListener(
|
|
this.onBeforeMaybeSpuriousCSPReport.callback,
|
|
{
|
|
urls: [ 'http://*/*', 'https://*/*' ],
|
|
types: [ 'csp_report' ]
|
|
},
|
|
[ 'blocking', 'requestBody' ]
|
|
);
|
|
}
|
|
|
|
let onHeadersReceivedClient = this.onHeadersReceived.callback,
|
|
onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0),
|
|
onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes);
|
|
let onHeadersReceived = function(details) {
|
|
normalizeRequestDetails(details);
|
|
if (
|
|
onHeadersReceivedClientTypes.length !== 0 &&
|
|
onHeadersReceivedClientTypes.indexOf(details.type) === -1
|
|
) {
|
|
return;
|
|
}
|
|
return onHeadersReceivedClient(details);
|
|
};
|
|
|
|
if ( onHeadersReceived ) {
|
|
let urls = this.onHeadersReceived.urls || ['<all_urls>'];
|
|
let types = onHeadersReceivedTypes;
|
|
wrApi.onHeadersReceived.addListener(
|
|
onHeadersReceived,
|
|
{ urls: urls, types: types },
|
|
this.onHeadersReceived.extra
|
|
);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|