background.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Default max-age 6 months in seconds
  2. var max_age = "15570000";
  3. //TODO: change log types to can do OR and show types of logs eg. show HSTS forced enabled, or forced disabled, or redirs ignored, redirs filtered, and/or. rather than info, debug, verbose, etc.; actually make the flagvar be what NOT to log, so a value of 0 would mean log everything, and any set bits mean don't log that type of logs.
  4. //nope: get rid of the 'new' - nevermind: https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  5. var logflags={};
  6. var blockhttp=true;//true if to block HTTP request for ignored hosts, false allow; otherwise, if host is not in ignore list it will be forced HTTPS! or it will fail to load(ie. if site is http only). Or in case of redirect loops(http->https->http) then the last http is allowed or not based on this value.(and that host is virtually ignored for the duration of the request and thus allowed to http complete) - TODO: also, if this is false, always log those allowed requests to Console!
  7. //Note: access the following console.log() messages by going chrome://extensions/ then clicking the 'background page' seen as 'Inspect views: background page' under this extension (HSTS Everywhere) then chosing the 'Console' tab.
  8. //src: https://stackoverflow.com/questions/1125256/how-to-create-a-constant-in-javascript
  9. function setConstant(obj, key, value) {//eg. setConstant(logflags, "NONE", 0);
  10. Object.defineProperty(obj, key, {
  11. enumerable: true,
  12. configurable: false,
  13. writable: false,
  14. value: value,
  15. });
  16. }
  17. //order doesn't matter, these are bit flips! when set, it will NOT show(on Console) that type of logs! (set them in 'nologbits')
  18. //XXX: to toggle INFO flag, type in Console: nologbits ^= logflags.INFO (this will enable/disable showing of INFO logs)
  19. setConstant(logflags, "NONE", 0xFFFFFFFF);
  20. setConstant(logflags, "ALL", 0x0);
  21. setConstant(logflags, "ERR", 0x1);
  22. setConstant(logflags, "ERROR", 0x1);
  23. setConstant(logflags, "WARN", 0x2);
  24. setConstant(logflags, "INFO", 0x4);
  25. setConstant(logflags, "VERB", 0x8);
  26. setConstant(logflags, "VERBOSE", 0x8);
  27. setConstant(logflags, "DEBUG", 0x10);
  28. setConstant(logflags, "HTTPALLOWED", 0x20);//XXX: this one's kinda redundant since blockhttp==false causes it to be forcefully enabled(without actually being set)
  29. setConstant(logflags, "HTTPBLOCKED", 0x40);
  30. //set this in Console! eg. nologbits = logelevels.DEBUG
  31. var nologbits = logflags.NONE;//don't log anything by default, I read somewhere in passing that it's slow and saved to disk by chromium...
  32. nologbits ^= logflags.HTTPBLOCKED;//actually do log when http gets blocked! (auto logged when http is allowed already!)
  33. function log(logtype, msg, forceshow=false) {//should never pass NONE level when calling!
  34. lognone=nologbits & logtype;
  35. //console.log(lognone+" "+logtype+" "+nologbits);
  36. if ( (lognone == 0) || (lognone != logtype) || (forceshow) ) {
  37. if (logtype & logflags.WARN != 0) {
  38. console.warn(msg);
  39. //TODO: add console.err or is it .error ? check and see!
  40. }else{
  41. console.log(msg);
  42. }
  43. }
  44. }
  45. function loghttpallowed(msg) {
  46. loghttp( logflags.HTTPALLOWED, msg );
  47. }
  48. function loghttpblocked(msg) {
  49. loghttp( logflags.HTTPBLOCKED, msg );
  50. }
  51. function loghttp(logtype,msg) {
  52. log(logtype | logflags.INFO, msg, false==blockhttp);
  53. }
  54. function logverb(msg) {
  55. log(logflags.VERBOSE, msg);
  56. }
  57. function logwarn(msg) {
  58. log(logflags.WARN, msg);
  59. }
  60. function loginfo(msg) {
  61. log(logflags.INFO, msg);
  62. }
  63. function logdebug(msg) {
  64. log(logflags.DEBUG, msg);
  65. }
  66. //NB: I left these two in 'ignore' as samples, but since I'm planning on permanently using HTTPS-Everywhere with 'Block HTTP requests', I won't be doing any HTTP requests ever, and thus I can afford to also set 'includeSubDomains' below.
  67. //This is a list of hosts for which to ignore HSTS (but if they specify HSTS in headers it will pass through to the browser! TODO: check if this is so!):
  68. var ignore = [//TODO: ignore in http->https redir too!
  69. "pastebin.com",
  70. "arstechnica.com",
  71. "imdb.com",
  72. "www.imdb.com",
  73. "static.sfdict.com",
  74. "dictionary.com",
  75. "www.dictionary.com",
  76. "www.merriam-webster.com",
  77. "www.w3schools.com",
  78. "batteryuniversity.com",
  79. "www.thinkwiki.org",
  80. "www.rustacean.net",
  81. "utf8everywhere.org",
  82. "conf.nixos.org",
  83. "lists.science.uu.nl",
  84. "news.gmane.org",
  85. "www.synonym.com",
  86. "www.theguardian.com",
  87. "ow.ly",
  88. //TODO: allow http to ignored hosts! even when blockhttp is on! perhaps add another var, blockhttptoignored=false by default!
  89. ];
  90. Array.prototype.clone = function() {
  91. return this.slice(0);
  92. }; //src: https://davidwalsh.name/javascript-clone-array
  93. //The following is list of hosts for which to force disable HSTS(incl. subdomains), this takes precedence over the ignore list above; just beware that if you're visiting a subdomain of a host listed here, it will enable HSTS for it and its subdomains; ie. 'test.com' will be force-disabled, but since it redirs to www.test.com this one will be force-enabled(and any subdomains like *.www.test.com, if any, due to 'includeSubDomains' below)
  94. var forceDisable = ignore.clone();
  95. forceDisable = forceDisable.concat([ //src: http://www.w3schools.com/jsref/jsref_concat_array.asp
  96. 'test.com',
  97. 'www.w3schools.com',
  98. ]);
  99. //TODO: should force http those that are in forceDisable and ignore lists AND if blockhttp == false! eg. going to https://www.w3schools.com/js/js_numbers.asp should force http by redirecting to http://www.w3schools.com/js/js_numbers.asp
  100. //hashmap? src: https://stackoverflow.com/questions/8877666/how-is-a-javascript-hash-map-implemented
  101. var redirloopdetect={}; //"hashmap"!
  102. var cwr = chrome.webRequest;
  103. //so, this part will force all http attempts to go through https ! and if they can't because no https server is listening well, too bad... you'll get a ERR_CONNECTION_REFUSED as usual.
  104. //why did I need this? well, the links from Variable bindings from here, for example: https://github.com/carols10cents/rustlings#variable-bindings were all http and https-everywhere wasn't doing anything about them (whilst 'Block All HTPP requests' being set was stropping anything from loading - so, don't need https-everywhere anymore now, i suppose)
  105. cwr.onBeforeRequest.addListener(
  106. function(details) {
  107. // console.log(details);
  108. ishttp=false;
  109. redirecturl = details.url;
  110. if (redirecturl.substring(0,5) === "http:") {
  111. ishttp=true;
  112. hostn = new URL(redirecturl).hostname;
  113. fullh = hostn+" (full: "+details.url+" )";
  114. if ( ignore.indexOf(hostn) > -1 ) {
  115. loghttp( (blockhttp?logflags.HTTPBLOCKED:logflags.HTTPALLOWED),
  116. "Host in ignore list: '"+fullh+"' therefore redir to https not forced! And this request is "+(blockhttp?"NOT ":"")+"allowed! Type 'blockhttp=true/false' in Console to change!");
  117. return { cancel: blockhttp };
  118. }
  119. if (details.requestId in redirloopdetect) {
  120. if (redirecturl.substring(0,5) !== "http:") {
  121. logwarn("Developer fail! this should always be 'http', it's: "+redirecturl);
  122. alert("Developer fail! this should always be 'http', it's: "+redirecturl);
  123. }
  124. //done: maybe just redir back to http instead? and based on blockhttp allow or not
  125. /*if (!blockhttp) {
  126. //manually add to ignore list or else it will redirect indefinitely, since we'll try to https it again!
  127. }*/
  128. if (blockhttp) {
  129. loghttpblocked("Cancelled http (id="+details.requestId+") redir-loop to '"+ redirecturl+"' (you might want to allow http or else this will never succeed!)");
  130. // logdebug("Blocked http to '"+ redirecturl);
  131. delete redirloopdetect[details.requestId];
  132. }else{
  133. loghttpallowed("Allowed http (id="+details.requestId+") to '"+ redirecturl);
  134. }
  135. return { cancel: blockhttp };//, redirectUrl: redirecturl };//HSTS being enabled and us not canceling this even tho it's a http request, will cause a https request to happen next!
  136. }
  137. details.url = redirecturl = redirecturl.slice(0,4) + "s" + redirecturl.slice(4);
  138. logverb("ForcedHTTPS as: '"+redirecturl+"'");
  139. } else {
  140. logwarn("!!!Sneaked url: "+redirecturl);//XXX: if this happens, it's some serious issue afoot!
  141. alert("!!! IMPOSSIBIRU! Congratulations, you've just discovered the impossible! This url '"+redirecturl+"' was not a http url but was caught by the http filter. The request is about to be stopped after you exit this, but make a screenshot first and show it around, since this probably means there's a bug in chromium!");
  142. return { cancel: true }
  143. }
  144. return { redirectUrl: redirecturl } //XXX: but HTTPS-Everywhere with 'Block HTTP request' will still cancel it! because details.url it receives is the same http one!
  145. }, {
  146. urls: ["http://*/*"],
  147. }, ["blocking"]);
  148. cwr.onBeforeRedirect.addListener(
  149. function(details) {
  150. //we're here because https tried to redirect to http! but we don't know if we forcefully set https and then got redirected back to http! yet.
  151. if (details.redirectUrl.substring(0,5) !== "http:") {
  152. loginfo("Detected ignored redirect to '"+details.redirectUrl+"' from '"+details.url+"'");
  153. return;
  154. }
  155. //ok, here, only redirects to http!
  156. loginfo("Detected https->http Redirect to '"+details.redirectUrl+"' from '"+details.url+"'");
  157. //eg. "Detected https->http Redirect to 'https://www.google.com/' from 'https://google.com/'"
  158. //Sample url which tries to redirect indefinitely(but chromium ofc. stops it after like 11 redirs): http://www.imdb.com/title/tt0088847/
  159. // console.log(details);
  160. //XXX: but details.requestId is the same on each redirect!
  161. //so maybe allow the redirect to happen one more time and then we know if it's in a loop!
  162. if (!(details.requestId in redirloopdetect)) {
  163. //flag only when the same url (except the protocol being different!)
  164. //since we're this far in, we know that details.url is https and redirectUrl is http !
  165. //XXX: to test this, go here: https://stackoverflow.com/questions/21747136/how-do-i-print-the-type-of-a-variable-in-rust/25413103#25413103 then click the link with the text 'Shubham's answer'
  166. if (details.url.substring(5) === details.redirectUrl.substring(4)) {
  167. logdebug("Flagging "+details.requestId+" redirect to: '"+details.redirectUrl+"' from '"+details.url+"'");
  168. // return { cancel: true }//hopefully? just guessing! 'It does not allow you to modify or cancel the request.' src: https://developer.chrome.com/extensions/webRequest
  169. // }else{
  170. redirloopdetect[details.requestId] = true;//any value i guess
  171. //and can't cancel it here! 'It does not allow you to modify or cancel the request.' src: https://developer.chrome.com/extensions/webRequest
  172. } else {
  173. logdebug("Not flagging "+details.requestId+" redirect to: '"+details.redirectUrl+"' from '"+details.url+"'");
  174. }
  175. }
  176. }, {
  177. urls: ["https://*/*"],//ok, this means the redir FROM this type of url, to any other type, is detected! eg. redir dest url can be http or https or anything else even!
  178. });
  179. cwr.onHeadersReceived.addListener(
  180. function(details) {
  181. thisage=max_age;
  182. hostn = new URL(details.url).hostname;
  183. fullh = hostn+" (full: "+details.url+" )";
  184. force_disable=false
  185. if (details.requestId in redirloopdetect) {
  186. delete redirloopdetect[details.requestId];
  187. //this means we need to force-disable the HSTS which we already set! or else it will redir loop! but we're here because we're allowing http request after a fail of such redir to https(because that site, eg. imdb, redir-ed us to http on its own!)
  188. loginfo("BOO! "+details.requestId+" "+details.url);
  189. //return { cancel: true };
  190. force_disable=true
  191. }
  192. if ( forceDisable.indexOf(hostn) > -1 ) {
  193. force_disable=true
  194. logverb("Host in forceDisable list: "+fullh); //eg. https://pastebin.com
  195. }
  196. // ignored=false;
  197. if ( ignore.indexOf(hostn) > -1 ) {
  198. logverb("Host in ignore list: '"+fullh+"' therefore HSTS not forced!"); //eg. https://pastebin.com
  199. // ignored=true;
  200. if (! force_disable) {//forceDisable list takes precedence over ignore list!
  201. return { }; //TODO: Does {} mean that no changes were perfomed to headers? so original headers are preserved? check online docs to see if this is so!
  202. }
  203. }
  204. for (var i = 0; i < details.responseHeaders.length; i++) {
  205. // console.log(details.responseHeaders[i].name);
  206. if (details.responseHeaders[i].name.toLowerCase() === "strict-transport-security") {
  207. /* if (ignored) {
  208. //remove HSTS header if host is in ignored list; OR we can leave it and set max-age to 0 to disable HSTS...
  209. details.responseHeaders.splice(i,1); //remove 1 item from index i; src: http://www.w3schools.com/jsref/jsref_splice.asp
  210. } else {*/
  211. logverb("HSTS already set in host's response headers: "+fullh+" "+details.responseHeaders[i].value); //eg. grc.com max-age=31536000; preload
  212. if ( ! force_disable ) {
  213. return { };
  214. }
  215. // }
  216. }
  217. }
  218. if ( force_disable ) {
  219. //How to turn off an already set(aka browser rememberes) HSTS setting? src: https://wordpress.org/support/topic/want-to-turn-off-http-strict-transport-security-hsts-header
  220. thisage=0; //confirmed to work on chromium 49.0.2623.87 (Developer Build) (64-bit) via Query Domain (after manually having added it with the above 'Add domain', see chrome://net-internals/#hsts )
  221. }
  222. details.responseHeaders.push({
  223. "name": "Strict-Transport-Security",
  224. "value": "max-age=" + thisage + "; includeSubDomains" //src: https://www.chromium.org/hsts
  225. //check HSTS status: under HSTS(while Capture is enabled!) then 'Query domain' from here chrome://net-internals/#hsts If "*_upgrade_mode: STRICT" then HSTS is on! How to interpret: https://security.stackexchange.com/questions/68883/checking-domains-hsts-status
  226. //also note, if 'broken HTTPS' (eg. https://rustbyexample.com since it's hosted by github) then the above query will yield 'Not found'
  227. });
  228. logverb("Force-"+(force_disable?"disabling":"enabling")+" HSTS for: "+fullh+" "+details.responseHeaders[details.responseHeaders.length-1].value);
  229. return {responseHeaders: details.responseHeaders};
  230. },
  231. {
  232. urls: ["https://*/*"],//http-only will never send HSTS headers - so is the spec.!
  233. types: ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other",
  234. "font", "ping",
  235. //rest are not accepted: "xbl", "xslt", "ping", "beacon", "xml_dtd", "font", "media", "websocket", "csp_report", "imageset", "web_manifest" src: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/WebRequest/ResourceType
  236. //FIXME: catch behind-the-scenes too! eg. uBlock/uMatrix can, also they can fetch resources over http!
  237. ]
  238. },
  239. ["blocking", "responseHeaders" ]
  240. );