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.

813 lines
24 KiB

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