matrix.js 26 KB

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