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.

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