summaryrefslogtreecommitdiffstats
path: root/src/lib/basedomain.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/basedomain.js')
-rw-r--r--src/lib/basedomain.js319
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);
+ }
+};