1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-17 16:02:33 +01:00
uBlock/src/js/uritools.js

381 lines
12 KiB
JavaScript
Raw Normal View History

2014-06-24 00:42:43 +02:00
/*******************************************************************************
2016-04-12 14:48:24 +02:00
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-present Raymond Hill
2014-06-24 00:42:43 +02:00
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
*/
2016-10-29 17:15:04 +02:00
'use strict';
2014-06-24 00:42:43 +02:00
/*******************************************************************************
RFC 3986 as reference: http://tools.ietf.org/html/rfc3986#appendix-A
Naming convention from https://en.wikipedia.org/wiki/URI_scheme#Examples
*/
/******************************************************************************/
Add ability to uncloak CNAME records Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/780 New webext permission added: `dns`, which purpose is to allow an extension to fetch the DNS record of specific hostnames, reference documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/dns The webext API `dns` is available in Firefox 60+ only. The new API will enable uBO to "uncloak" the actual hostname used in network requests. The ability is currently disabled by default for now -- this is only a first commit related to the above issue to allow advanced users to immediately use the new ability. Four advanced settings have been created to control the uncloaking of actual hostnames: cnameAliasList: a space-separated list of hostnames. Default value: unset => empty list. Special value: * => all hostnames. A space-separated list of hostnames => this tells uBO to "uncloak" the hostnames in the list will. cnameIgnoreList: a space-separated list of hostnames. Default value: unset => empty list. Special value: * => all hostnames. A space-separated list of hostnames => this tells uBO to NOT re-run the network request through uBO's filtering engine with the CNAME hostname. This is useful to exclude commonly used actual hostnames from being re-run through uBO's filtering engine, so as to avoid pointless overhead. cnameIgnore1stParty: boolean. Default value: true. Whether uBO should ignore to re-run a network request through the filtering engine when the CNAME hostname is 1st-party to the alias hostname. cnameMaxTTL: number of minutes. Default value: 120. This tells uBO to clear its CNAME cache after the specified time. For efficiency purpose, uBO will cache alias=>CNAME associations for reuse so as to reduce calls to `browser.dns.resolve`. All the associations will be cleared after the specified time to ensure the map does not grow too large and too ensure uBO uses up to date CNAME information.
2019-11-19 18:05:33 +01:00
µBlock.URI = (( ) => {
2014-06-24 00:42:43 +02:00
/******************************************************************************/
// Favorite regex tool: http://regex101.com/
// Ref: <http://tools.ietf.org/html/rfc3986#page-50>
// I removed redundant capture groups: capture less = peform faster. See
// <http://jsperf.com/old-uritools-vs-new-uritools>
// Performance improvements welcomed.
// jsperf: <http://jsperf.com/old-uritools-vs-new-uritools>
2019-02-24 15:01:58 +01:00
const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
2014-06-24 00:42:43 +02:00
// Derived
2019-02-24 15:01:58 +01:00
const reSchemeFromURI = /^[^:\/?#]+:/;
const reOriginFromURI = /^(?:[^:\/?#]+:)\/\/[^\/?#]+/;
const rePathFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/;
2014-06-24 00:42:43 +02:00
// These are to parse authority field, not parsed by above official regex
// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and
// if it fails, the IPv6 compatible regex istr used. This helps
// peformance by avoiding the use of a too complicated regex first.
// https://github.com/gorhill/httpswitchboard/issues/211
// "While a hostname may not contain other characters, such as the
// "underscore character (_), other DNS names may contain the underscore"
2019-02-24 15:01:58 +01:00
const reHostPortFromAuthority = /^(?:[^@]*@)?([^:]*)(:\d*)?$/;
const reIPv6PortFromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i;
2014-06-24 00:42:43 +02:00
2019-02-24 15:01:58 +01:00
const reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i;
2014-06-24 00:42:43 +02:00
// Coarse (but fast) tests
2019-02-24 15:01:58 +01:00
const reValidHostname = /^([a-z\d]+(-*[a-z\d]+)*)(\.[a-z\d]+(-*[a-z\d])*)*$/;
2014-06-24 00:42:43 +02:00
/******************************************************************************/
2019-02-24 15:01:58 +01:00
const reset = function(o) {
2014-06-24 00:42:43 +02:00
o.scheme = '';
o.hostname = '';
o._ipv4 = undefined;
o._ipv6 = undefined;
o.port = '';
o.path = '';
o.query = '';
o.fragment = '';
return o;
};
2019-02-24 15:01:58 +01:00
const resetAuthority = function(o) {
2014-06-24 00:42:43 +02:00
o.hostname = '';
o._ipv4 = undefined;
o._ipv6 = undefined;
o.port = '';
return o;
};
/******************************************************************************/
// This will be exported
2019-02-24 15:01:58 +01:00
const URI = {
2014-06-24 00:42:43 +02:00
scheme: '',
authority: '',
hostname: '',
_ipv4: undefined,
_ipv6: undefined,
port: '',
domain: undefined,
path: '',
query: '',
fragment: '',
schemeBit: (1 << 0),
userBit: (1 << 1),
passwordBit: (1 << 2),
hostnameBit: (1 << 3),
portBit: (1 << 4),
pathBit: (1 << 5),
queryBit: (1 << 6),
fragmentBit: (1 << 7),
allBits: (0xFFFF)
};
URI.authorityBit = (URI.userBit | URI.passwordBit | URI.hostnameBit | URI.portBit);
URI.normalizeBits = (URI.schemeBit | URI.hostnameBit | URI.pathBit | URI.queryBit);
/******************************************************************************/
// See: https://en.wikipedia.org/wiki/URI_scheme#Examples
// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
//
// foo://example.com:8042/over/there?name=ferret#nose
// \_/ \______________/\_________/ \_________/ \__/
// | | | | |
// scheme authority path query fragment
// | _____________________|__
// / \ / \
// urn:example:animal:ferret:nose
URI.set = function(uri) {
if ( uri === undefined ) {
return reset(URI);
}
2019-02-24 15:01:58 +01:00
let matches = reRFC3986.exec(uri);
2014-06-24 00:42:43 +02:00
if ( !matches ) {
return reset(URI);
}
this.scheme = matches[1] !== undefined ? matches[1].slice(0, -1) : '';
this.authority = matches[2] !== undefined ? matches[2].slice(2).toLowerCase() : '';
this.path = matches[3] !== undefined ? matches[3] : '';
// <http://tools.ietf.org/html/rfc3986#section-6.2.3>
// "In general, a URI that uses the generic syntax for authority
// "with an empty path should be normalized to a path of '/'."
if ( this.authority !== '' && this.path === '' ) {
this.path = '/';
}
this.query = matches[4] !== undefined ? matches[4].slice(1) : '';
this.fragment = matches[5] !== undefined ? matches[5].slice(1) : '';
// Assume very simple authority, i.e. just a hostname (highest likelihood
// case for µBlock)
if ( reHostFromNakedAuthority.test(this.authority) ) {
this.hostname = this.authority;
this.port = '';
return this;
}
// Authority contains more than just a hostname
matches = reHostPortFromAuthority.exec(this.authority);
if ( !matches ) {
matches = reIPv6PortFromAuthority.exec(this.authority);
if ( !matches ) {
return resetAuthority(URI);
}
}
this.hostname = matches[1] !== undefined ? matches[1] : '';
// http://en.wikipedia.org/wiki/FQDN
if ( this.hostname.endsWith('.') ) {
2014-06-24 00:42:43 +02:00
this.hostname = this.hostname.slice(0, -1);
}
this.port = matches[2] !== undefined ? matches[2].slice(1) : '';
return this;
};
/******************************************************************************/
// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
//
// foo://example.com:8042/over/there?name=ferret#nose
// \_/ \______________/\_________/ \_________/ \__/
// | | | | |
// scheme authority path query fragment
// | _____________________|__
// / \ / \
// urn:example:animal:ferret:nose
URI.assemble = function(bits) {
if ( bits === undefined ) {
bits = this.allBits;
}
2019-02-24 15:01:58 +01:00
const s = [];
2014-06-24 00:42:43 +02:00
if ( this.scheme && (bits & this.schemeBit) ) {
s.push(this.scheme, ':');
}
if ( this.hostname && (bits & this.hostnameBit) ) {
s.push('//', this.hostname);
}
if ( this.port && (bits & this.portBit) ) {
s.push(':', this.port);
}
if ( this.path && (bits & this.pathBit) ) {
s.push(this.path);
}
if ( this.query && (bits & this.queryBit) ) {
s.push('?', this.query);
}
if ( this.fragment && (bits & this.fragmentBit) ) {
s.push('#', this.fragment);
}
return s.join('');
};
/******************************************************************************/
2016-04-28 17:28:08 +02:00
URI.originFromURI = function(uri) {
const matches = reOriginFromURI.exec(uri);
2016-04-28 17:28:08 +02:00
return matches !== null ? matches[0].toLowerCase() : '';
};
/******************************************************************************/
2014-06-24 00:42:43 +02:00
URI.schemeFromURI = function(uri) {
2019-02-24 15:01:58 +01:00
const matches = reSchemeFromURI.exec(uri);
if ( !matches ) { return ''; }
2014-06-24 00:42:43 +02:00
return matches[0].slice(0, -1).toLowerCase();
};
/******************************************************************************/
Add ability to uncloak CNAME records Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/780 New webext permission added: `dns`, which purpose is to allow an extension to fetch the DNS record of specific hostnames, reference documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/dns The webext API `dns` is available in Firefox 60+ only. The new API will enable uBO to "uncloak" the actual hostname used in network requests. The ability is currently disabled by default for now -- this is only a first commit related to the above issue to allow advanced users to immediately use the new ability. Four advanced settings have been created to control the uncloaking of actual hostnames: cnameAliasList: a space-separated list of hostnames. Default value: unset => empty list. Special value: * => all hostnames. A space-separated list of hostnames => this tells uBO to "uncloak" the hostnames in the list will. cnameIgnoreList: a space-separated list of hostnames. Default value: unset => empty list. Special value: * => all hostnames. A space-separated list of hostnames => this tells uBO to NOT re-run the network request through uBO's filtering engine with the CNAME hostname. This is useful to exclude commonly used actual hostnames from being re-run through uBO's filtering engine, so as to avoid pointless overhead. cnameIgnore1stParty: boolean. Default value: true. Whether uBO should ignore to re-run a network request through the filtering engine when the CNAME hostname is 1st-party to the alias hostname. cnameMaxTTL: number of minutes. Default value: 120. This tells uBO to clear its CNAME cache after the specified time. For efficiency purpose, uBO will cache alias=>CNAME associations for reuse so as to reduce calls to `browser.dns.resolve`. All the associations will be cleared after the specified time to ensure the map does not grow too large and too ensure uBO uses up to date CNAME information.
2019-11-19 18:05:33 +01:00
URI.hostnameFromURI = vAPI.hostnameFromURI;
URI.domainFromHostname = vAPI.domainFromHostname;
2018-04-05 21:22:19 +02:00
2015-01-08 16:37:19 +01:00
URI.domain = function() {
return this.domainFromHostname(this.hostname);
};
/******************************************************************************/
URI.entityFromDomain = function(domain) {
2019-02-24 15:01:58 +01:00
const pos = domain.indexOf('.');
return pos !== -1 ? domain.slice(0, pos) + '.*' : '';
};
/******************************************************************************/
2016-01-22 17:13:29 +01:00
URI.pathFromURI = function(uri) {
2019-02-24 15:01:58 +01:00
const matches = rePathFromURI.exec(uri);
2016-01-22 17:13:29 +01:00
return matches !== null ? matches[1] : '';
};
/******************************************************************************/
2014-06-24 00:42:43 +02:00
URI.domainFromURI = function(uri) {
2019-02-24 15:01:58 +01:00
if ( !uri ) { return ''; }
2014-06-24 00:42:43 +02:00
return this.domainFromHostname(this.hostnameFromURI(uri));
};
/******************************************************************************/
2016-10-29 17:15:04 +02:00
URI.isNetworkURI = function(uri) {
2017-05-27 17:51:24 +02:00
return reNetworkURI.test(uri);
2016-10-29 17:15:04 +02:00
};
const reNetworkURI = /^(?:ftps?|https?|wss?):\/\//;
2017-05-27 17:51:24 +02:00
2016-10-29 17:15:04 +02:00
/******************************************************************************/
URI.isNetworkScheme = function(scheme) {
2017-05-27 17:51:24 +02:00
return reNetworkScheme.test(scheme);
2016-10-29 17:15:04 +02:00
};
const reNetworkScheme = /^(?:ftps?|https?|wss?)$/;
2017-05-27 17:51:24 +02:00
2016-10-29 17:15:04 +02:00
/******************************************************************************/
2014-06-24 00:42:43 +02:00
// Normalize the way µBlock expects it
URI.normalizedURI = function() {
// Will be removed:
// - port
// - user id/password
// - fragment
return this.assemble(this.normalizeBits);
};
/******************************************************************************/
URI.rootURL = function() {
2019-02-24 15:01:58 +01:00
if ( !this.hostname ) { return ''; }
2014-06-24 00:42:43 +02:00
return this.assemble(this.schemeBit | this.hostnameBit);
};
/******************************************************************************/
URI.isValidHostname = function(hostname) {
try {
2019-02-24 15:01:58 +01:00
return reValidHostname.test(hostname);
2014-06-24 00:42:43 +02:00
}
catch (e) {
}
2019-02-24 15:01:58 +01:00
return false;
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
// Return the parent domain. For IP address, there is no parent domain.
URI.parentHostnameFromHostname = function(hostname) {
// `locahost` => ``
// `example.org` => `example.org`
// `www.example.org` => `example.org`
// `tomato.www.example.org` => `example.org`
2019-02-24 15:01:58 +01:00
const domain = this.domainFromHostname(hostname);
2014-06-24 00:42:43 +02:00
// `locahost` === `` => bye
// `example.org` === `example.org` => bye
// `www.example.org` !== `example.org` => stay
// `tomato.www.example.org` !== `example.org` => stay
2019-02-24 15:01:58 +01:00
if ( domain === '' || domain === hostname ) { return; }
2014-06-24 00:42:43 +02:00
// Parent is hostname minus first label
return hostname.slice(hostname.indexOf('.') + 1);
};
/******************************************************************************/
// Return all possible parent hostnames which can be derived from `hostname`,
// ordered from direct parent up to domain inclusively.
URI.parentHostnamesFromHostname = function(hostname) {
// TODO: I should create an object which is optimized to receive
// the list of hostnames by making it reusable (junkyard etc.) and which
// has its own element counter property in order to avoid memory
// alloc/dealloc.
2019-02-24 15:01:58 +01:00
const domain = this.domainFromHostname(hostname);
2014-06-24 00:42:43 +02:00
if ( domain === '' || domain === hostname ) {
return [];
}
2019-02-24 15:01:58 +01:00
const nodes = [];
2014-06-24 00:42:43 +02:00
for (;;) {
2019-02-24 15:01:58 +01:00
const pos = hostname.indexOf('.');
if ( pos < 0 ) { break; }
2014-06-24 00:42:43 +02:00
hostname = hostname.slice(pos + 1);
nodes.push(hostname);
2019-02-24 15:01:58 +01:00
if ( hostname === domain ) { break; }
2014-06-24 00:42:43 +02:00
}
return nodes;
};
/******************************************************************************/
// Return all possible hostnames which can be derived from `hostname`,
// ordered from self up to domain inclusively.
URI.allHostnamesFromHostname = function(hostname) {
2019-02-24 15:01:58 +01:00
const nodes = this.parentHostnamesFromHostname(hostname);
2014-06-24 00:42:43 +02:00
nodes.unshift(hostname);
return nodes;
};
/******************************************************************************/
URI.toString = function() {
return this.assemble();
};
/******************************************************************************/
// Export
return URI;
/******************************************************************************/
})();
/******************************************************************************/