'use strict'; /** * Polyfill for `endsWith` * * @param {string} str * @param {string} pattern * @return {boolean} */ function endsWith(str, pattern) { return ( str.lastIndexOf(pattern) === (str.length - pattern.length) ); } /** * Check if `vhost` is a valid suffix of `hostname` (top-domain) * * It means that `vhost` needs to be a suffix of `hostname` and we then need to * make sure that: either they are equal, or the character preceding `vhost` in * `hostname` is a '.' (it should not be a partial label). * * * hostname = 'not.evil.com' and vhost = 'vil.com' => not ok * * hostname = 'not.evil.com' and vhost = 'evil.com' => ok * * hostname = 'not.evil.com' and vhost = 'not.evil.com' => ok * * @param {string} hostname * @param {string} vhost * @return {boolean} */ function shareSameDomainSuffix(hostname, vhost) { if (endsWith(hostname, vhost)) { return ( hostname.length === vhost.length || hostname[hostname.length - vhost.length - 1] === '.' ); } return false; } /** * Given a hostname and its public suffix, extract the general domain. * * @param {string} hostname * @param {string} publicSuffix * @return {string} */ function extractDomainWithSuffix(hostname, publicSuffix) { // Locate the index of the last '.' in the part of the `hostname` preceding // the public suffix. // // examples: // 1. not.evil.co.uk => evil.co.uk // ^ ^ // | | start of public suffix // | index of the last dot // // 2. example.co.uk => example.co.uk // ^ ^ // | | start of public suffix // | // | (-1) no dot found before the public suffix var publicSuffixIndex = hostname.length - publicSuffix.length - 2; var lastDotBeforeSuffixIndex = hostname.lastIndexOf('.', publicSuffixIndex); // No '.' found, then `hostname` is the general domain (no sub-domain) if (lastDotBeforeSuffixIndex === -1) { return hostname; } // Extract the part between the last '.' return hostname.substr(lastDotBeforeSuffixIndex + 1); } /** * Detects the domain based on rules and upon and a host string * * @api * @param {string} host * @return {String} */ module.exports = function getDomain(validHosts, suffix, hostname) { // Check if `hostname` ends with a member of `validHosts`. for (var i = 0; i < validHosts.length; i += 1) { var vhost = validHosts[i]; if (shareSameDomainSuffix(hostname, vhost)) { return vhost; } } // If there is no suffix, there is no hostname if (suffix === null) { return null; } // If `hostname` is a valid public suffix, then there is no domain to return. // Since we already know that `getPublicSuffix` returns a suffix of `hostname` // there is no need to perform a string comparison and we only compare the // size. if (suffix.length === hostname.length) { return null; } // To extract the general domain, we start by identifying the public suffix // (if any), then consider the domain to be the public suffix with one added // level of depth. (e.g.: if hostname is `not.evil.co.uk` and public suffix: // `co.uk`, then we take one more level: `evil`, giving the final result: // `evil.co.uk`). return extractDomainWithSuffix(hostname, suffix); };