"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; } 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 += 1) { var part = parts[i]; var nextNode = node[part]; if (nextNode === undefined) { nextNode = Object.create(null); node[part] = nextNode; } node = nextNode; } node.$ = VALID_HOSTNAME_VALUE; return 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; // We have a match! if (trie.$ !== undefined) { publicSuffixIndex = index + 1; } // No more `parts` to look for if (index === -1) { return publicSuffixIndex; } part = parts[index]; // Check branch corresponding to next part of hostname nextNode = trie[part]; if (nextNode !== undefined) { publicSuffixIndex = minIndex( publicSuffixIndex, lookupInTrie(parts, nextNode, index - 1) ); } // Check wildcard branch nextNode = trie['*']; if (nextNode !== undefined) { publicSuffixIndex = minIndex( publicSuffixIndex, lookupInTrie(parts, nextNode, index - 1) ); } return publicSuffixIndex; } /** * 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); 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); } } } } /** * Load the trie from JSON (as serialized by JSON.stringify). */ SuffixTrie.fromJson = function (json) { var trie = new SuffixTrie(); trie.exceptions = json.exceptions; trie.rules = json.rules; return trie; }; /** * 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 parts = hostname.split('.'); // Look for a match in rules var publicSuffixIndex = lookupInTrie( parts, this.rules, parts.length - 1 ); if (publicSuffixIndex === null) { return null; } // Look for exceptions var exceptionIndex = lookupInTrie( parts, this.exceptions, parts.length - 1 ); if (exceptionIndex !== null) { return parts.slice(exceptionIndex + 1).join('.'); } return parts.slice(publicSuffixIndex).join('.'); }; module.exports = SuffixTrie;