├── .gitignore ├── firefox ├── icon.png ├── manifest.json ├── options.html ├── options.js └── background.js ├── .gitattributes ├── Makefile ├── .eslintrc ├── LICENSE ├── chrome └── https-by-default.patch └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /firefox/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rob--W/https-by-default/HEAD/firefox/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Linguist ignores bootstrap.js by default, so mark it as a non-vendor file. 2 | # https://github.com/github/linguist/blob/53c3cb3/lib/linguist/vendor.yml#L46 3 | bootstrap.js linguist-vendored=false 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FFFILES = manifest.json 2 | FFFILES += icon.png 3 | FFFILES += background.js 4 | FFFILES += options.html 5 | FFFILES += options.js 6 | 7 | firefox/https-by-default.xpi: $(addprefix firefox/,$(FFFILES)) 8 | cd firefox && zip https-by-default.xpi $(FFFILES) 9 | 10 | clean: 11 | rm -f firefox/https-by-default.xpi 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 8 5 | }, 6 | "env": { 7 | "es6": true, 8 | "browser": true, 9 | "webextensions": true 10 | }, 11 | "rules": { 12 | "strict": [2, "global"], 13 | "max-len": [2, { 14 | "code": 100 15 | }], 16 | "no-console": [0] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HTTPS by default", 3 | "description": "Request websites over secure https by default from the location bar instead of insecure http. Exceptions can be added at the add-on's preferences page (to not use HTTPS by default for some sites).", 4 | "version": "0.4.5", 5 | "manifest_version": 2, 6 | "background": { 7 | "scripts": ["background.js"] 8 | }, 9 | "permissions": [ 10 | "storage", 11 | "tabs", 12 | "webNavigation", 13 | "webRequest", 14 | "webRequestBlocking", 15 | "*://*/*" 16 | ], 17 | "options_ui": { 18 | "page": "options.html" 19 | }, 20 | "applications": { 21 | "gecko": { 22 | "strict_min_version": "57.0a1", 23 | "id": "https-by-default@robwu.nl" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /firefox/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | 19 | HTTPS is enabled by default for all navigations from the location bar and bookmarks. 20 | If a site does not support https, you can opt in to using http by default for that site by adding the domain to the following list. 21 | When https by default is turned off for a domain, its subdomains will also not use https by default. 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rob Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /firefox/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var domains_nohttps_input = document.getElementById('domains_nohttps_input'); 4 | var enable_logging_input = document.getElementById('enable_logging_input'); 5 | var save_button = document.getElementById('save_button'); 6 | 7 | browser.storage.local.get({ 8 | domains_nohttps: '', 9 | enable_logging: false, 10 | }).then(({domains_nohttps, enable_logging}) => { 11 | if (domains_nohttps) { 12 | domains_nohttps_input.value = domains_nohttps; 13 | } 14 | enable_logging_input.checked = enable_logging; 15 | }); 16 | 17 | 18 | let throttle; 19 | 20 | function commitChange() { 21 | clearTimeout(throttle); 22 | 23 | let domains_nohttps = domains_nohttps_input.value; 24 | // TODO: Validate? 25 | browser.storage.local.set({domains_nohttps}).then(() => { 26 | if (domains_nohttps_input.value === domains_nohttps) { 27 | save_button.value = 'Saved!'; 28 | } 29 | }); 30 | } 31 | 32 | save_button.onclick = commitChange; 33 | domains_nohttps_input.onchange = commitChange; 34 | domains_nohttps_input.oninput = () => { 35 | save_button.value = 'Save'; 36 | clearTimeout(throttle); 37 | throttle = setTimeout(commitChange, 1000); 38 | }; 39 | 40 | enable_logging_input.onchange = () => { 41 | browser.storage.local.set({ 42 | enable_logging: enable_logging_input.checked, 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /chrome/https-by-default.patch: -------------------------------------------------------------------------------- 1 | diff --git a/components/omnibox/browser/location_bar_model_impl.cc b/components/omnibox/browser/location_bar_model_impl.cc 2 | index 689f02345fd8..9539bd697e0c 100644 3 | --- a/components/omnibox/browser/location_bar_model_impl.cc 4 | +++ b/components/omnibox/browser/location_bar_model_impl.cc 5 | @@ -41,12 +41,15 @@ LocationBarModelImpl::~LocationBarModelImpl() { 6 | 7 | // LocationBarModelImpl Implementation. 8 | std::u16string LocationBarModelImpl::GetFormattedFullURL() const { 9 | - return GetFormattedURL(url_formatter::kFormatUrlOmitDefaults); 10 | + return GetFormattedURL( 11 | + url_formatter::kFormatUrlOmitDefaults & 12 | + ~url_formatter::kFormatUrlOmitHTTP); 13 | } 14 | 15 | std::u16string LocationBarModelImpl::GetURLForDisplay() const { 16 | url_formatter::FormatUrlTypes format_types = 17 | - url_formatter::kFormatUrlOmitDefaults; 18 | + url_formatter::kFormatUrlOmitDefaults & 19 | + ~url_formatter::kFormatUrlOmitHTTP; 20 | if (delegate_->ShouldTrimDisplayUrlAfterHostName()) { 21 | format_types |= url_formatter::kFormatUrlTrimAfterHost; 22 | } 23 | diff --git a/components/omnibox/browser/autocomplete_input.cc b/components/omnibox/browser/autocomplete_input.cc 24 | index 43fc0dc783d8..1f7e5ce9dee1 100644 25 | --- a/components/omnibox/browser/autocomplete_input.cc 26 | +++ b/components/omnibox/browser/autocomplete_input.cc 27 | @@ -285,6 +285,17 @@ metrics::OmniboxInputType AutocompleteInput::Parse( 28 | // between an HTTP URL and a query, or the scheme is HTTP or HTTPS, in which 29 | // case we should reject invalid formulations. 30 | 31 | + if (!parts->scheme.is_nonempty() && 32 | + base::EqualsCaseInsensitiveASCII(parsed_scheme_utf8, url::kHttpScheme)) { 33 | + // Scheme was not specified. url_fixer::FixupURL automatically adds http:, 34 | + // but we want to default to https instead. 35 | + if (scheme) 36 | + *scheme = base::ASCIIToUTF16(url::kHttpsScheme); 37 | + GURL::Replacements replacements; 38 | + replacements.SetSchemeStr(url::kHttpsScheme); 39 | + *canonicalized_url = canonicalized_url->ReplaceComponents(replacements); 40 | + } 41 | + 42 | // Determine the host family. We get this information by (re-)canonicalizing 43 | // the already-canonicalized host rather than using the user's original input, 44 | // in case fixup affected the result here (e.g. an input that looks like an 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTPS by default 2 | 3 | Upon requesting "example.com" from the location bar, your browser will attempt 4 | to load `http://example.com` over an insecure connection by default. 5 | Those who want to load the site over a secure connection have to manually put 6 | "https://" in front of the URL. Because it is easier to not type "https://", 7 | most websites are accessed over an insecure connection. 8 | 9 | This project is an endeavour to get HTTPS to become the default scheme in web 10 | browsers. With the following instructions, requesting `example.com` from the 11 | location bar will result in a navigation to `https://example.com` instead of 12 | `http://example.com` (which is the current ubiquitous but insecure default). 13 | 14 | Some websites are incorrectly configured and cannot be accessed over a secure 15 | connection. If you come across such a site, edit the URL in the location bar 16 | and insert "http://" in front of it to access the site anyway. 17 | 18 | Many of these days' web browsers hide the "http://" prefix in the location bar. 19 | When "http" is the default scheme, focusing the location bar and pressing Enter 20 | will trigger a navigation to the same URL, i.e. reload the current page. With 21 | https enabled by default, the page will not be reloaded, but you will be 22 | navigated to the https-version of the site instead (unless you put "http://" in 23 | front of the URL). 24 | 25 | 26 | # Firefox 27 | 28 | Install the [HTTPS by default add-on](https://addons.mozilla.org/en-US/firefox/addon/https-by-default) 29 | to enable https by default in Firefox. 30 | 31 | Visit `about:config` and set `browser.urlbar.trimURLs` to `false` to not hide 32 | the "http://" prefix by default. 33 | 34 | 35 | # Chrome / Chromium 36 | 37 | Chrome's extension APIs is not powerful enough to support this feature. 38 | See https://stackoverflow.com/a/26462483/938089 for instructions on getting and 39 | compiling a stable version of Chrome. Before compiling, apply the patch from 40 | this repository to get https by default: 41 | 42 | ```sh 43 | cd chromium/src # this is the location of Chromium's git repository 44 | git apply https-by-default.patch # the .patch file from this repo at chrome/. 45 | ``` 46 | 47 | After applying this patch, https will be used by default for navigations that 48 | are triggered from the omnibox, and the "http://" prefix will not be removed 49 | from the omnibox. 50 | 51 | 52 | # Roadmap 53 | 54 | At the initial stage, the project's focus is to offer the option to enable https 55 | by default in web browsers. The ultimate goal is to get browser vendors to 56 | enable https by default. This is a significant change, and as such it will need 57 | compelling data to demonstrate that the change does not hinder usability. If the 58 | extensions from this project take off, I could update them to measure the impact 59 | of https by default on usability. This feature will only be added if it can be 60 | done without compromising the users' privacy. 61 | 62 | 63 | # Alternatives 64 | 65 | - HTTPS Everywhere (https://www.eff.org/https-everywhere) 66 | 67 | This is a Firefox addon and a Chrome extension that contains a huge database 68 | of rules which redirects http requests to https. This characteristic is its 69 | forte and also its weakness. The rules allows the add-on to force https for 70 | *known* sites. Unlisted sites will still be accessed over an insecure 71 | connection by default. 72 | 73 | - HTTP Strict Transport Security (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security) 74 | 75 | Website authors can include the `Strict-Transport-Security` in their secure 76 | response to tell the browser to force https for subsequent visits to the site. 77 | This *only* works if the website author adds this STS header *and* if the user 78 | visits the site at least once over https, or if the site was registered in a 79 | pre-loaded HSTS list. Unfortunately, the combination of both is not quite 80 | common. 81 | -------------------------------------------------------------------------------- /firefox/background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Rob Wu (https://robwu.nl) 3 | */ 4 | 'use strict'; 5 | 6 | const DOMAIN_WILDCARD_LEAF_SYMBOL = Symbol('Domain wildcard prefix'); 7 | 8 | var prefsParsed = { 9 | domains_nohttps: new Map(), 10 | enable_logging: false, 11 | }; 12 | var prefsReady = false; 13 | var prefsReadyPromise = browser.storage.local.get({ 14 | enable_logging: false, 15 | domains_nohttps: '', 16 | }) 17 | .then(({domains_nohttps, enable_logging}) => { 18 | doParsePrefs(domains_nohttps); 19 | prefsParsed.enable_logging = enable_logging; 20 | }, (() => {})) 21 | .then(() => { prefsReady = true; }); 22 | 23 | browser.storage.onChanged.addListener((changes) => { 24 | if (changes.domains_nohttps) { 25 | doParsePrefs(changes.domains_nohttps.newValue); 26 | } 27 | if (changes.enable_logging) { 28 | prefsParsed.enable_logging = changes.enable_logging.newValue; 29 | } 30 | }); 31 | 32 | 33 | var tabCreationTimes = new Map(); 34 | var tabActivatedTimes = new Map(); 35 | var tabPendingRedirectInfos = new Map(); 36 | 37 | browser.tabs.onCreated.addListener(tab => { 38 | if (tab.id) { 39 | tabCreationTimes.set(tab.id, Date.now()); 40 | if (tab.active) { 41 | tabActivatedTimes.set(tab.id, Date.now()); 42 | } 43 | } 44 | }); 45 | browser.tabs.onActivated.addListener(({tabId}) => { 46 | tabActivatedTimes.set(tabId, Date.now()); 47 | }); 48 | browser.tabs.onRemoved.addListener(tabId => { 49 | tabCreationTimes.delete(tabId); 50 | tabActivatedTimes.delete(tabId); 51 | unregisterRedirectInfo(tabId); 52 | }); 53 | browser.tabs.query({}).then(tabs => { 54 | for (let tab of tabs) { 55 | // If the extension is loading around Firefox's start-up, then we 56 | // should not rewrite URLs. 57 | // If the extension was loaded long after Firefox's start-up, then 58 | // these timestamps are probably in the past (or the tab is not 59 | // an about:blank page), and we will not inadvertently stop the 60 | // redirect from happening. 61 | tabCreationTimes.set(tab.id, tab.lastAccessed || Date.now()); 62 | tabActivatedTimes.set(tab.id, tab.active ? Date.now() : 0); 63 | } 64 | }); 65 | 66 | browser.webRequest.onBeforeRequest.addListener(async (details) => { 67 | if (details.originUrl) { 68 | // Likely a web-triggered navigation, or a reload of such a page. 69 | return; 70 | } 71 | if (details.tabId === -1) { 72 | // Invisible navigation. Unlikely to be requested by the user. 73 | return; 74 | } 75 | 76 | // Possibly a navigation from the awesomebar, bookmark, etc. 77 | // ... or a reload of a (discarded) tab. 78 | 79 | // I would like to only rewrite typed URLs without explicit scheme, 80 | // but unfortunately the extension API does not offer the typed text, 81 | // so we will rewrite any non-web-initiated navigation, 82 | // including bookmarks, auto-completed URLs and full URLs with "http:" prefix. 83 | 84 | let {tabId, url: requestedUrl, requestedId} = details; 85 | 86 | if (!prefsReady) { 87 | await prefsReadyPromise; 88 | } 89 | 90 | if (!shouldRedirectToHttps(requestedUrl)) { 91 | return; 92 | } 93 | 94 | let currentTab; 95 | for (let start = Date.now(); Date.now() - start < 200; ) { 96 | try { 97 | currentTab = await browser.tabs.get(tabId); 98 | } catch (e) { 99 | // Tab does not exist. E.g. when a URL is loaded in a new tab page 100 | // and the request happens before the tab exists. 101 | await new Promise(resolve => { setTimeout(resolve, 20); }); 102 | } 103 | } 104 | 105 | // Heuristic: On Firefox for Android, tabs can be discarded (and its URL 106 | // becomes "about:blank"). When a tab is re-activated, the original URL is 107 | // loaded again. These URLs should not be modified by us. 108 | // On Firefox for Desktop, this can also be a new tab of unknown origin. 109 | if (currentTab && currentTab.url === 'about:blank') { 110 | let tabCreationTime = tabCreationTimes.get(tabId); 111 | if (tabCreationTime === undefined) { 112 | // The request was generated before the tab was created, 113 | // or the tab has been removed. 114 | return; 115 | } 116 | let tabActivatedTime = tabActivatedTimes.get(tabId); 117 | if (tabId === undefined) { 118 | // If the time of when the tab was first activated is unknown, 119 | // fall back to the time of when the time was last activated. 120 | tabActivatedTime = currentTab.lastAccessed; 121 | } 122 | // Typing a site takes time, so it is reasonable to choose a relatively 123 | // long time threshold. One second is a very realistic underbound for 124 | // typing some domain name. It is also large enough to allow the browser 125 | // to process the request, even if the device is very slow (CPU-wise). 126 | if (details.timeStamp - tabActivatedTime < 1000) { 127 | // Likely resuming from a discarded tab on Android. 128 | return; 129 | } 130 | // If the tab is created around the same time as the request, then this 131 | // is possibly an Alt-Enter navigation on Firefox Desktop. 132 | // But it can also be a bookmark opened in a new tab, an 133 | // extension-created tab (#15) or a URL opened via the command line (#14). 134 | // The latter cases are probably more common, so we don't redirect for 135 | // these. 136 | if (details.timeStamp - tabCreationTimes.get(tabId) < 300) { 137 | return; 138 | } 139 | } 140 | 141 | if (currentTab && isDerivedURL(currentTab.url, requestedUrl)) { 142 | // User had likely edited the current URL and pressed Enter. 143 | // Do not rewrite the request to HTTPS. 144 | return; 145 | } 146 | 147 | if (tabCreationTimes.has(tabId)) { 148 | var pendingRedirectInfo = tabPendingRedirectInfos.get(tabId); 149 | if (pendingRedirectInfo && pendingRedirectInfo.redirectedRequestId === requestedId) { 150 | // Don't rewrite redirects. Redirects are triggered by a server response, and 151 | // are certainly not the result of a manually typed URL. 152 | return; 153 | } 154 | if (pendingRedirectInfo && pendingRedirectInfo.url === requestedUrl && 155 | currentTab && currentTab.status === 'loading') { 156 | // The previous HTTP->HTTPS navigation hasn't started, and the HTTP navigation is 157 | // attempted again. This site does probably not support HTTPS, and the user is trying 158 | // to force navigation to HTTP. 159 | return; 160 | } 161 | registerRedirectInfo(tabId, requestedUrl); 162 | } 163 | 164 | // Replace "http:" with "https:". 165 | let httpsUrl = requestedUrl.replace(':', 's:'); 166 | 167 | if (prefsParsed.enable_logging) { 168 | console.log('[HTTPS by default] Redirecting ' + requestedUrl); 169 | } 170 | 171 | return { 172 | redirectUrl: httpsUrl, 173 | }; 174 | }, { 175 | urls: ['http://*/*'], 176 | types: ['main_frame'] 177 | }, ['blocking']); 178 | 179 | /** 180 | * Determines whether the given http:-URL should be redirected to https:. 181 | * 182 | * @param {string} requestedUrl A valid http:-URL. 183 | * @returns {boolean} Whether to redirect to https. 184 | */ 185 | function shouldRedirectToHttps(requestedUrl) { 186 | let {hostname} = new URL(requestedUrl); 187 | 188 | if (!hostname.includes('.')) { 189 | // Any globally resolvable address should have a TLD. 190 | // Otherwise it is not likely to obtain a SSL certificate for it. 191 | // E.g. localhost. 192 | return false; 193 | } 194 | 195 | if (hostname.endsWith('.test') || 196 | hostname.endsWith('.example') || 197 | hostname.endsWith('.invalid') || 198 | hostname.endsWith('.localhost')) { 199 | // Reserved root level DNS names - RFC 2606. 200 | return false; 201 | } 202 | 203 | if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) || 204 | hostname.startsWith('[') && hostname.endsWith(']')) { 205 | // Don't redirect IPv4 or IPv6 addresses. 206 | return false; 207 | } 208 | 209 | let map = prefsParsed.domains_nohttps; 210 | for (let part of hostname.split('.').reverse()) { 211 | map = map.get(part); 212 | if (!map) { 213 | break; 214 | } 215 | if (map.has(DOMAIN_WILDCARD_LEAF_SYMBOL)) { 216 | return false; 217 | } 218 | } 219 | 220 | // By default, redirect to https:. 221 | return true; 222 | } 223 | 224 | /** 225 | * Determines whether the requested URL is based on the current URL. 226 | * 227 | * @param {string} currentUrl - The current URL of the tab. 228 | * @param {string} requestedUrl - The requested http:-URL. 229 | * @returns {boolean} Whether to avoid rewriting the request to https. 230 | */ 231 | function isDerivedURL(currentUrl, requestedUrl) { 232 | if (currentUrl === requestedUrl) { 233 | // In Firefox, tab.url shows the URL of the currently loaded resource in 234 | // a tab, so if the URLs are equal, it is a page reload. 235 | return true; 236 | } 237 | if (!currentUrl.startsWith('http')) { 238 | // Not a http(s) URL, e.g. about:. 239 | return false; 240 | } 241 | let cur; 242 | try { 243 | cur = new URL(currentUrl); 244 | } catch (e) { 245 | return false; 246 | } 247 | let req = new URL(requestedUrl); 248 | if (req.hostname === cur.hostname) { 249 | // The user had already accessed the domain over HTTP, so there is no 250 | // much gain in forcing a redirect to HTTPS. 251 | // 252 | // This supports the use case of editing the current (HTTP) URL and 253 | // then navigating to it. 254 | // 255 | // This also covers the following scenario: 256 | // - User opens http://xxx 257 | // - The extension redirects to https://xxx 258 | // - ...but https://xxx is not serving the content that the user expects 259 | // - User opens http://xxx again 260 | // - Extension should not redirect to https. 261 | return true; 262 | } 263 | 264 | if (cur.protocol === 'https:') { 265 | // If the current tab's URL is https, do not downgrade to http. 266 | return false; 267 | } 268 | 269 | if ((req.pathname.length > 1 || 270 | req.search.length > 2 || 271 | req.hash.length > 2) && 272 | req.pathname === cur.pathname && 273 | req.search === cur.search && 274 | req.hash === cur.hash) { 275 | // Everything after the domain name is non-empty and equal. 276 | // The user might be trying to correct a misspelled domain name. 277 | // Do not rewrite to HTTPS. 278 | return true; 279 | } 280 | 281 | // Proceed to redirect to https. 282 | return false; 283 | } 284 | 285 | // Records the intercepted request that is going to be redirected to HTTPS. 286 | // The redirection URL will be discarded when a response is received for the main frame in the 287 | // given tab, when the tab is removed, or when the request fails. 288 | // 289 | // The caller should make sure that tabId refers to a valid tab. 290 | function registerRedirectInfo(tabId, requestedUrl) { 291 | let redirectInfo = { 292 | url: requestedUrl, 293 | redirectedRequestId: null, 294 | unregister, 295 | }; 296 | function unregister() { 297 | browser.webRequest.onHeadersReceived.removeListener(onHeadersReceived); 298 | browser.webRequest.onResponseStarted.removeListener(unregister); 299 | browser.webRequest.onErrorOccurred.removeListener(unregister); 300 | tabPendingRedirectInfos.delete(tabId); 301 | } 302 | 303 | function onHeadersReceived({requestedId, statusCode}) { 304 | if (statusCode !== 301 && statusCode !== 302 && statusCode !== 303 && 305 | statusCode !== 307 && statusCode !== 308) { 306 | unregister(); 307 | return; 308 | } 309 | if (tabPendingRedirectInfos.get(tabId) !== redirectInfo) { 310 | // unregister() has been invoked between the queued webRequest event and the 311 | // actual dispatch. This is not expected to happen, but can happen in theory. 312 | // Exit now to avoid registering and leaking a webRequest event handler. 313 | return; 314 | } 315 | 316 | // When a request is restarted, the requestedId is preserved. 317 | redirectInfo.redirectedRequestId = requestedId; 318 | 319 | // If the response did not have a valid Location header, the request won't be restarted. 320 | // Detect the successful response body via webRequest.onResponseStarted. 321 | // The onHeadersReceived event can fire multiple times throughout a request, but it is safe 322 | // to call addListener because the event handler won't be registered twice. 323 | browser.webRequest.onResponseStarted.addListener(unregister, { 324 | urls: ['*://*/*'], 325 | types: ['main_frame'], 326 | tabId, 327 | }); 328 | } 329 | 330 | unregisterRedirectInfo(tabId); 331 | 332 | // A request was successfully received for the main frame, so clear the registered redirection 333 | // URL. This is not necessarily a response for |requestedUrl|, any response will do. 334 | browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { 335 | urls: ['*://*/*'], 336 | types: ['main_frame'], 337 | tabId, 338 | }, ['blocking']); 339 | 340 | // The server is not reachable via HTTPs, and the URL can be unregistered because 341 | // tab.url will show the URL of the attempted navigation: 342 | browser.webRequest.onErrorOccurred.addListener(unregister, { 343 | urls: [requestedUrl], 344 | types: ['main_frame'], 345 | tabId, 346 | }); 347 | 348 | // Expose the unregister function so that if somehow neither of the above events happen, 349 | // that the listener is removed when the tab is removed (via tabs.onRemoved): 350 | tabPendingRedirectInfos.set(tabId, redirectInfo); 351 | } 352 | 353 | function unregisterRedirectInfo(tabId) { 354 | let pendingRedirectInfo = tabPendingRedirectInfos.get(tabId); 355 | if (pendingRedirectInfo) { 356 | pendingRedirectInfo.unregister(); 357 | tabPendingRedirectInfos.delete(tabId); 358 | } 359 | } 360 | 361 | function doParsePrefs(domains_nohttps) { 362 | prefsParsed.domains_nohttps = new Map(); 363 | if (domains_nohttps) { 364 | console.assert(typeof domains_nohttps === 'string'); 365 | for (let domain of domains_nohttps.split(/\s+/)) { 366 | if (!domain) { 367 | continue; 368 | } 369 | let map = prefsParsed.domains_nohttps; 370 | for (let part of domain.split('.').reverse()) { 371 | if (!map.has(part)) { 372 | map.set(part, new Map()); 373 | } 374 | map = map.get(part); 375 | } 376 | map.set(DOMAIN_WILDCARD_LEAF_SYMBOL); 377 | } 378 | } 379 | } 380 | --------------------------------------------------------------------------------