traffic.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 Raymond Hill
  4. Copyright (C) 2019-2022 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. 'use strict';
  19. /******************************************************************************/
  20. ηMatrix.webRequest = (function() {
  21. Cu.import('chrome://ematrix/content/lib/UriTools.jsm');
  22. // Intercept and filter web requests according to white and black lists.
  23. var onBeforeRootFrameRequestHandler = function (details) {
  24. let ηm = ηMatrix;
  25. let requestURL = details.url;
  26. let requestHostname = UriTools.hostnameFromURI(requestURL);
  27. let tabId = details.tabId;
  28. ηm.tabContextManager.push(tabId, requestURL);
  29. let tabContext = ηm.tabContextManager.mustLookup(tabId);
  30. let rootHostname = tabContext.rootHostname;
  31. // Disallow request as per matrix?
  32. let block = ηm.mustBlock(rootHostname, requestHostname, 'doc');
  33. let pageStore = ηm.pageStoreFromTabId(tabId);
  34. pageStore.recordRequest('doc', requestURL, block);
  35. ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block);
  36. // Not blocked
  37. if (!block) {
  38. // rhill 2013-11-07: Senseless to do this for
  39. // behind-the-scene requests.
  40. ηm.cookieHunter.recordPageCookies(pageStore);
  41. return;
  42. }
  43. // Blocked
  44. var query = btoa(JSON.stringify({
  45. url: requestURL,
  46. hn: requestHostname,
  47. why: '?'
  48. }));
  49. vAPI.tabs.replace(tabId,
  50. vAPI.getURL('main-blocked.html?details=') + query);
  51. return {
  52. cancel: true
  53. };
  54. };
  55. // Intercept and filter web requests according to white and black lists.
  56. var onBeforeRequestHandler = function (details) {
  57. let ηm = ηMatrix;
  58. let requestURL = details.url;
  59. let requestScheme = UriTools.schemeFromURI(requestURL);
  60. if (UriTools.isNetworkScheme(requestScheme) === false) {
  61. return;
  62. }
  63. var requestType = requestTypeNormalizer[details.type] || 'other';
  64. // https://github.com/gorhill/httpswitchboard/issues/303
  65. // Wherever the main doc comes from, create a receiver page
  66. // URL: synthetize one if needed.
  67. if (requestType === 'doc' && details.parentFrameId === -1) {
  68. return onBeforeRootFrameRequestHandler(details);
  69. }
  70. // Re-classify orphan HTTP requests as behind-the-scene
  71. // requests. There is not much else which can be done, because
  72. // there are URLs which cannot be handled by ηMatrix,
  73. // i.e. `opera://startpage`, as this would lead to
  74. // complications with no obvious solution, like how to scope
  75. // on unknown scheme? Etc.
  76. // https://github.com/gorhill/httpswitchboard/issues/191
  77. // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
  78. let tabContext = ηm.tabContextManager.mustLookup(details.tabId);
  79. let tabId = tabContext.tabId;
  80. let rootHostname = tabContext.rootHostname;
  81. let specificity = 0;
  82. // Filter through matrix
  83. let block = ηm.tMatrix.mustBlock(rootHostname,
  84. UriTools.hostnameFromURI(requestURL),
  85. requestType);
  86. if (block) {
  87. specificity = ηm.tMatrix.specificityRegister;
  88. }
  89. // Record request.
  90. // https://github.com/gorhill/httpswitchboard/issues/342
  91. // The way requests are handled now, it may happen at this
  92. // point some processing has already been performed, and that
  93. // a synthetic URL has been constructed for logging
  94. // purpose. Use this synthetic URL if it is available.
  95. let pageStore = ηm.mustPageStoreFromTabId(tabId);
  96. // Enforce strict secure connection?
  97. if (tabContext.secure
  98. && UriTools.isSecureScheme(requestScheme) === false) {
  99. pageStore.hasMixedContent = true;
  100. if (block === false) {
  101. block = ηm.tMatrix.evaluateSwitchZ('https-strict', rootHostname);
  102. }
  103. }
  104. pageStore.recordRequest(requestType, requestURL, block);
  105. ηm.logger.writeOne(tabId, 'net', rootHostname,
  106. requestURL, details.type, block);
  107. if (block) {
  108. pageStore.cacheBlockedCollapsible(requestType,
  109. requestURL, specificity);
  110. return {
  111. 'cancel': true
  112. };
  113. }
  114. };
  115. // Sanitize outgoing headers as per user settings.
  116. var onBeforeSendHeadersHandler = function (details) {
  117. let ηm = ηMatrix;
  118. let requestURL = details.url;
  119. let requestScheme = UriTools.schemeFromURI(requestURL);
  120. // Ignore non-network schemes
  121. if (UriTools.isNetworkScheme(requestScheme) === false) {
  122. return;
  123. }
  124. // Re-classify orphan HTTP requests as behind-the-scene
  125. // requests. There is not much else which can be done, because
  126. // there are URLs which cannot be handled by HTTP Switchboard,
  127. // i.e. `opera://startpage`, as this would lead to
  128. // complications with no obvious solution, like how to scope
  129. // on unknown scheme? Etc.
  130. // https://github.com/gorhill/httpswitchboard/issues/191
  131. // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
  132. let tabId = details.tabId;
  133. let pageStore = ηm.mustPageStoreFromTabId(tabId);
  134. let requestType = requestTypeNormalizer[details.type] || 'other';
  135. let requestHeaders = details.requestHeaders;
  136. let headerIndex, headerValue;
  137. // https://github.com/gorhill/httpswitchboard/issues/342
  138. // Is this hyperlink auditing? If yes, create a synthetic URL
  139. // for reporting hyperlink auditing in request log. This way
  140. // the user is better informed of what went on.
  141. // https://html.spec.whatwg.org/multipage/links.html#hyperlink-auditing
  142. //
  143. // Target URL = the href of the link
  144. // Doc URL = URL of the document containing the target URL
  145. // Ping URLs = servers which will be told that user clicked target URL
  146. //
  147. // `Content-Type` = `text/ping` (always present)
  148. // `Ping-To` = target URL (always present)
  149. // `Ping-From` = doc URL
  150. // `Referer` = doc URL
  151. // request URL = URL which will receive the information
  152. //
  153. // With hyperlink-auditing, removing header(s) is pointless, the whole
  154. // request must be cancelled.
  155. headerIndex = headerIndexFromName('ping-to', requestHeaders);
  156. if (headerIndex !== -1) {
  157. headerValue = requestHeaders[headerIndex].value;
  158. if (headerValue !== '') {
  159. let block = ηm.userSettings.processHyperlinkAuditing;
  160. pageStore.recordRequest('other',
  161. requestURL
  162. + '{Ping-To:'
  163. + headerValue
  164. + '}', block);
  165. ηm.logger.writeOne(tabId, 'net', '', requestURL, 'ping', block);
  166. if (block) {
  167. ηm.hyperlinkAuditingFoiledCounter += 1;
  168. return {
  169. 'cancel': true
  170. };
  171. }
  172. }
  173. }
  174. // If we reach this point, request is not blocked, so what is
  175. // left to do is to sanitize headers.
  176. let rootHostname = pageStore.pageHostname;
  177. let requestHostname = UriTools.hostnameFromURI(requestURL);
  178. let modified = false;
  179. // Process `Cookie` header.
  180. headerIndex = headerIndexFromName('cookie', requestHeaders);
  181. if (headerIndex !== -1
  182. && ηm.mustBlock(rootHostname, requestHostname, 'cookie')) {
  183. modified = true;
  184. headerValue = requestHeaders[headerIndex].value;
  185. requestHeaders.splice(headerIndex, 1);
  186. ηm.cookieHeaderFoiledCounter++;
  187. if (requestType === 'doc') {
  188. pageStore.perLoadBlockedRequestCount++;
  189. ηm.logger.writeOne(tabId, 'net', '', headerValue, 'COOKIE', true);
  190. }
  191. }
  192. // Process `Referer` header.
  193. // https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402
  194. // https://github.com/gorhill/uMatrix/issues/320
  195. // http://tools.ietf.org/html/rfc6454#section-7.3
  196. // "The user agent MAY include an Origin header field in any HTTP
  197. // "request.
  198. // "The user agent MUST NOT include more than one Origin header field in
  199. // "any HTTP request.
  200. // "Whenever a user agent issues an HTTP request from a "privacy-
  201. // "sensitive" context, the user agent MUST send the value "null" in the
  202. // "Origin header field."
  203. // https://github.com/gorhill/uMatrix/issues/358
  204. // Do not spoof `Origin` header for the time being.
  205. // https://github.com/gorhill/uMatrix/issues/773
  206. // For non-GET requests, remove `Referer` header instead of spoofing it.
  207. headerIndex = headerIndexFromName('referer', requestHeaders);
  208. if (headerIndex !== -1) {
  209. headerValue = requestHeaders[headerIndex].value;
  210. if (headerValue !== '') {
  211. let toDomain = UriTools.domainFromHostname(requestHostname);
  212. if (toDomain !== ''
  213. && toDomain !== UriTools.domainFromURI(headerValue)) {
  214. pageStore.has3pReferrer = true;
  215. if (ηm.tMatrix.evaluateSwitchZ('referrer-spoof',
  216. rootHostname)) {
  217. modified = true;
  218. let newValue;
  219. if (details.method === 'GET') {
  220. newValue = requestHeaders[headerIndex].value =
  221. requestScheme + '://' + requestHostname + '/';
  222. } else {
  223. requestHeaders.splice(headerIndex, 1);
  224. }
  225. ηm.refererHeaderFoiledCounter++;
  226. if (requestType === 'doc') {
  227. pageStore.perLoadBlockedRequestCount++;
  228. ηm.logger.writeOne(tabId, 'net', '',
  229. headerValue, 'REFERER', true);
  230. if (newValue !== undefined) {
  231. ηm.logger.writeOne(tabId, 'net', '',
  232. newValue, 'REFERER', false);
  233. }
  234. }
  235. }
  236. }
  237. }
  238. }
  239. if (modified) {
  240. return {
  241. requestHeaders: requestHeaders
  242. };
  243. }
  244. };
  245. // To prevent inline javascript from being executed.
  246. // Prevent inline scripting using `Content-Security-Policy`:
  247. // https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html
  248. // This fixes:
  249. // https://github.com/gorhill/httpswitchboard/issues/35
  250. var onHeadersReceived = function (details) {
  251. // Ignore schemes other than 'http...'
  252. let ηm = ηMatrix;
  253. let tabId = details.tabId;
  254. let requestURL = details.url;
  255. let requestType = requestTypeNormalizer[details.type] || 'other';
  256. // https://github.com/gorhill/uMatrix/issues/145
  257. // Check if the main_frame is a download
  258. if (requestType === 'doc') {
  259. ηm.tabContextManager.push(tabId, requestURL);
  260. }
  261. let tabContext = ηm.tabContextManager.lookup(tabId);
  262. if (tabContext === null) {
  263. return;
  264. }
  265. let csp = [];
  266. let cspReport = [];
  267. let rootHostname = tabContext.rootHostname;
  268. let requestHostname = UriTools.hostnameFromURI(requestURL);
  269. // Inline script tags.
  270. if (ηm.mustAllow(rootHostname, requestHostname, 'script') !== true) {
  271. csp.push(ηm.cspNoInlineScript);
  272. }
  273. // Inline style tags.
  274. if (ηm.mustAllow(rootHostname, requestHostname, 'css') !== true) {
  275. csp.push(ηm.cspNoInlineStyle);
  276. }
  277. // https://bugzilla.mozilla.org/show_bug.cgi?id=1302667
  278. let cspNoWorker = ηm.cspNoWorker;
  279. if (cspNoWorker === undefined) {
  280. cspNoWorker = cspNoWorkerInit();
  281. }
  282. if (ηm.tMatrix.evaluateSwitchZ('no-workers', rootHostname)) {
  283. csp.push(cspNoWorker);
  284. } else if (ηm.rawSettings.disableCSPReportInjection === false) {
  285. cspReport.push(cspNoWorker);
  286. }
  287. let headers = details.responseHeaders;
  288. let cspDirectives, i;
  289. if (csp.length !== 0) {
  290. cspDirectives = csp.join(',');
  291. i = headerIndexFromName('content-security-policy', headers);
  292. if (i !== -1) {
  293. headers[i].value += ',' + cspDirectives;
  294. } else {
  295. headers.push({
  296. name: 'Content-Security-Policy',
  297. value: cspDirectives
  298. });
  299. }
  300. if (requestType === 'doc') {
  301. ηm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false);
  302. }
  303. }
  304. if (cspReport.length !== 0) {
  305. cspDirectives = cspReport.join(',');
  306. i = headerIndexFromName('content-security-policy-report-only',
  307. headers);
  308. if (i !== -1) {
  309. headers[i].value += ',' + cspDirectives;
  310. } else {
  311. headers.push({
  312. name: 'Content-Security-Policy-Report-Only',
  313. value: cspDirectives
  314. });
  315. }
  316. }
  317. return {
  318. responseHeaders: headers
  319. };
  320. };
  321. let cspNoWorkerInit = function () {
  322. if (ηMatrix.cspNoWorker === undefined) {
  323. ηMatrix.cspNoWorker = "worker-src 'none'; "
  324. +"frame-src data: blob: *; "
  325. +"report-uri about:blank";
  326. }
  327. return ηMatrix.cspNoWorker;
  328. };
  329. // Caller must ensure headerName is normalized to lower case.
  330. let headerIndexFromName = function (headerName, headers) {
  331. for (let i=0; i<headers.length; ++i) {
  332. if ( headers[i].name.toLowerCase() === headerName ) {
  333. return i;
  334. }
  335. }
  336. return -1;
  337. };
  338. let requestTypeNormalizer = {
  339. 'font' : 'css',
  340. 'image' : 'image',
  341. 'imageset' : 'image',
  342. 'main_frame' : 'doc',
  343. 'media' : 'media',
  344. 'object' : 'media',
  345. 'other' : 'other',
  346. 'script' : 'script',
  347. 'stylesheet' : 'css',
  348. 'sub_frame' : 'frame',
  349. 'websocket' : 'xhr',
  350. 'xmlhttprequest': 'xhr'
  351. };
  352. vAPI.net.onBeforeRequest = {
  353. extra: [ 'blocking' ],
  354. callback: onBeforeRequestHandler
  355. };
  356. vAPI.net.onBeforeSendHeaders = {
  357. extra: [ 'blocking', 'requestHeaders' ],
  358. callback: onBeforeSendHeadersHandler
  359. };
  360. vAPI.net.onHeadersReceived = {
  361. urls: [ 'http://*/*', 'https://*/*' ],
  362. types: [ 'main_frame', 'sub_frame' ],
  363. extra: [ 'blocking', 'responseHeaders' ],
  364. callback: onHeadersReceived
  365. };
  366. return {
  367. start: function () {
  368. vAPI.net.registerListeners();
  369. },
  370. };
  371. })();