diff --git a/index.js b/index.js index 8586bd5..9758054 100644 --- a/index.js +++ b/index.js @@ -6,11 +6,12 @@ var allRules = Trie.fromJson(require('./rules.json')); var cleanHostValue = require('./lib/clean-host.js'); var getDomain = require('./lib/domain.js'); +var getPublicSuffix = require('./lib/public-suffix.js'); var getSubdomain = require('./lib/subdomain.js'); var isValid = require('./lib/is-valid.js'); -var getPublicSuffix = require('./lib/public-suffix.js'); var tldExists = require('./lib/tld-exists.js'); + /** * Creates a new instance of tldjs * @param {Object.} options [description] @@ -22,23 +23,24 @@ function factory(options) { return { cleanHostValue: cleanHostValue, - getDomain: function (host, isHostClean) { - return getDomain(rules, validHosts, host, isHostClean); + getDomain: function (hostname) { + return getDomain(rules, validHosts, hostname); }, - getSubdomain: function (host, isHostClean) { - return getSubdomain(rules, validHosts, host, isHostClean); + getSubdomain: function (hostname) { + return getSubdomain(rules, validHosts, hostname); }, - isValid: function (host) { - return isValid(validHosts, host); + isValid: function (hostname) { + return isValid(validHosts, hostname); }, - getPublicSuffix: function (host, isHostClean) { - return getPublicSuffix(rules, host, isHostClean); + getPublicSuffix: function (hostname) { + return getPublicSuffix(rules, hostname); }, - tldExists: function (tld, isHostClean) { - return tldExists(rules, tld, isHostClean); + tldExists: function (tld) { + return tldExists(rules, tld); }, fromUserSettings: factory }; } + module.exports = factory({ validHosts: [], rules: allRules }); diff --git a/lib/clean-host.js b/lib/clean-host.js index df26aa4..a278909 100644 --- a/lib/clean-host.js +++ b/lib/clean-host.js @@ -18,21 +18,21 @@ var hasPrefixRE = /^(([a-z][a-z0-9+.-]*)?:)?\/\//; var invalidHostnameChars = /[^A-Za-z0-9.-]/; var trailingDotsRE = /[.]+$/g; -//@see https://github.com/oncletom/tld.js/issues/95 +// @see https://github.com/oncletom/tld.js/issues/95 function rtrim(value) { return String(value).replace(trailingDotsRE, ''); } -module.exports = function cleanHostValue(value, isHostClean) { - if (isHostClean) { - return value; - } - +module.exports = function cleanHostValue(value) { value = String(value).trim().toLowerCase(); var parts = URL.parse(hasPrefixRE.test(value) ? value : '//' + value, null, true); - if (parts.hostname && !invalidHostnameChars.test(parts.hostname)) { return rtrim(parts.hostname); } - if (!invalidHostnameChars.test(value)) { return rtrim(value); } + if (parts.hostname && !invalidHostnameChars.test(parts.hostname)) { + return rtrim(parts.hostname); + } else if (!invalidHostnameChars.test(value)) { + return rtrim(value); + } + return ''; }; diff --git a/lib/domain.js b/lib/domain.js index aad1d90..1014231 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -1,7 +1,86 @@ +"use strict"; + var isValid = require('./is-valid.js'); var cleanHostValue = require('./clean-host.js'); var getPublicSuffix = require('./public-suffix.js'); + +/** + * 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 * @@ -9,43 +88,35 @@ var getPublicSuffix = require('./public-suffix.js'); * @param {string} host * @return {String} */ -module.exports = function getDomain(allRules, validHosts, host, isHostClean) { - var _validHosts = validHosts || []; - var cleanHost = cleanHostValue(host, isHostClean); +module.exports = function getDomain(rules, validHosts, hostname) { + hostname = cleanHostValue(hostname); - if (isValid(_validHosts, cleanHost) === false) { + if (isValid(validHosts, hostname) === false) { return null; } - // Check if `host` ends with '.' followed by one host specified in validHosts. - for (var i = 0; i < validHosts.length; i++) { + // Check if `hostname` ends with a member of `validHosts`. + for (var i = 0; i < validHosts.length; i += 1) { var vhost = validHosts[i]; - if (cleanHost.indexOf(vhost) === (cleanHost.length - vhost.length) && ( - cleanHost.length === vhost.length || - cleanHost[cleanHost.length - vhost.length - 1] === '.')) { + if (shareSameDomainSuffix(hostname, vhost)) { return vhost; } } - var suffix = getPublicSuffix(allRules, cleanHost, true); - if (suffix === null) { - // TODO - shouldn't it be null? - // Otherwise 'should return the known valid host' fails - // return cleanHost; - 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`). + var suffix = getPublicSuffix(rules, hostname); - if (suffix.length === cleanHost.length) { + // 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; } - // google.fr (length 9) - // suffix = fr (length 2) - // 5 = 9 - 2 - 1 (ignore the dot) - 1 (zero-based indexing) - var lastDotBeforeSuffixIndex = cleanHost.lastIndexOf('.', cleanHost.length - suffix.length - 2); - if (lastDotBeforeSuffixIndex === -1) { - return cleanHost; - } - - return cleanHost.substring(lastDotBeforeSuffixIndex + 1); + return extractDomainWithSuffix(hostname, suffix); }; diff --git a/lib/from-host.js b/lib/from-host.js index 071af1e..e6414bb 100644 --- a/lib/from-host.js +++ b/lib/from-host.js @@ -1,3 +1,5 @@ +"use strict"; + /** * Utility to extract the TLD from a host string * @@ -10,5 +12,5 @@ module.exports = function extractTldFromHost(host) { return null; } - return host.substring(lastDotIndex + 1); + return host.substr(lastDotIndex + 1); }; diff --git a/lib/is-valid.js b/lib/is-valid.js index 666b473..9891125 100644 --- a/lib/is-valid.js +++ b/lib/is-valid.js @@ -1,3 +1,5 @@ +"use strict"; + /** * Checking if a host string is valid * It's usually a preliminary check before trying to use getDomain or anything else @@ -8,6 +10,6 @@ * @param host {String} * @return {Boolean} */ -module.exports = function isValid (validHosts, host) { +module.exports = function isValid(validHosts, host) { return typeof host === 'string' && (validHosts.indexOf(host) !== -1 || (host.indexOf('.') !== -1 && host[0] !== '.')); }; diff --git a/lib/parsers/publicsuffix-org.js b/lib/parsers/publicsuffix-org.js index 3b601a3..85e928c 100644 --- a/lib/parsers/publicsuffix-org.js +++ b/lib/parsers/publicsuffix-org.js @@ -9,12 +9,12 @@ var PublicSuffixOrgParser = {}; /** * Filters a commented or empty line * - * @param row {String} - * @return {String|null} + * @param {string} row + * @return {string|null} */ function keepOnlyRules(row) { var trimmed = row.trim(); - if (!trimmed || trimmed.indexOf('//') === 0) { + if (trimmed.length === 0 || trimmed.indexOf('//') === 0) { return null; } @@ -27,7 +27,8 @@ function keepOnlyRules(row) { /** * Returns a rule based on string analysis * - * @param rule {PublicSuffixRule} + * @param {string} row + * @return {object} a public suffix rule */ function domainBuilder(row) { var rule = { @@ -36,16 +37,20 @@ function domainBuilder(row) { parts: null, }; - var spaceIndex = row.indexOf(' '); // Only read line up to the first white-space + var spaceIndex = row.indexOf(' '); + if (spaceIndex !== -1) { + row = row.substr(0, spaceIndex); + } + row = punycode.toASCII(row); - //setting initial rule + // Keep track of initial rule rule.source = row; - // exceptions + // Exception if (row[0] === '!') { - row = row.substring(1); + row = row.substr(1); rule.exception = true; } diff --git a/lib/public-suffix.js b/lib/public-suffix.js index 2efd892..d6d0814 100644 --- a/lib/public-suffix.js +++ b/lib/public-suffix.js @@ -1,3 +1,5 @@ +"use strict"; + var cleanHostValue = require('./clean-host.js'); var extractTldFromHost = require('./from-host.js'); @@ -6,21 +8,23 @@ var extractTldFromHost = require('./from-host.js'); * * @api * @since 1.5 - * @param {string} host - * @return {String} + * @param {string} hostname + * @return {string} */ -module.exports = function getPublicSuffix(rules, host, isHostClean) { - var cleanHost = cleanHostValue(host, isHostClean); +module.exports = function getPublicSuffix(rules, hostname) { + // Extract hostname + hostname = cleanHostValue(hostname); - // Host is a valid TLD - if (rules.hasTld(cleanHost)) { - return cleanHost; + // First check if `hostname` is already a valid top-level Domain. + if (rules.hasTld(hostname)) { + return hostname; } - var candidate = rules.suffixLookup(cleanHost); + var candidate = rules.suffixLookup(hostname); if (candidate === null) { - // Prevailing rule is '*' - return extractTldFromHost(cleanHost); + // Prevailing rule is '*' so we consider the top-level domain to be the + // public suffix of `hostname` (e.g.: 'example.org' => 'org'). + return extractTldFromHost(hostname); } return candidate; diff --git a/lib/subdomain.js b/lib/subdomain.js index 115afaf..b651495 100644 --- a/lib/subdomain.js +++ b/lib/subdomain.js @@ -1,21 +1,26 @@ +"use strict"; + var cleanHostValue = require('./clean-host.js'); var getDomain = require('./domain.js'); + /** - * Returns the subdomain of a host string + * Returns the subdomain of a hostname string * * @api - * @param {string} host - * @return {string|null} a subdomain string if any, blank string if subdomain is empty, otherwise null + * @param {string} hostname + * @return {string|null} a subdomain string if any, blank string if subdomain + * is empty, otherwise null. */ -module.exports = function getSubdomain(allRules, validHosts, host, isHostClean) { - var cleanHost = cleanHostValue(host, isHostClean); - var domain = getDomain(allRules, validHosts, cleanHost, true); +module.exports = function getSubdomain(rules, validHosts, hostname) { + hostname = cleanHostValue(hostname); + + var domain = getDomain(rules, validHosts, hostname); // No domain found? Just abort, abort! if (domain === null) { return null; } - return cleanHost.substring(0, cleanHost.length - domain.length - 1); + return hostname.substr(0, hostname.length - domain.length - 1); }; diff --git a/lib/suffix-trie.js b/lib/suffix-trie.js index ba5c6e6..cead4ed 100644 --- a/lib/suffix-trie.js +++ b/lib/suffix-trie.js @@ -1,21 +1,38 @@ "use strict"; - var VALID_HOSTNAME_VALUE = 0; +/** + * Return min(a, b), handling possible `null` values. + * + * @param {number|null} a + * @param {number|null} b + * @return {number|null} + */ function minIndex(a, b) { - if (a === null) return b; - if (b === null) return a; + if (a === null) { + return b; + } else if (b === null) { + return a; + } + return a < b ? a : b; } +/** + * Insert a public suffix rule in the `trie`. + * + * @param {object} rule + * @param {object} trie + * @return {object} trie (updated) + */ function insertInTrie(rule, trie) { var parts = rule.parts; var node = trie; - for (var i = 0; i < parts.length; i++) { + for (var i = 0; i < parts.length; i += 1) { var part = parts[i]; var nextNode = node[part]; if (nextNode === undefined) { @@ -32,26 +49,32 @@ function insertInTrie(rule, trie) { } +/** + * Recursive lookup of `parts` (starting at `index`) in the tree. + * + * @param {array} parts + * @param {object} trie + * @param {number} index - when to start in `parts` (initially: length - 1) + * @return {number} size of the suffix found (in number of parts matched) + */ function lookupInTrie(parts, trie, index) { var part; var nextNode; var publicSuffixIndex = null; - if (trie === undefined) { - return null; - } - - // We have a match + // We have a match! if (trie.$ !== undefined) { publicSuffixIndex = index + 1; } + // No more `parts` to look for if (index === -1) { return publicSuffixIndex; } - // Check branch corresponding to next part of hostname part = parts[index]; + + // Check branch corresponding to next part of hostname nextNode = trie[part]; if (nextNode !== undefined) { publicSuffixIndex = minIndex( @@ -60,7 +83,7 @@ function lookupInTrie(parts, trie, index) { ); } - // Check wildcard + // Check wildcard branch nextNode = trie['*']; if (nextNode !== undefined) { publicSuffixIndex = minIndex( @@ -73,20 +96,31 @@ function lookupInTrie(parts, trie, index) { } +/** + * Contains the public suffix ruleset as a Trie for efficient look-up. + * + * @constructor + */ function SuffixTrie(rules) { this.exceptions = Object.create(null); this.rules = Object.create(null); - (rules || []).forEach(function (rule) { - if (rule.exception) { - insertInTrie(rule, this.exceptions); - } else { - insertInTrie(rule, this.rules); + if (rules) { + for (var i = 0; i < rules.length; i += 1) { + var rule = rules[i]; + if (rule.exception) { + insertInTrie(rule, this.exceptions); + } else { + insertInTrie(rule, this.rules); + } } - }.bind(this)); + } } +/** + * Load the trie from JSON (as serialized by JSON.stringify). + */ SuffixTrie.fromJson = function (json) { var trie = new SuffixTrie(); @@ -97,17 +131,26 @@ SuffixTrie.fromJson = function (json) { }; +/** + * Check if `value` is a valid TLD. + */ SuffixTrie.prototype.hasTld = function (value) { + // All TLDs are at the root of the `trie`. return this.rules[value] !== undefined; }; +/** + * Check if `hostname` has a valid public suffix in `trie`. + * + * @param {string} hostname + * @return {string|null} public suffix + */ SuffixTrie.prototype.suffixLookup = function (hostname) { - var publicSuffix = null; var parts = hostname.split('.'); // Look for a match in rules - publicSuffixIndex = lookupInTrie( + var publicSuffixIndex = lookupInTrie( parts, this.rules, parts.length - 1 @@ -117,36 +160,18 @@ SuffixTrie.prototype.suffixLookup = function (hostname) { return null; } - publicSuffix = parts.slice(publicSuffixIndex).join('.'); - // Look for exceptions - var publicSuffixIndex = lookupInTrie( + var exceptionIndex = lookupInTrie( parts, this.exceptions, parts.length - 1 ); - if (publicSuffixIndex !== null) { - publicSuffix = parts.slice(publicSuffixIndex + 1).join('.'); + if (exceptionIndex !== null) { + return parts.slice(exceptionIndex + 1).join('.'); } - return publicSuffix; -}; - - -SuffixTrie.prototype.lookup = function (hostname) { - var candidate = this.suffixLookup(hostname); - - if (candidate !== hostname) { - return null; - } - - return candidate; -}; - - -SuffixTrie.prototype.has = function (hostname) { - return this.lookup(hostname) !== null; + return parts.slice(publicSuffixIndex).join('.'); }; diff --git a/lib/tld-exists.js b/lib/tld-exists.js index 8cd47ed..b712ed4 100644 --- a/lib/tld-exists.js +++ b/lib/tld-exists.js @@ -1,3 +1,5 @@ +"use strict"; + var cleanHostValue = require('./clean-host.js'); var extractTldFromHost = require('./from-host.js'); @@ -8,17 +10,16 @@ var extractTldFromHost = require('./from-host.js'); * @param {string} host * @return {boolean} */ -module.exports = function tldExists(rules, host, isHostClean) { - var cleanHost = cleanHostValue(host, isHostClean); - var hostTld; +module.exports = function tldExists(rules, hostname) { + hostname = cleanHostValue(hostname); // Easy case, it's a TLD - if (rules.hasTld(cleanHost)) { + if (rules.hasTld(hostname)) { return true; } // Popping only the TLD of the hostname - hostTld = extractTldFromHost(cleanHost); + var hostTld = extractTldFromHost(hostname); if (hostTld === null) { return false; } diff --git a/lib/updater.js b/lib/updater.js index c5a0691..668b171 100644 --- a/lib/updater.js +++ b/lib/updater.js @@ -11,7 +11,7 @@ var parser = require('./parsers/publicsuffix-org.js'); module.exports = { providerUrl: providerUrl, - run: function runUpdater(done){ + run: function runUpdater(done) { done = typeof done === 'function' ? done : function(){}; var req = http.request(providerUrl, function (res) { diff --git a/test/tld.js b/test/tld.js index 2e914c4..bf46b59 100644 --- a/test/tld.js +++ b/test/tld.js @@ -3,6 +3,7 @@ /* global suite, test */ var tld = require('../index.js'); +var parser = require('../lib/parsers/publicsuffix-org.js'); var expect = require('expect.js'); describe('tld.js', function () { @@ -154,8 +155,9 @@ describe('tld.js', function () { expect(tld.getPublicSuffix('google.co.uk')).to.be('co.uk'); }); + // @see https://github.com/oncletom/tld.js/pull/97 it('should return www.ck if www.www.ck', function () { - expect(tld.getPublicSuffix('www.www.ck')).to.be('www.ck'); + expect(tld.getPublicSuffix('www.www.ck')).to.be('ck'); }); //@see https://github.com/oncletom/tld.js/issues/30 @@ -175,11 +177,12 @@ describe('tld.js', function () { expect(tld.getPublicSuffix('microsoft.eu')).to.be('eu'); }); + // @see https://github.com/oncletom/tld.js/pull/97 it('should return null if the publicsuffix does not exist', function(){ - expect(tld.getPublicSuffix('www.freedom.nsa')).to.be(null); + expect(tld.getPublicSuffix('www.freedom.nsa')).to.be('nsa'); }); - //@see https://github.com/oncletom/tld.js/issues/95 + // @see https://github.com/oncletom/tld.js/issues/95 it('should ignore the trailing dot in a domain', function () { expect(tld.getPublicSuffix('https://www.google.co.uk./maps')).to.equal('co.uk'); }); @@ -321,26 +324,148 @@ describe('tld.js', function () { describe('validHosts', function(){ var customTld; - before(function(){ - customTld = tld.fromUserSettings({ - validHosts: ['localhost'] + context('non-empty array', function () { + before(function(){ + customTld = tld.fromUserSettings({ + validHosts: ['localhost'] + }); + }); + + it('should now be a valid host', function(){ + expect(customTld.isValid('localhost')).to.be(true); + }); + + it('should return the known valid host', function () { + expect(customTld.getDomain('localhost')).to.equal('localhost'); + expect(customTld.getDomain('subdomain.localhost')).to.equal('localhost'); + expect(customTld.getDomain('subdomain.notlocalhost')).to.equal('subdomain.notlocalhost'); + expect(customTld.getDomain('subdomain.not-localhost')).to.equal('subdomain.not-localhost'); + }); + + //@see https://github.com/oncletom/tld.js/issues/66 + it('should return the subdomain of a validHost', function(){ + expect(customTld.getSubdomain('vhost.localhost')).to.equal('vhost'); + }); + + it('should fallback to normal extraction if no match in validHost', function(){ + expect(customTld.getSubdomain('vhost.evil.com')).to.equal('vhost'); }); }); - it('should now be a valid host', function(){ - expect(customTld.isValid('localhost')).to.be(true); + context('empty value', function () { + it('falls-back to empty array', function () { + expect(function () { + customTld = tld.fromUserSettings({ validHosts: null }); + }).not.to.throwError(); + expect(function () { + customTld = tld.fromUserSettings({ validHosts: undefined }); + }).not.to.throwError(); + expect(function () { + customTld = tld.fromUserSettings({ validHosts: [] }); + }).not.to.throwError(); + }); }); + }); - it('should return the known valid host', function () { - expect(customTld.getDomain('localhost')).to.equal('localhost'); - expect(customTld.getDomain('subdomain.localhost')).to.equal('localhost'); - expect(customTld.getDomain('subdomain.notlocalhost')).to.equal('subdomain.notlocalhost'); - expect(customTld.getDomain('subdomain.not-localhost')).to.equal('subdomain.not-localhost'); + describe('SuffixTrie', function () { + it('should ignore empty line', function () { + var tlds = parser.parse('\n'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({}); + }); + + it('should ignore comment', function () { + var tlds = parser.parse('// \n'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({}); + }); + + it('should parse up to the first space', function () { + var tlds = parser.parse('co.uk .evil'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ uk: { co: { $: 0 } } }); + }); + + it('should parse normal rule', function () { + var tlds = parser.parse('co.uk'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ uk: { co: { $: 0 } } }); + }); + + it('should parse exception', function () { + var tlds = parser.parse('!co.uk'); + expect(tlds.exceptions).to.eql({ uk: { co: { $: 0 } } }); + expect(tlds.rules).to.eql({}); + }); + + it('should parse wildcard', function () { + var tlds = parser.parse('*'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ '*': { $: 0 } }); + expect(tlds.suffixLookup('foo')).to.equal('foo'); + + tlds = parser.parse('*.uk'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ uk: { '*': { $: 0 } } }); + expect(tlds.suffixLookup('bar.uk')).to.equal('bar.uk'); + expect(tlds.suffixLookup('bar.baz')).to.equal(null); + + tlds = parser.parse('foo.*.baz'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ baz: { '*': { foo: { $: 0 } } } }); + expect(tlds.suffixLookup('foo.bar.baz')).to.equal('foo.bar.baz'); + expect(tlds.suffixLookup('foo.foo.bar')).to.equal(null); + expect(tlds.suffixLookup('bar.foo.baz')).to.equal(null); + expect(tlds.suffixLookup('foo.baz')).to.equal(null); + expect(tlds.suffixLookup('baz')).to.equal(null); + + tlds = parser.parse('foo.bar.*'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ '*': { bar: { foo: { $: 0 } } } }); + expect(tlds.suffixLookup('foo.bar.baz')).to.equal('foo.bar.baz'); + expect(tlds.suffixLookup('foo.foo.bar')).to.equal(null); + + tlds = parser.parse('foo.*.*'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ '*': { '*': { foo: { $: 0 } } } }); + expect(tlds.suffixLookup('foo.bar.baz')).to.equal('foo.bar.baz'); + expect(tlds.suffixLookup('foo.foo.bar')).to.equal('foo.foo.bar'); + expect(tlds.suffixLookup('baz.foo.bar')).to.equal(null); + + tlds = parser.parse('fo.bar.*\nfoo.bar.baz'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ + baz: { + bar: { foo: { $: 0 } }, + }, + '*': { + bar: { fo: { $: 0 } }, + } + }); + expect(tlds.suffixLookup('foo.bar.baz')).to.equal('foo.bar.baz'); + + tlds = parser.parse('bar.*\nfoo.bar.baz'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ + baz: { + bar: { foo: { $: 0 } }, + }, + '*': { + bar: { $: 0 }, + } + }); + expect(tlds.suffixLookup('foo.bar.baz')).to.equal('foo.bar.baz'); }); - //@see https://github.com/oncletom/tld.js/issues/66 - it('should return the subdomain of a validHost', function(){ - expect(customTld.getSubdomain('vhost.localhost')).to.equal('vhost'); + it('should insert rules with same TLD', function () { + var tlds = parser.parse('co.uk\nca.uk'); + expect(tlds.exceptions).to.eql({}); + expect(tlds.rules).to.eql({ + uk: { + ca: { $: 0 }, + co: { $: 0 } + } + }); }); }); });