diff options
Diffstat (limited to 'src/lib/basedomain.js')
-rw-r--r-- | src/lib/basedomain.js | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/src/lib/basedomain.js b/src/lib/basedomain.js new file mode 100644 index 0000000..32e8768 --- /dev/null +++ b/src/lib/basedomain.js @@ -0,0 +1,319 @@ +/*! + * Parts of original code from ipv6.js <https://github.com/beaugunderson/javascript-ipv6> + * Copyright 2011 Beau Gunderson + * Available under MIT license <http://mths.be/mit> + */ + +/* globals punycode:false */ + +const RE_V4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|0x[0-9a-f][0-9a-f]?|0[0-7]{3})\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|0x[0-9a-f][0-9a-f]?|0[0-7]{3})$/i; +const RE_V4_HEX = /^0x([0-9a-f]{8})$/i; +const RE_V4_NUMERIC = /^[0-9]+$/; +const RE_V4inV6 = /(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + +const RE_BAD_CHARACTERS = /([^0-9a-f:])/i; +const RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]$)/i; + +function isIPv4(address) { + if (RE_V4.test(address)) { + return true; + } + if (RE_V4_HEX.test(address)) { + return true; + } + if (RE_V4_NUMERIC.test(address)) { + return true; + } + return false; +} + +function isIPv6(address) { + var a4addon = 0; + var address4 = address.match(RE_V4inV6); + if (address4) { + var temp4 = address4[0].split('.'); + for (var i = 0; i < 4; i++) { + if (/^0[0-9]+/.test(temp4[i])) { + return false; + } + } + address = address.replace(RE_V4inV6, ''); + if (/[0-9]$/.test(address)) { + return false; + } + + address = address + temp4.join(':'); + a4addon = 2; + } + + if (RE_BAD_CHARACTERS.test(address)) { + return false; + } + + if (RE_BAD_ADDRESS.test(address)) { + return false; + } + + function count(string, substring) { + return (string.length - string.replace(new RegExp(substring,"g"), '').length) / substring.length; + } + + var halves = count(address, '::'); + if (halves == 1 && count(address, ':') <= 6 + 2 + a4addon) { + return true; + } + if (halves == 0 && count(address, ':') == 7 + a4addon) { + return true; + } + return false; +} + +/** + * Returns base domain for specified host based on Public Suffix List. + * @param {String} hostname The name of the host to get the base domain for + */ +function getBaseDomain(/**String*/ hostname) /**String*/ { + // remove trailing dot(s) + hostname = hostname.replace(/\.+$/, ''); + + // return IP address untouched + if (isIPv6(hostname) || isIPv4(hostname)) { + return hostname; + } + + // decode punycode if exists + if (hostname.indexOf('xn--') >= 0) { + hostname = punycode.toUnicode(hostname); + } + + // search through PSL + var prevDomains = []; + var curDomain = hostname; + var nextDot = curDomain.indexOf('.'); + var tld = 0; + + for (;;) { + var suffix = window.publicSuffixes[curDomain]; + if (typeof suffix != 'undefined') { + tld = suffix; + break; + } + + if (nextDot < 0) { + tld = 1; + break; + } + + prevDomains.push(curDomain.substring(0,nextDot)); + curDomain = curDomain.substring(nextDot+1); + nextDot = curDomain.indexOf('.'); + } + + while (tld > 0 && prevDomains.length > 0) { + curDomain = prevDomains.pop() + '.' + curDomain; + tld--; + } + + return curDomain; +} + +/** + * Converts an IP address to a number. If given input is not a valid IP address + * then 0 is returned. + * @param {String} ip The IP address to convert + * @returns {Integer} + */ +function ipAddressToNumber(ip) { + // Separate IP address into octets, make sure there are four. + var octets = ip.split("."); + if (octets.length !== 4) { + return 0; + } + + var result = 0; + var maxOctetIndex = 3; + for (var i = maxOctetIndex; i >= 0; i--) { + var octet = parseInt(octets[maxOctetIndex - i], 10); + + // If octet is invalid return early, no need to continue. + if (Number.isNaN(octet) || octet < 0 || octet > 255) { + return 0; + } + + // Use bit shifting to store each octet for result. + result |= octet << (i * 8); // eslint-disable-line no-bitwise + } + + // Results of bitwise operations in JS are interpreted as signed + // so use zero-fill right shift to return unsigned number. + return result >>> 0; // eslint-disable-line no-bitwise +} + +/** + * Determines if domain is private, that is localhost or the IP address spaces + * specified by RFC 1918. + * @param {String} domain The domain to check + * @returns {Boolean} + */ +function isPrivateDomain(domain) { // eslint-disable-line no-unused-vars + // Check for localhost match. + if (domain === "localhost") { + return true; + } + + // Check for private IP match. + var ipNumber = ipAddressToNumber(domain); + var privateIpMasks = { + "127.0.0.0": "255.0.0.0", + "10.0.0.0": "255.0.0.0", + "172.16.0.0": "255.240.0.0", + "192.168.0.0": "255.255.0.0", + }; + for (var ip in privateIpMasks) { + // Ignore object properties. + if (!privateIpMasks.hasOwnProperty(ip)) { + continue; + } + + // Compare given IP value to private IP value using bitwise AND. + // Make sure result of AND is unsigned by using zero-fill right shift. + var privateIpNumber = ipAddressToNumber(ip); + var privateMaskNumber = ipAddressToNumber(privateIpMasks[ip]); + if (((ipNumber & privateMaskNumber) >>> 0) === privateIpNumber) { // eslint-disable-line no-bitwise + return true; + } + } + + // Getting here means given host didn't match localhost + // or other private addresses so return false. + return false; +} + +/** + * Checks whether a request is third party for the given document, uses + * information from the public suffix list to determine the effective domain + * name for the document. + * @param {String} requestHost The host of the 3rd party request + * @param {String} documentHost The host of the document + */ +function isThirdParty(/**String*/ requestHost, /**String*/ documentHost) { // eslint-disable-line no-unused-vars + // Remove trailing dots + requestHost = requestHost.replace(/\.+$/, ""); + documentHost = documentHost.replace(/\.+$/, ""); + + // Extract domain name - leave IP addresses unchanged, otherwise leave only base domain + var documentDomain = getBaseDomain(documentHost); + if (requestHost.length > documentDomain.length) { + return (requestHost.substr(requestHost.length - documentDomain.length - 1) != "." + documentDomain); + } else { + return (requestHost != documentDomain); + } +} + +/** + * Extracts host name from a URL. + */ +function extractHostFromURL(/**String*/ url) { // eslint-disable-line no-unused-vars + if (url && extractHostFromURL._lastURL == url) { + return extractHostFromURL._lastDomain; + } + + var host = ""; + try { + host = new URI(url).host; + } catch (e) { + // Keep the empty string for invalid URIs. + } + + extractHostFromURL._lastURL = url; + extractHostFromURL._lastDomain = host; + return host; +} + +/** + * Parses URLs and provides an interface similar to nsIURI in Gecko, see + * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIURI. + * TODO: Make sure the parsing actually works the same as nsStandardURL. + * @constructor + */ +function URI(/**String*/ spec) { + this.spec = spec; + this._schemeEnd = spec.indexOf(":"); + if (this._schemeEnd < 0) { + throw new Error("Invalid URI scheme"); + } + + if (spec.substr(this._schemeEnd + 1, 2) != "//") { + // special case for filesystem, blob URIs + if (this.scheme === "filesystem" || this.scheme === "blob") { + this._schemeEnd = spec.indexOf(":", this._schemeEnd + 1); + if (spec.substr(this._schemeEnd + 1, 2) != "//") { + throw new Error("Unexpected URI structure"); + } + } else { + throw new Error("Unexpected URI structure"); + } + } + + this._hostPortStart = this._schemeEnd + 3; + this._hostPortEnd = spec.indexOf("/", this._hostPortStart); + if (this._hostPortEnd < 0) { + throw new Error("Invalid URI host"); + } + + var authEnd = spec.indexOf("@", this._hostPortStart); + if (authEnd >= 0 && authEnd < this._hostPortEnd) { + this._hostPortStart = authEnd + 1; + } + + this._portStart = -1; + this._hostEnd = spec.indexOf("]", this._hostPortStart + 1); + if (spec[this._hostPortStart] == "[" && this._hostEnd >= 0 && this._hostEnd < this._hostPortEnd) { + // The host is an IPv6 literal + this._hostStart = this._hostPortStart + 1; + if (spec[this._hostEnd + 1] == ":") { + this._portStart = this._hostEnd + 2; + } + } else { + this._hostStart = this._hostPortStart; + this._hostEnd = spec.indexOf(":", this._hostStart); + if (this._hostEnd >= 0 && this._hostEnd < this._hostPortEnd) { + this._portStart = this._hostEnd + 1; + } else { + this._hostEnd = this._hostPortEnd; + } + } +} +URI.prototype = { + spec: null, + get scheme() { + return this.spec.substring(0, this._schemeEnd).toLowerCase(); + }, + get host() { + return this.spec.substring(this._hostStart, this._hostEnd); + }, + get asciiHost() { + var host = this.host; + if (/^[\x00-\x7F]+$/.test(host)) { // eslint-disable-line no-control-regex + return host; + } else { + return punycode.toASCII(host); + } + }, + get hostPort() { + return this.spec.substring(this._hostPortStart, this._hostPortEnd); + }, + get port() { + if (this._portStart < 0) { + return -1; + } else { + return parseInt(this.spec.substring(this._portStart, this._hostPortEnd), 10); + } + }, + get path() { + return this.spec.substring(this._hostPortEnd); + }, + get prePath() { + return this.spec.substring(0, this._hostPortEnd); + } +}; |