matrix.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 Raymond Hill
  4. Copyright (C) 2019-2022 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. /* jshint bitwise: false */
  19. 'use strict';
  20. /******************************************************************************/
  21. ηMatrix.Matrix = (function() {
  22. Cu.import('chrome://ematrix/content/lib/Punycode.jsm');
  23. Cu.import('chrome://ematrix/content/lib/UriTools.jsm');
  24. /******************************************************************************/
  25. var ηm = ηMatrix;
  26. var magicId = 'axyorpwxtmnf';
  27. var uniqueIdGenerator = 1;
  28. /******************************************************************************/
  29. var Matrix = function() {
  30. this.id = uniqueIdGenerator++;
  31. this.reset();
  32. this.sourceRegister = '';
  33. this.decomposedSourceRegister = [''];
  34. this.specificityRegister = 0;
  35. };
  36. /******************************************************************************/
  37. Matrix.Transparent = 0;
  38. Matrix.Red = 1;
  39. Matrix.Green = 2;
  40. Matrix.Gray = 3;
  41. Matrix.Indirect = 0x00;
  42. Matrix.Direct = 0x80;
  43. Matrix.RedDirect = Matrix.Red | Matrix.Direct;
  44. Matrix.RedIndirect = Matrix.Red | Matrix.Indirect;
  45. Matrix.GreenDirect = Matrix.Green | Matrix.Direct;
  46. Matrix.GreenIndirect = Matrix.Green | Matrix.Indirect;
  47. Matrix.GrayDirect = Matrix.Gray | Matrix.Direct;
  48. Matrix.GrayIndirect = Matrix.Gray | Matrix.Indirect;
  49. /******************************************************************************/
  50. var typeBitOffsets = new Map([
  51. [ '*', 0 ],
  52. [ 'doc', 2 ],
  53. [ 'cookie', 4 ],
  54. [ 'css', 6 ],
  55. [ 'image', 8 ],
  56. [ 'media', 10 ],
  57. [ 'script', 12 ],
  58. [ 'xhr', 14 ],
  59. [ 'frame', 16 ],
  60. [ 'other', 18 ]
  61. ]);
  62. var stateToNameMap = new Map([
  63. [ 1, 'block' ],
  64. [ 2, 'allow' ],
  65. [ 3, 'inherit' ]
  66. ]);
  67. var nameToStateMap = {
  68. 'block': 1,
  69. 'allow': 2,
  70. 'noop': 2,
  71. 'inherit': 3
  72. };
  73. var switchBitOffsets = new Map([
  74. [ 'matrix-off', 0 ],
  75. [ 'https-strict', 2 ],
  76. /* 4 is now unused, formerly assigned to UA spoofing */
  77. [ 'referrer-spoof', 6 ],
  78. [ 'noscript-spoof', 8 ],
  79. [ 'no-workers', 10 ]
  80. ]);
  81. var switchStateToNameMap = new Map([
  82. [ 1, 'true' ],
  83. [ 2, 'false' ]
  84. ]);
  85. var nameToSwitchStateMap = {
  86. 'true': 1,
  87. 'false': 2
  88. };
  89. /******************************************************************************/
  90. Matrix.columnHeaderIndices = (function() {
  91. var out = new Map(),
  92. i = 0;
  93. for ( var type of typeBitOffsets.keys() ) {
  94. out.set(type, i++);
  95. }
  96. return out;
  97. })();
  98. Matrix.switchNames = new Set(switchBitOffsets.keys());
  99. /******************************************************************************/
  100. // For performance purpose, as simple tests as possible
  101. var reHostnameVeryCoarse = /[g-z_-]/;
  102. var reIPv4VeryCoarse = /\.\d+$/;
  103. // http://tools.ietf.org/html/rfc5952
  104. // 4.3: "MUST be represented in lowercase"
  105. // Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
  106. var isIPAddress = function(hostname) {
  107. if ( reHostnameVeryCoarse.test(hostname) ) {
  108. return false;
  109. }
  110. if ( reIPv4VeryCoarse.test(hostname) ) {
  111. return true;
  112. }
  113. return hostname.charAt(0) === '[';
  114. };
  115. /******************************************************************************/
  116. var toBroaderHostname = function(hostname) {
  117. if ( hostname === '*' ) { return ''; }
  118. if ( isIPAddress(hostname) ) {
  119. return toBroaderIPAddress(hostname);
  120. }
  121. var pos = hostname.indexOf('.');
  122. if ( pos === -1 ) {
  123. return '*';
  124. }
  125. return hostname.slice(pos + 1);
  126. };
  127. var toBroaderIPAddress = function(ipaddress) {
  128. // Can't broaden IPv6 (for now)
  129. if ( ipaddress.charAt(0) === '[' ) {
  130. return '*';
  131. }
  132. var pos = ipaddress.lastIndexOf('.');
  133. return pos !== -1 ? ipaddress.slice(0, pos) : '*';
  134. };
  135. Matrix.toBroaderHostname = toBroaderHostname;
  136. /******************************************************************************/
  137. // Find out src-des relationship, using coarse-to-fine grained tests for
  138. // speed. If desHostname is 1st-party to srcHostname, the domain is returned,
  139. // otherwise the empty string.
  140. var extractFirstPartyDesDomain = function(srcHostname, desHostname) {
  141. if ( srcHostname === '*' || desHostname === '*' || desHostname === '1st-party' ) {
  142. return '';
  143. }
  144. var ηmuri = UriTools;
  145. var srcDomain = ηmuri.domainFromHostname(srcHostname) || srcHostname;
  146. var desDomain = ηmuri.domainFromHostname(desHostname) || desHostname;
  147. return desDomain === srcDomain ? desDomain : '';
  148. };
  149. /******************************************************************************/
  150. Matrix.prototype.reset = function() {
  151. this.switches = new Map();
  152. this.rules = new Map();
  153. this.rootValue = Matrix.RedIndirect;
  154. this.modifiedTime = 0;
  155. };
  156. /******************************************************************************/
  157. Matrix.prototype.decomposeSource = function(srcHostname) {
  158. if ( srcHostname === this.sourceRegister ) { return; }
  159. var hn = srcHostname;
  160. this.decomposedSourceRegister[0] = this.sourceRegister = hn;
  161. var i = 1;
  162. for (;;) {
  163. hn = toBroaderHostname(hn);
  164. this.decomposedSourceRegister[i++] = hn;
  165. if ( hn === '' ) { break; }
  166. }
  167. };
  168. /******************************************************************************/
  169. // Copy another matrix to self. Do this incrementally to minimize impact on
  170. // a live matrix.
  171. Matrix.prototype.assign = function(other) {
  172. var k, entry;
  173. // Remove rules not in other
  174. for ( k of this.rules.keys() ) {
  175. if ( other.rules.has(k) === false ) {
  176. this.rules.delete(k);
  177. }
  178. }
  179. // Remove switches not in other
  180. for ( k of this.switches.keys() ) {
  181. if ( other.switches.has(k) === false ) {
  182. this.switches.delete(k);
  183. }
  184. }
  185. // Add/change rules in other
  186. for ( entry of other.rules ) {
  187. this.rules.set(entry[0], entry[1]);
  188. }
  189. // Add/change switches in other
  190. for ( entry of other.switches ) {
  191. this.switches.set(entry[0], entry[1]);
  192. }
  193. this.modifiedTime = other.modifiedTime;
  194. return this;
  195. };
  196. // https://www.youtube.com/watch?v=e9RS4biqyAc
  197. /******************************************************************************/
  198. // If value is undefined, the switch is removed
  199. Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) {
  200. var bitOffset = switchBitOffsets.get(switchName);
  201. if ( bitOffset === undefined ) {
  202. return false;
  203. }
  204. if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) {
  205. return false;
  206. }
  207. var bits = this.switches.get(srcHostname) || 0;
  208. bits &= ~(3 << bitOffset);
  209. bits |= newVal << bitOffset;
  210. if ( bits === 0 ) {
  211. this.switches.delete(srcHostname);
  212. } else {
  213. this.switches.set(srcHostname, bits);
  214. }
  215. this.modifiedTime = Date.now();
  216. return true;
  217. };
  218. /******************************************************************************/
  219. Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
  220. var bitOffset = typeBitOffsets.get(type),
  221. k = srcHostname + ' ' + desHostname,
  222. oldBitmap = this.rules.get(k);
  223. if ( oldBitmap === undefined ) {
  224. oldBitmap = 0;
  225. }
  226. var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
  227. if ( newBitmap === oldBitmap ) {
  228. return false;
  229. }
  230. if ( newBitmap === 0 ) {
  231. this.rules.delete(k);
  232. } else {
  233. this.rules.set(k, newBitmap);
  234. }
  235. this.modifiedTime = Date.now();
  236. return true;
  237. };
  238. /******************************************************************************/
  239. Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) {
  240. var r = this.evaluateCellZ(srcHostname, desHostname, type);
  241. if ( r === 1 ) {
  242. return false;
  243. }
  244. this.setCell(srcHostname, desHostname, type, 0);
  245. r = this.evaluateCellZ(srcHostname, desHostname, type);
  246. if ( r === 1 ) {
  247. return true;
  248. }
  249. this.setCell(srcHostname, desHostname, type, 1);
  250. return true;
  251. };
  252. /******************************************************************************/
  253. Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) {
  254. var r = this.evaluateCellZ(srcHostname, desHostname, type);
  255. if ( r === 2 ) {
  256. return false;
  257. }
  258. this.setCell(srcHostname, desHostname, type, 0);
  259. r = this.evaluateCellZ(srcHostname, desHostname, type);
  260. if ( r === 2 ) {
  261. return true;
  262. }
  263. this.setCell(srcHostname, desHostname, type, 2);
  264. return true;
  265. };
  266. /******************************************************************************/
  267. Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) {
  268. var r = this.evaluateCellZ(srcHostname, desHostname, type);
  269. if ( r === 0 || r === 3 ) {
  270. return false;
  271. }
  272. this.setCell(srcHostname, desHostname, type, 0);
  273. r = this.evaluateCellZ(srcHostname, desHostname, type);
  274. if ( r === 0 || r === 3 ) {
  275. return true;
  276. }
  277. this.setCell(srcHostname, desHostname, type, 3);
  278. return true;
  279. };
  280. /******************************************************************************/
  281. Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
  282. var key = srcHostname + ' ' + desHostname;
  283. var bitmap = this.rules.get(key);
  284. if ( bitmap === undefined ) {
  285. return 0;
  286. }
  287. return bitmap >> typeBitOffsets.get(type) & 3;
  288. };
  289. /******************************************************************************/
  290. Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
  291. this.decomposeSource(srcHostname);
  292. var bitOffset = typeBitOffsets.get(type),
  293. s, v, i = 0;
  294. for (;;) {
  295. s = this.decomposedSourceRegister[i++];
  296. if ( s === '' ) { break; }
  297. v = this.rules.get(s + ' ' + desHostname);
  298. if ( v !== undefined ) {
  299. v = v >> bitOffset & 3;
  300. if ( v !== 0 ) {
  301. return v;
  302. }
  303. }
  304. }
  305. // srcHostname is '*' at this point
  306. // Preset blacklisted hostnames are blacklisted in global scope
  307. if ( type === '*' && ηm.ubiquitousBlacklist.test(desHostname) ) {
  308. return 1;
  309. }
  310. // https://github.com/gorhill/uMatrix/issues/65
  311. // Hardcoded global `doc` rule
  312. if ( type === 'doc' && desHostname === '*' ) {
  313. return 2;
  314. }
  315. return 0;
  316. };
  317. /******************************************************************************/
  318. Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) {
  319. // Matrix filtering switch
  320. this.specificityRegister = 0;
  321. if ( this.evaluateSwitchZ('matrix-off', srcHostname) ) {
  322. return Matrix.GreenIndirect;
  323. }
  324. // TODO: There are cells evaluated twice when the type is '*'. Unsure
  325. // whether it's worth trying to avoid that, as this could introduce
  326. // overhead which may not be gained back by skipping the redundant tests.
  327. // And this happens *only* when building the matrix UI, not when
  328. // evaluating net requests.
  329. // Specific-hostname specific-type cell
  330. this.specificityRegister = 1;
  331. var r = this.evaluateCellZ(srcHostname, desHostname, type);
  332. if ( r === 1 ) { return Matrix.RedDirect; }
  333. if ( r === 2 ) { return Matrix.GreenDirect; }
  334. // Specific-hostname any-type cell
  335. this.specificityRegister = 2;
  336. var rl = this.evaluateCellZ(srcHostname, desHostname, '*');
  337. if ( rl === 1 ) { return Matrix.RedIndirect; }
  338. var d = desHostname;
  339. var firstPartyDesDomain = extractFirstPartyDesDomain(srcHostname, desHostname);
  340. // Ancestor cells, up to 1st-party destination domain
  341. if ( firstPartyDesDomain !== '' ) {
  342. this.specificityRegister = 3;
  343. for (;;) {
  344. if ( d === firstPartyDesDomain ) { break; }
  345. d = d.slice(d.indexOf('.') + 1);
  346. // specific-hostname specific-type cell
  347. r = this.evaluateCellZ(srcHostname, d, type);
  348. if ( r === 1 ) { return Matrix.RedIndirect; }
  349. if ( r === 2 ) { return Matrix.GreenIndirect; }
  350. // Do not override a narrower rule
  351. if ( rl !== 2 ) {
  352. rl = this.evaluateCellZ(srcHostname, d, '*');
  353. if ( rl === 1 ) { return Matrix.RedIndirect; }
  354. }
  355. }
  356. // 1st-party specific-type cell: it's a special row, looked up only
  357. // when destination is 1st-party to source.
  358. r = this.evaluateCellZ(srcHostname, '1st-party', type);
  359. if ( r === 1 ) { return Matrix.RedIndirect; }
  360. if ( r === 2 ) { return Matrix.GreenIndirect; }
  361. // Do not override narrower rule
  362. if ( rl !== 2 ) {
  363. rl = this.evaluateCellZ(srcHostname, '1st-party', '*');
  364. if ( rl === 1 ) { return Matrix.RedIndirect; }
  365. }
  366. }
  367. // Keep going, up to root
  368. this.specificityRegister = 4;
  369. for (;;) {
  370. d = toBroaderHostname(d);
  371. if ( d === '*' ) { break; }
  372. // specific-hostname specific-type cell
  373. r = this.evaluateCellZ(srcHostname, d, type);
  374. if ( r === 1 ) { return Matrix.RedIndirect; }
  375. if ( r === 2 ) { return Matrix.GreenIndirect; }
  376. // Do not override narrower rule
  377. if ( rl !== 2 ) {
  378. rl = this.evaluateCellZ(srcHostname, d, '*');
  379. if ( rl === 1 ) { return Matrix.RedIndirect; }
  380. }
  381. }
  382. // Any-hostname specific-type cells
  383. this.specificityRegister = 5;
  384. r = this.evaluateCellZ(srcHostname, '*', type);
  385. // Line below is strict-blocking
  386. if ( r === 1 ) { return Matrix.RedIndirect; }
  387. // Narrower rule wins
  388. if ( rl === 2 ) { return Matrix.GreenIndirect; }
  389. if ( r === 2 ) { return Matrix.GreenIndirect; }
  390. // Any-hostname any-type cell
  391. this.specificityRegister = 6;
  392. r = this.evaluateCellZ(srcHostname, '*', '*');
  393. if ( r === 1 ) { return Matrix.RedIndirect; }
  394. if ( r === 2 ) { return Matrix.GreenIndirect; }
  395. return this.rootValue;
  396. };
  397. // https://www.youtube.com/watch?v=4C5ZkwrnVfM
  398. /******************************************************************************/
  399. Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) {
  400. var out = [];
  401. for ( var type of typeBitOffsets.keys() ) {
  402. out.push(this.evaluateCellZXY(srcHostname, desHostname, type));
  403. }
  404. return out;
  405. };
  406. /******************************************************************************/
  407. Matrix.prototype.mustBlock = function(srcHostname, desHostname, type) {
  408. return (this.evaluateCellZXY(srcHostname, desHostname, type) & 3) === Matrix.Red;
  409. };
  410. /******************************************************************************/
  411. Matrix.prototype.srcHostnameFromRule = function(rule) {
  412. return rule.slice(0, rule.indexOf(' '));
  413. };
  414. /******************************************************************************/
  415. Matrix.prototype.desHostnameFromRule = function(rule) {
  416. return rule.slice(rule.indexOf(' ') + 1);
  417. };
  418. /******************************************************************************/
  419. Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) {
  420. var bitOffset = switchBitOffsets.get(switchName);
  421. if ( bitOffset === undefined ) {
  422. return false;
  423. }
  424. var state = this.evaluateSwitchZ(switchName, srcHostname);
  425. if ( newState === state ) {
  426. return false;
  427. }
  428. if ( newState === undefined ) {
  429. newState = !state;
  430. }
  431. var bits = this.switches.get(srcHostname) || 0;
  432. bits &= ~(3 << bitOffset);
  433. if ( bits === 0 ) {
  434. this.switches.delete(srcHostname);
  435. } else {
  436. this.switches.set(srcHostname, bits);
  437. }
  438. this.modifiedTime = Date.now();
  439. state = this.evaluateSwitchZ(switchName, srcHostname);
  440. if ( state === newState ) {
  441. return true;
  442. }
  443. this.switches.set(srcHostname, bits | ((newState ? 1 : 2) << bitOffset));
  444. return true;
  445. };
  446. /******************************************************************************/
  447. // 0 = inherit from broader scope, up to default state
  448. // 1 = non-default state
  449. // 2 = forced default state (to override a broader non-default state)
  450. Matrix.prototype.evaluateSwitch = function(switchName, srcHostname) {
  451. var bits = this.switches.get(srcHostname) || 0;
  452. if ( bits === 0 ) {
  453. return 0;
  454. }
  455. var bitOffset = switchBitOffsets.get(switchName);
  456. if ( bitOffset === undefined ) {
  457. return 0;
  458. }
  459. return (bits >> bitOffset) & 3;
  460. };
  461. /******************************************************************************/
  462. Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) {
  463. var bitOffset = switchBitOffsets.get(switchName);
  464. if ( bitOffset === undefined ) { return false; }
  465. this.decomposeSource(srcHostname);
  466. var s, bits, i = 0;
  467. for (;;) {
  468. s = this.decomposedSourceRegister[i++];
  469. if ( s === '' ) { break; }
  470. bits = this.switches.get(s) || 0;
  471. if ( bits !== 0 ) {
  472. bits = bits >> bitOffset & 3;
  473. if ( bits !== 0 ) {
  474. return bits === 1;
  475. }
  476. }
  477. }
  478. return false;
  479. };
  480. /******************************************************************************/
  481. Matrix.prototype.extractAllSourceHostnames = (function() {
  482. var cachedResult = new Set();
  483. var matrixId = 0;
  484. var readTime = 0;
  485. return function() {
  486. if ( matrixId !== this.id || readTime !== this.modifiedTime ) {
  487. cachedResult.clear();
  488. for ( var rule of this.rules.keys() ) {
  489. cachedResult.add(rule.slice(0, rule.indexOf(' ')));
  490. }
  491. matrixId = this.id;
  492. readTime = this.modifiedTime;
  493. }
  494. return cachedResult;
  495. };
  496. })();
  497. /******************************************************************************/
  498. Matrix.prototype.toString = function() {
  499. var out = [];
  500. var rule, type, switchName, val;
  501. var srcHostname, desHostname;
  502. for ( rule of this.rules.keys() ) {
  503. srcHostname = this.srcHostnameFromRule(rule);
  504. desHostname = this.desHostnameFromRule(rule);
  505. for ( type of typeBitOffsets.keys() ) {
  506. val = this.evaluateCell(srcHostname, desHostname, type);
  507. if ( val === 0 ) { continue; }
  508. out.push(
  509. Punycode.toUnicode(srcHostname) + ' ' +
  510. Punycode.toUnicode(desHostname) + ' ' +
  511. type + ' ' +
  512. stateToNameMap.get(val)
  513. );
  514. }
  515. }
  516. for ( srcHostname of this.switches.keys() ) {
  517. for ( switchName of switchBitOffsets.keys() ) {
  518. val = this.evaluateSwitch(switchName, srcHostname);
  519. if ( val === 0 ) { continue; }
  520. out.push(switchName + ': ' + srcHostname + ' ' + switchStateToNameMap.get(val));
  521. }
  522. }
  523. return out.sort().join('\n');
  524. };
  525. /******************************************************************************/
  526. Matrix.prototype.fromString = function(text, append) {
  527. var matrix = append ? this : new Matrix();
  528. var textEnd = text.length;
  529. var lineBeg = 0, lineEnd;
  530. var line, pos;
  531. var fields, fieldVal;
  532. var switchName;
  533. var srcHostname = '';
  534. var desHostname = '';
  535. var type, state;
  536. while ( lineBeg < textEnd ) {
  537. lineEnd = text.indexOf('\n', lineBeg);
  538. if ( lineEnd < 0 ) {
  539. lineEnd = text.indexOf('\r', lineBeg);
  540. if ( lineEnd < 0 ) {
  541. lineEnd = textEnd;
  542. }
  543. }
  544. line = text.slice(lineBeg, lineEnd).trim();
  545. lineBeg = lineEnd + 1;
  546. pos = line.indexOf('# ');
  547. if ( pos !== -1 ) {
  548. line = line.slice(0, pos).trim();
  549. }
  550. if ( line === '' ) {
  551. continue;
  552. }
  553. fields = line.split(/\s+/);
  554. // Less than 2 fields makes no sense
  555. if ( fields.length < 2 ) {
  556. continue;
  557. }
  558. fieldVal = fields[0];
  559. // Special directives:
  560. // title
  561. pos = fieldVal.indexOf('title:');
  562. if ( pos !== -1 ) {
  563. // TODO
  564. continue;
  565. }
  566. // Name
  567. pos = fieldVal.indexOf('name:');
  568. if ( pos !== -1 ) {
  569. // TODO
  570. continue;
  571. }
  572. // Switch on/off
  573. // `switch:` srcHostname state
  574. // state = [`true`, `false`]
  575. switchName = '';
  576. if ( fieldVal === 'switch:' || fieldVal === 'matrix:' ) {
  577. fieldVal = 'matrix-off:';
  578. }
  579. pos = fieldVal.indexOf(':');
  580. if ( pos !== -1 ) {
  581. switchName = fieldVal.slice(0, pos);
  582. }
  583. if ( switchBitOffsets.has(switchName) ) {
  584. srcHostname = Punycode.toASCII(fields[1]);
  585. // No state field: reject
  586. fieldVal = fields[2];
  587. if ( fieldVal === null ) {
  588. continue;
  589. }
  590. // Unknown state: reject
  591. if ( nameToSwitchStateMap.hasOwnProperty(fieldVal) === false ) {
  592. continue;
  593. }
  594. // Backward compatibility:
  595. // `chromium-behind-the-scene` is now `behind-the-scene`
  596. if ( srcHostname === 'chromium-behind-the-scene' ) {
  597. srcHostname = 'behind-the-scene';
  598. }
  599. matrix.setSwitch(switchName, srcHostname, nameToSwitchStateMap[fieldVal]);
  600. continue;
  601. }
  602. // Unknown directive
  603. if ( fieldVal.endsWith(':') ) {
  604. continue;
  605. }
  606. // Valid rule syntax:
  607. // srcHostname desHostname [type [state]]
  608. // type = a valid request type
  609. // state = [`block`, `allow`, `inherit`]
  610. // srcHostname desHostname type
  611. // type = a valid request type
  612. // state = `allow`
  613. // srcHostname desHostname
  614. // type = `*`
  615. // state = `allow`
  616. // Lines with invalid syntax silently ignored
  617. srcHostname = Punycode.toASCII(fields[0]);
  618. desHostname = Punycode.toASCII(fields[1]);
  619. fieldVal = fields[2];
  620. if ( fieldVal !== undefined ) {
  621. type = fieldVal;
  622. // https://github.com/gorhill/uMatrix/issues/759
  623. // Backward compatibility.
  624. if ( type === 'plugin' ) {
  625. type = 'media';
  626. }
  627. // Unknown type: reject
  628. if ( typeBitOffsets.has(type) === false ) {
  629. continue;
  630. }
  631. } else {
  632. type = '*';
  633. }
  634. fieldVal = fields[3];
  635. if ( fieldVal !== undefined ) {
  636. // Unknown state: reject
  637. if ( nameToStateMap.hasOwnProperty(fieldVal) === false ) {
  638. continue;
  639. }
  640. state = nameToStateMap[fieldVal];
  641. } else {
  642. state = 2;
  643. }
  644. matrix.setCell(srcHostname, desHostname, type, state);
  645. }
  646. if ( !append ) {
  647. this.assign(matrix);
  648. }
  649. this.modifiedTime = Date.now();
  650. };
  651. /******************************************************************************/
  652. Matrix.prototype.toSelfie = function() {
  653. return {
  654. magicId: magicId,
  655. switches: Array.from(this.switches),
  656. rules: Array.from(this.rules)
  657. };
  658. };
  659. /******************************************************************************/
  660. Matrix.prototype.fromSelfie = function(selfie) {
  661. if ( selfie.magicId !== magicId ) { return false; }
  662. this.switches = new Map(selfie.switches);
  663. this.rules = new Map(selfie.rules);
  664. this.modifiedTime = Date.now();
  665. return true;
  666. };
  667. /******************************************************************************/
  668. Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
  669. var out = [];
  670. var desHostname, type;
  671. var switchName, i, thisVal, otherVal;
  672. for (;;) {
  673. for ( switchName of switchBitOffsets.keys() ) {
  674. thisVal = this.evaluateSwitch(switchName, srcHostname);
  675. otherVal = other.evaluateSwitch(switchName, srcHostname);
  676. if ( thisVal !== otherVal ) {
  677. out.push({
  678. 'what': switchName,
  679. 'src': srcHostname
  680. });
  681. }
  682. }
  683. i = desHostnames.length;
  684. while ( i-- ) {
  685. desHostname = desHostnames[i];
  686. for ( type of typeBitOffsets.keys() ) {
  687. thisVal = this.evaluateCell(srcHostname, desHostname, type);
  688. otherVal = other.evaluateCell(srcHostname, desHostname, type);
  689. if ( thisVal === otherVal ) { continue; }
  690. out.push({
  691. 'what': 'rule',
  692. 'src': srcHostname,
  693. 'des': desHostname,
  694. 'type': type
  695. });
  696. }
  697. }
  698. srcHostname = toBroaderHostname(srcHostname);
  699. if ( srcHostname === '' ) {
  700. break;
  701. }
  702. }
  703. return out;
  704. };
  705. /******************************************************************************/
  706. Matrix.prototype.applyDiff = function(diff, from) {
  707. var changed = false;
  708. var i = diff.length;
  709. var action, val;
  710. while ( i-- ) {
  711. action = diff[i];
  712. if ( action.what === 'rule' ) {
  713. val = from.evaluateCell(action.src, action.des, action.type);
  714. changed = this.setCell(action.src, action.des, action.type, val) || changed;
  715. continue;
  716. }
  717. if ( switchBitOffsets.has(action.what) ) {
  718. val = from.evaluateSwitch(action.what, action.src);
  719. changed = this.setSwitch(action.what, action.src, val) || changed;
  720. continue;
  721. }
  722. }
  723. return changed;
  724. };
  725. /******************************************************************************/
  726. return Matrix;
  727. /******************************************************************************/
  728. // https://www.youtube.com/watch?v=wlNrQGmj6oQ
  729. })();
  730. /******************************************************************************/