diff options
Diffstat (limited to 'lib/PublicSuffixList.jsm')
-rw-r--r-- | lib/PublicSuffixList.jsm | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/lib/PublicSuffixList.jsm b/lib/PublicSuffixList.jsm new file mode 100644 index 0000000..72b0669 --- /dev/null +++ b/lib/PublicSuffixList.jsm @@ -0,0 +1,308 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +var EXPORTED_SYMBOLS = ['publicSuffixList']; + +var exceptions = {}; +var rules = {}; +var magic = 'iscjsfsaolnm'; + +// This value dictate how the search will be performed: +// < cutoffLength → indexOf() +// >= cutoffLength → binary search +var cutoffLength = 256; + +var reMustPunycode = /[^\w.*-]/; + +var onChangedListeners = []; + +function search(store, hostname) { + let pos = hostname.lastIndexOf('.'); + let tld; + let remainder; + + if (pos < 0) { + tld = hostname; + remainder = hostname; + } else { + tld = hostname.slice(pos+1); + remainder = hostname.slice(0, pos); + } + + let sub = store[tld]; + if (!sub) { + return false; + } + + if (typeof sub === 'string') { + return (sub.indexOf(' '+remainder+' ') >= 0); + } + + let l = remainder.length; + let val = sub[l]; + if (!val) { + return false; + } + + let left = 0; + let right = Math.floor(val.length/l+0.5); + + while (left < right) { + let i = left+right >> 1; + let key = val.substr(l*i, l); + if (remainder < key) { + right = i; + } else if (remainder > key) { + left = i+1; + } else { + return true; + } + } + + return false; +} + +function getPublicSuffix(hostname) { + if (!hostname) { + return ''; + } + + while (true) { + let pos = hostname.indexOf('.'); + if (pos < 0) { + return hostname; + } + + if (search(exceptions, hostname)) { + return hostname.slice(pos+1); + } + + if (search(rules, hostname)) { + return hostname; + } + + if (search(rules, '*'+hostname.slice(pos))) { + return hostname; + } + + hostname = hostname.slice(pos+1); + } +} + +function getDomain(hostname) { + if (!hostname || hostname.charAt(0) == '.') { + return ''; + } + + hostname = hostname.toLowerCase(); + + let suffix = getPublicSuffix(hostname); + if (suffix === hostname) { + return ''; + } + + let len = hostname.length-suffix.length; + let pos = hostname.lastIndexOf('.', hostname.lastIndexOf('.', len) - 1); + if (pos <= 0) { + return hostname; + } + + return hostname.slice(pos+1); +} + +function crystallize(store) { + for (let tld in store) { + if (!store.hasOwnProperty(tld)) { + continue; + } + + let suff = store[tld].join(' '); + if (!suff) { + store[tld] = ''; + continue; + } + + if (suff.length < cutoffLength) { + store[tld] = ' ' + suff + ' '; + continue; + } + + suff = []; + for (let i=store[tld].length-1; i>=0; --i) { + let s = store[tld][i]; + let l = s.length; + if (!suff[l]) { + suff[l] = []; + } + suff[l].push(s); + } + + for (let i=suff.length-1; i>=0; --i) { + if (suff[i]) { + suff[i] = suff[i].sort().join(''); + } + } + + store[tld] = suff; + } + + return store; +} + +function parse(text, toAscii) { + exceptions = {}; + rules = {}; + + let beg = 0; + let end = 0; + let tend = text.length; + + while (beg < tend) { + end = text.indexOf('\n', beg); + if (end < 0) { + end = text.indexOf('\r', beg); + if (end < 0) { + end = tend; + } + } + + let line = text.slice(beg, end).trim(); + beg = end+1; + + if (line.length === 0) { + continue; + } + + let pos = line.indexOf('//'); + if (pos >= 0) { + line = line.slice(0, pos); + } + + line = line.trim(); + if (!line) { + continue; + } + + let store; + if (line.charAt(0) == '!') { + store = exceptions; + line = line.slice(1); + } else { + store = rules; + } + + if (reMustPunycode.test(line)) { + line = toAscii(line); + } + + line = line.toLowerCase(); + + let tld; + pos = line.lastIndexOf('.'); + if (pos < 0) { + tld = line; + } else { + tld = line.slice(pos+1); + line = line.slice(0, pos); + } + + if (!store.hasOwnProperty(tld)) { + store[tld] = []; + } + + if (line) { + store[tld].push(line); + } + } + + crystallize(exceptions); + crystallize(rules); + + callListeners(onChangedListeners); +} + +function toSelfie() { + return { + magic: magic, + rules: rules, + exceptions: exception, + }; +} + +function fromSelfie(selfie) { + if (typeof selfie !== 'object' || typeof selfie.magic !== 'string' + || selfie.magic !== magic) { + return false; + } + + rules = selfie.rules; + exceptions = selfie.exceptions; + callListeners(onChangedListeners); + + return true; +} + +var addListener = function (listeners, callback) { + if (typeof callback !== 'function') { + return; + } + + if (listeners.indexOf(callback) === -1) { + listeners.push(callback); + } +}; + +var removeListener = function (listeners, callback) { + let pos = listeners.indexOf(callback); + if (pos !== -1) { + listeners.splice(pos, 1); + } +}; + +var callListeners = function (listeners) { + for (let i=0; i<listeners.length; ++i) { + listeners[i](); + } +}; + +var onChanged = { + addListener: function (callback) { + addListener(onChangedListeners, callback); + }, + removeListener: function (callback) { + removeListener(onChangedListeners, callback); + }, +}; + +var publicSuffixList = { + version: '1.0', + + parse: parse, + getDomain: getDomain, + getPublicSuffix: getPublicSuffix, + toSelfie: toSelfie, + fromSelfie: fromSelfie, + onChanged: onChanged, +} |