You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

886 lines
27 KiB

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