PublicSuffixList.jsm 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
  4. Copyright (C) 2019-2020-2021 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. 'use strict';
  19. var EXPORTED_SYMBOLS = ['publicSuffixList'];
  20. var exceptions = {};
  21. var rules = {};
  22. var magic = 'iscjsfsaolnm';
  23. // This value dictate how the search will be performed:
  24. // < cutoffLength → indexOf()
  25. // >= cutoffLength → binary search
  26. var cutoffLength = 256;
  27. var reMustPunycode = /[^\w.*-]/;
  28. var onChangedListeners = [];
  29. function search(store, hostname) {
  30. let pos = hostname.lastIndexOf('.');
  31. let tld;
  32. let remainder;
  33. if (pos < 0) {
  34. tld = hostname;
  35. remainder = hostname;
  36. } else {
  37. tld = hostname.slice(pos+1);
  38. remainder = hostname.slice(0, pos);
  39. }
  40. let sub = store[tld];
  41. if (!sub) {
  42. return false;
  43. }
  44. if (typeof sub === 'string') {
  45. return (sub.indexOf(' '+remainder+' ') >= 0);
  46. }
  47. let l = remainder.length;
  48. let val = sub[l];
  49. if (!val) {
  50. return false;
  51. }
  52. let left = 0;
  53. let right = Math.floor(val.length/l+0.5);
  54. while (left < right) {
  55. let i = left+right >> 1;
  56. let key = val.substr(l*i, l);
  57. if (remainder < key) {
  58. right = i;
  59. } else if (remainder > key) {
  60. left = i+1;
  61. } else {
  62. return true;
  63. }
  64. }
  65. return false;
  66. }
  67. function getPublicSuffix(hostname) {
  68. if (!hostname) {
  69. return '';
  70. }
  71. while (true) {
  72. let pos = hostname.indexOf('.');
  73. if (pos < 0) {
  74. return hostname;
  75. }
  76. if (search(exceptions, hostname)) {
  77. return hostname.slice(pos+1);
  78. }
  79. if (search(rules, hostname)) {
  80. return hostname;
  81. }
  82. if (search(rules, '*'+hostname.slice(pos))) {
  83. return hostname;
  84. }
  85. hostname = hostname.slice(pos+1);
  86. }
  87. }
  88. function getDomain(hostname) {
  89. if (!hostname || hostname.charAt(0) == '.') {
  90. return '';
  91. }
  92. hostname = hostname.toLowerCase();
  93. let suffix = getPublicSuffix(hostname);
  94. if (suffix === hostname) {
  95. return '';
  96. }
  97. let len = hostname.length-suffix.length;
  98. let pos = hostname.lastIndexOf('.', hostname.lastIndexOf('.', len) - 1);
  99. if (pos <= 0) {
  100. return hostname;
  101. }
  102. return hostname.slice(pos+1);
  103. }
  104. function crystallize(store) {
  105. for (let tld in store) {
  106. if (!store.hasOwnProperty(tld)) {
  107. continue;
  108. }
  109. let suff = store[tld].join(' ');
  110. if (!suff) {
  111. store[tld] = '';
  112. continue;
  113. }
  114. if (suff.length < cutoffLength) {
  115. store[tld] = ' ' + suff + ' ';
  116. continue;
  117. }
  118. suff = [];
  119. for (let i=store[tld].length-1; i>=0; --i) {
  120. let s = store[tld][i];
  121. let l = s.length;
  122. if (!suff[l]) {
  123. suff[l] = [];
  124. }
  125. suff[l].push(s);
  126. }
  127. for (let i=suff.length-1; i>=0; --i) {
  128. if (suff[i]) {
  129. suff[i] = suff[i].sort().join('');
  130. }
  131. }
  132. store[tld] = suff;
  133. }
  134. return store;
  135. }
  136. function parse(text, toAscii) {
  137. exceptions = {};
  138. rules = {};
  139. let beg = 0;
  140. let end = 0;
  141. let tend = text.length;
  142. while (beg < tend) {
  143. end = text.indexOf('\n', beg);
  144. if (end < 0) {
  145. end = text.indexOf('\r', beg);
  146. if (end < 0) {
  147. end = tend;
  148. }
  149. }
  150. let line = text.slice(beg, end).trim();
  151. beg = end+1;
  152. if (line.length === 0) {
  153. continue;
  154. }
  155. let pos = line.indexOf('//');
  156. if (pos >= 0) {
  157. line = line.slice(0, pos);
  158. }
  159. line = line.trim();
  160. if (!line) {
  161. continue;
  162. }
  163. let store;
  164. if (line.charAt(0) == '!') {
  165. store = exceptions;
  166. line = line.slice(1);
  167. } else {
  168. store = rules;
  169. }
  170. if (reMustPunycode.test(line)) {
  171. line = toAscii(line);
  172. }
  173. line = line.toLowerCase();
  174. let tld;
  175. pos = line.lastIndexOf('.');
  176. if (pos < 0) {
  177. tld = line;
  178. } else {
  179. tld = line.slice(pos+1);
  180. line = line.slice(0, pos);
  181. }
  182. if (!store.hasOwnProperty(tld)) {
  183. store[tld] = [];
  184. }
  185. if (line) {
  186. store[tld].push(line);
  187. }
  188. }
  189. crystallize(exceptions);
  190. crystallize(rules);
  191. callListeners(onChangedListeners);
  192. }
  193. function toSelfie() {
  194. return {
  195. magic: magic,
  196. rules: rules,
  197. exceptions: exception,
  198. };
  199. }
  200. function fromSelfie(selfie) {
  201. if (typeof selfie !== 'object' || typeof selfie.magic !== 'string'
  202. || selfie.magic !== magic) {
  203. return false;
  204. }
  205. rules = selfie.rules;
  206. exceptions = selfie.exceptions;
  207. callListeners(onChangedListeners);
  208. return true;
  209. }
  210. var addListener = function (listeners, callback) {
  211. if (typeof callback !== 'function') {
  212. return;
  213. }
  214. if (listeners.indexOf(callback) === -1) {
  215. listeners.push(callback);
  216. }
  217. };
  218. var removeListener = function (listeners, callback) {
  219. let pos = listeners.indexOf(callback);
  220. if (pos !== -1) {
  221. listeners.splice(pos, 1);
  222. }
  223. };
  224. var callListeners = function (listeners) {
  225. for (let i=0; i<listeners.length; ++i) {
  226. listeners[i]();
  227. }
  228. };
  229. var onChanged = {
  230. addListener: function (callback) {
  231. addListener(onChangedListeners, callback);
  232. },
  233. removeListener: function (callback) {
  234. removeListener(onChangedListeners, callback);
  235. },
  236. };
  237. var publicSuffixList = {
  238. version: '1.0',
  239. parse: parse,
  240. getDomain: getDomain,
  241. getPublicSuffix: getPublicSuffix,
  242. toSelfie: toSelfie,
  243. fromSelfie: fromSelfie,
  244. onChanged: onChanged,
  245. }