/** \file
* \brief This file contains common functions for Network Boundary Shield.
*
* \author Copyright (C) 2020 Pavel Pohner
* \author Copyright (C) 2020-2021 Martin Bednář
* \author Copyright (C) 2022 Marek Salon
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
*/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/**
* \defgroup NBS Network Boundary Shield
*
* \brief The Network Boundary Shield (NBS) is a protection against attacks from an external network (the Internet)
* to an internal network - especially against a reconnaissance attacks when a web browser is abused as a proxy.
* See, for example, the ForcePoint report https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf,
* https://www.forcepoint.com/blog/x-labs/attacking-internal-network-public-internet-using-browser-proxy.
* Another example is the detection of applications running on the localhost, see
* https://jshelter.org/localportscanning/.
*
* The NBS functionality is based on filtering HTTP requests. The Network Boundary Shield uses blocking webRequest API to handle HTTP requests.
* This means that processing of each HTTP request is paused before it is analyzed and allowed (if it seems benign) or blocked (if it is suspicious).
*
* The main goal of NBS is to prevent attacks like a public website requests a resource from the
* local computer (e.g. to determine open TCP ports and thus running applications) or
* internal network (e.g. the logo of the manufacturer of the local router); NBS will detect that
* a web page hosted on the public Internet tries to connect to a local IP address. NBS blocks only
* HTTP requests from a web page hosted on a public IP address to a private network resource. The
* user can allow specific web pages to access local resources (e.g. when using Intranet services).
*
* NBS uses CSV files provided by IANA
* (https://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xml) to
* determine public and local IP address prefixes. Both IPv4 and IPv6 is supported. The CSV files
* are downloaded during the $(PROJECT_NAME) building process.
*
* The NBS has only a small impact on the web browser performance. The impact differs for each implementation.
*
* More information about the Network Boundary Shield can be obtained from the master thesis by Pavel Pohner: https://www.vutbr.cz/studenti/zav-prace/detail/129272 (in Czech).
*/
/** \file
* \ingroup NBS
*
* This file contains basic logic of the NBS, NBS global variables and objects,
* functions for reading and parsing CSV files, and functions for identifying and processing IP addresses and checking IP ranges.
*/
/**
* Locally served IPV4 DNS zones loaded from IANA.
*/
var localIPV4DNSZones;
/**
* Locally served IPV6 DNS zones loaded from IANA.
*/
var localIPV6DNSZones;
/**
* Associtive array of hosts, that are currently among trusted "do not blocked" hosts.
*/
var doNotBlockHosts = {};
/**
* Associtive array of settings supported by this module.
*/
var nbsSettings = {};
/**
* Object holding active notifications of this module.
*/
var nbsNotifications = {};
/**
* Definition of settings supported by this module.
*/
const NBS_DEF_SETTINGS = {
blocking: {
label: browser.i18n.getMessage("nbsBlocking"),
description: browser.i18n.getMessage("nbsBlockingDescription"),
description2: [browser.i18n.getMessage("nbsBlockingDescription2")],
params: [
{
// 0
short: browser.i18n.getMessage("protectionConfigurationOptionActivatedOff"),
description: browser.i18n.getMessage("nbsBlockingOffDescription")
},
{
// 1
short: browser.i18n.getMessage("protectionConfigurationOptionActivatedOn"),
description: browser.i18n.getMessage("nbsBlockingOnDescription")
}
]
},
notifications: {
label: browser.i18n.getMessage("shieldNotifications"),
description: browser.i18n.getMessage("NBSNotificationsDescription"),
description2: [browser.i18n.getMessage("NBSNotificationsDescription2")],
params: [
{
// 0
short: browser.i18n.getMessage("protectionConfigurationOptionActivatedOff"),
description: browser.i18n.getMessage("NBSNotificationsOffDescription")
},
{
// 1
short: browser.i18n.getMessage("protectionConfigurationOptionActivatedOn"),
description: browser.i18n.getMessage("NBSNotificationsOnDescription")
}
]
}
};
/**
* The function that loads module configuration from sync storage.
*/
function nbsLoadConfiguration() {
browser.storage.sync.get(["requestShieldOn", "nbsWhitelist", "nbsSettings"]).then(function(result) {
//If found object is true or undefined, turn the requestShieldOn
if (result.requestShieldOn == undefined || result.requestShieldOn)
{
//Hook up the listeners
browser.webRequest.onBeforeSendHeaders.addListener(
beforeSendHeadersListener,
{urls: [""]},
["blocking", "requestHeaders"]
);
if (typeof onResponseStartedListener === "function")
{
browser.webRequest.onResponseStarted.addListener(
onResponseStartedListener,
{urls: [""]},
["responseHeaders"]
);
}
}
doNotBlockHosts = result.nbsWhitelist ? result.nbsWhitelist : {};
nbsSettings = result.nbsSettings ? result.nbsSettings : {};
});
}
/// \cond (Exclude this section from the doxygen documentation. If this section is not excluded, it is documented as a separate function.)
/// Hook up the listener for receiving messages
nbsLoadConfiguration();
browser.runtime.onMessage.addListener(nbsCommonMessageListener);
browser.runtime.onMessage.addListener(nbsMessageListener);
browser.runtime.onMessage.addListener(nbsSettingsListener);
// Listen for permissions removal to adapt settings accordingly
browser.permissions.onRemoved.addListener((permissions) => {
correctSettingsForRemovedPermissions(permissions.permissions, nbsSettings, NBS_DEF_SETTINGS);
browser.storage.sync.set({"nbsSettings": nbsSettings});
});
// Obtain file path in user's file system and read CSV file with IPv4 local zones
readFile(browser.runtime.getURL("ipv4.dat"))
.then(_res => {
//Parse loaded CSV and store it in prepared variable
localIPV4DNSZones = parseCSV(_res, true);
})
.catch(_error => {
console.error(_error );
});
// Obtain file path in user's file system and read CSV file with IPv6 local zones
readFile(browser.runtime.getURL("ipv6.dat"))
.then(_res => {
//Parse loaded CSV and store it in prepared variable
localIPV6DNSZones = parseCSV(_res, false);
})
.catch(_error => {
console.error(_error );
});
/// \endcond
/**
* Checks validity of IPv4 addresses.
*
* \param url An URL that may or may not contains an IPv4 address instead of a domain name.
*
* \returns TRUE if the url matches IPv4 regex, FALSE otherwise.
*/
function isIPV4(url)
{
var reg = new RegExp("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
return reg.test(url);
}
/**
* Checks validity IPV6 address.
*
* \param url An URL that may or may not contains an IPv6 address instead of a domain name.
*
* \returns TRUE, if URL is valid IPV6 address, FALSE otherwise.
*/
function isIPV6(url)
{
if (url[0] === "[" && url[url.length - 1] === "]") {
url = url.substring(1, url.length - 1);
}
var reg = new RegExp("^(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))$", 'm');
return reg.test(url);
}
/**
* Checks whether the ipAddr is found in IPv4 localZones.
* If the IPv4 address is found in any IPv4 local zone, it means that this IPv4 address is private.
* IPv4 local zone is e.g. 192.168.000.000/16.
*
* \param ipAddr Valid IPv4 address.
*
* \returns TRUE if ipAddr exists in localZones fetched from IANA, FALSE otherwise.
*/
function isIPV4Private(ipAddr)
{
//Split IP address on dots, obtain 4 numbers
var substrIP = ipAddr.split('.');
//Convert IP address into array of 4 integers
var ipArray = substrIP.map(function(val){
return parseInt(val, 10);
});
//For each IPv4 locally served zone
for (var i = 0; i < localIPV4DNSZones.length; i++)
{
//Split the zone into array of J numbers
var zone = localIPV4DNSZones[i].split('.');
var k = 0;
//For each number of local zone IP
//(Decrementing, because local zones IPs are reverted
for (var j = zone.length - 1; j >= 0; j--)
{
//Check if the corresponding numbers match
//If not, then break and move onto next local zone
if (ipArray[k] != zone[j])
{
break;
}
else if(j == 0) //Checked all numbers of local zone
{
return true;
}
k++;
}
}
return false;
}
/**
* Checks whether the ipAddr is found in IPv6 localZones.
* If the IPv6 address is found in any IPv6 local zone, it means that this IPv6 address is private.
* IPv6 local zone is e.g. fe80::/10.
*
* \param ipAddr Valid IPv6 address.
*
* \returns TRUE if ipAddr exists in localZones fetched from IANA, FALSE otherwise.
*/
function isIPV6Private(ipAddr)
{
//Expand shorten IPv6 addresses to full length
ipAddr = expandIPV6(ipAddr);
//Split into array of fields
var substrIP = ipAddr.split(":");
//Join the fields into one string
ipAddr = substrIP.join("").toUpperCase();
//For each IPv6 locally served zone
for (var i = 0; i < localIPV6DNSZones.length; i++)
{
var zone = localIPV6DNSZones[i];
//For each char of zone
for (var j = 0; j < zone.length; j++)
{
//Compare the chars, if they do not match, break and move onto next zone
if (ipAddr.charAt(j) != zone.charAt(j))
{
break;
}
//Checked all chars of current zone -> private IP range
else if(j == zone.length - 1)
{
return true;
}
}
}
return false;
}
/**
* Function for parsing CSV files obtained from IANA.
* It strips .IN-ADDR and .IP6 from zones and comma delimiter, merges them into array by CSV rows.
*
* \param csv CSV obtained from IANA.
* \param ipv4 Boolean, saying whether the csv is IPv4 CSV or IPv6.
*
* \returns an array of parsed CSV values.
*/
function parseCSV(csv, ipv4)
{
//converting into array
var csvArray = CSVToArray(csv);
var DNSzones = [];
if (ipv4) //ipv4.csv
{
//cycle through first column of the CSV -> obtaining IP zones
//Starting with i = 1, skipping the CSV header
for (var i = 1; i < csvArray.length; i++)
{
//i-1, means start from 0
//Obtains IP zone, strips .IN-ADDR from the end of it, stroes into array
DNSzones[i-1] = csvArray[i][0].substring(0, csvArray[i][0].indexOf(".IN-ADDR"));
}
return DNSzones;
}
else //ipv6.csv
{
//Same as ipv4
for (var i = 1; i < csvArray.length-1; i++)
{
DNSzones[i-1] = csvArray[i][0].substring(0, csvArray[i][0].indexOf(".IP6"));
}
for (var i = 0; i < DNSzones.length; i++)
{
//Additionally splits the IP zone on dots
var splitted = DNSzones[i].split(".");
DNSzones[i] = "";
//Joins splitted IP zone into one string
for (var j = splitted.length - 1; j >= 0 ; j--)
{
DNSzones[i] += splitted[j];
}
}
return DNSzones;
}
}
/**
* Auxillary function for parsing CSV files.
* Converts CSV to array.
*
* \param strData Loaded CSV file as a string.
*
* \returns array containing CSV rows.
*/
function CSVToArray(strData){
// Create a regular expression to parse the CSV values.
var objPattern = new RegExp(
(
// Delimiters.
"(\\,|\\r?\\n|\\r|^)" +
// Quoted fields.
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
// Standard fields.
"([^\"\\,\\r\\n]*))"
),
"gi"
);
//Array to hold data
var csvData = [[]];
//Array to hold regex matches
var regexMatches = null;
//While not match
while (regexMatches = objPattern.exec(strData)){
// Get the delimiter that was found
var strMatchedDelimiter = regexMatches[1];
if (strMatchedDelimiter.length && (strMatchedDelimiter != ",")){
//New row
csvData.push([]);
}
// captured data (quoted or unquoted)
if (regexMatches[2]){
//quoted
var strMatchedValue = regexMatches[2].replace(
new RegExp( "\"\"", "g" ),
"\""
);
} else {
//non-quoted value.
var strMatchedValue = regexMatches[3];
}
//Add to data array
csvData[csvData.length - 1].push( strMatchedValue );
}
// Return the parsed data
return( csvData );
}
/**
* Function for expanding shorten ipv6 addresses.
*
* \param ip6addr Valid ipv6 address.
*
* \returns expanded ipv6 address in string.
*/
function expandIPV6(ip6addr)
{
if (ip6addr[0] === "[" && ip6addr[ip6addr.length - 1] === "]") {
ip6addr = ip6addr.substring(1, ip6addr.length - 1);
}
var expandedIP6 = "";
//Check for omitted groups of zeros (::)
if (ip6addr.indexOf("::") == -1)
{
//There are none omitted groups of zeros
expandedIP6 = ip6addr;
}
else
{
//Split IP on one compressed group
var splittedIP = ip6addr.split("::");
var amountOfGroups = 0;
//For each group
for (var i = 0; i < splittedIP.length; ++i)
{
//Split on :
amountOfGroups += splittedIP[i].split(":").length;
}
expandedIP6 += splittedIP[0] + ":";
//For each splitted group
for (var i = 0; i < 8 - amountOfGroups; ++i)
{
//insert zeroes
expandedIP6 += "0000:";
}
//Insert the rest of the splitted IP
expandedIP6 += splittedIP[1];
}
//Split expanded IPv6 into parts
var addrParts = expandedIP6.split(":");
var addrToReturn = "";
//For each part
for (var i = 0; i < 8; ++i)
{
//check the length of the part
while(addrParts[i].length < 4)
{
//if it's less than 4, insert zero
addrParts[i] = "0" + addrParts[i];
}
addrToReturn += i != 7 ? addrParts[i] + ":" : addrParts[i];
}
return addrToReturn;
}
/**
* Check if the hostname or any of it's domains is whitelisted.
*
* \param hostname Any hostname (subdomains allowed).
*
* \returns TRUE when domain (or subdomain) is whitelisted, FALSE otherwise.
*/
function isNbsWhitelisted(hostname)
{
//Calling a function from url.js
var domains = extractSubDomains(hostname);
for (var domain of domains)
{
if (doNotBlockHosts[domain] != undefined)
{
return true;
}
}
return false;
}
/**
* Log data about NBS blocking in context of tabs.
* This data will be used for notification creation.
*
* \param origin Origin of the request.
* \param target Target of the request.
* \param tabId Tab ID of blocked request.
*/
function notifyBlockedRequest(origin, target, tabId) {
if (nbsSettings.notifications) {
nbsNotifications[tabId] = nbsNotifications[tabId] || {};
nbsNotifications[tabId].records = nbsNotifications[tabId].records || {};
nbsNotifications[tabId].records[`${origin},${target}`] = (nbsNotifications[tabId].records[`${origin},${target}`] || 0) + 1;
nbsNotifications[tabId].total = (nbsNotifications[tabId].total || 0) + 1;
}
// start notifying a user when the first blocked request occurs
if (nbsNotifications[tabId].total == 1) {
setTimeout(showNbsNotification, 2000, tabId);
createCumulativeNotification(tabId);
}
}
// Listen for tab update to clear notifications data of the tab
browser.tabs.onUpdated.addListener(function (tabId, changeInfo) {
if (changeInfo.status == "loading") {
clearNbsNotification(tabId);
}
});
// Listen for tab remove to clear notifications data of the tab
browser.tabs.onRemoved.addListener(clearNbsNotification);
/**
* Clear notification data for the tab.
*
* \param tabId Tab ID of notification.
*/
function clearNbsNotification(tabId) {
if (nbsNotifications[tabId]) {
if (nbsNotifications[tabId].timerId) {
clearTimeout(nbsNotifications[tabId].timerId);
}
delete nbsNotifications[tabId];
}
}
/**
* Create second notification containing a summary of accumulated data.
* This notification is shown after the initial one if a page continues to access local network.
*
* \param tabId Integer number representing ID of browser tab.
*/
async function createCumulativeNotification(tabId) {
if (nbsNotifications[tabId].last == nbsNotifications[tabId].total) {
let active = await browser.notifications.getAll();
if (!Object.keys(active).includes("nbs-" + tabId)) {
showNbsNotification(tabId);
}
return;
}
nbsNotifications[tabId].last = nbsNotifications[tabId].total;
nbsNotifications[tabId].timerId = setTimeout(createCumulativeNotification, 4000, tabId);
}
/**
* Creates and presents notification about blocked requests.
*
* \param tabId Integer number representing ID of browser tab.
*/
function showNbsNotification(tabId) {
nbsNotifications[tabId].last = nbsNotifications[tabId].total;
let message;
let records = Object.keys(nbsNotifications[tabId].records);
if (records.length == 1) {
// The page contacted just one target, display both host and target
let [origin, target] = records[0].split(",");
let count = nbsNotifications[tabId].records[records[0]];
let params = [origin, target, count];
message = nbsSettings.blocking ? browser.i18n.getMessage("NBSBlockedMessageWithTarget", params) : browser.i18n.getMessage("NBSDetectedMessageWithTarget", params);
}
else {
// The page contacted multiple targets, display just the host
let host = getEffectiveDomain(availableTabs[tabId].url);
let params = [host, nbsNotifications[tabId].total];
message = nbsSettings.blocking ? browser.i18n.getMessage("NBSBlockedMessageMultipleTargets", params) : browser.i18n.getMessage("NBSDetectedMessageMultipleTargets", params);
}
browser.notifications.create("nbs-" + tabId, {
"type": "basic",
"iconUrl": browser.runtime.getURL("img/icon-48.png"),
"title": nbsSettings.blocking ? browser.i18n.getMessage("NBSBlockedTitle") : browser.i18n.getMessage("NBSDetectedTitle"),
"message": message
});
setTimeout(() => {
browser.notifications.clear("nbs-" + tabId);
}, 6000);
}
/**
* \brief The event listener, hooked up to the webExtension onMessage event.
*
* The listener sends message response which contains information if the current site is whitelisted or not.
*
* \param message Receives full message (destructured as {message, site}).
* \param sender Sender of the message.
*/
function nbsMessageListener({message, site}, sender)
{
//Message came from popup,js, asking whether is this site whitelisted
if (message === "is current site whitelisted?")
{
return Promise.resolve(`current site is ${isNbsWhitelisted(site) ? '' : 'not '}whitelisted`);
}
}
/**
* \brief The event listener, hooked up to the webExtension onMessage event.
*
* The listener sends message response which contains information about cuurent module settings.
*
* \param message Receives full message.
*/
function nbsSettingsListener(message)
{
if (message.purpose === "nbs-get-settings") {
// send settings definition and current values
return Promise.resolve({
def: NBS_DEF_SETTINGS,
val: nbsSettings
});
}
else if (message.purpose === "nbs-set-settings") {
// update current settings
nbsSettings[message.id] = message.value;
browser.storage.sync.set({"nbsSettings": nbsSettings});
}
else if (message.purpose === "nbs-load-config") {
// load current configuration
nbsLoadConfiguration();
}
}
/**
* Event listener hooked up to webExtensions onMessage event.
* Does appropriate action based on message (e.g. Turn on/off the NBS, add/remove a site to/from whitelist, ...).
*
* \param message Receives full message.
* \param sender Sender of the message.
*/
function nbsCommonMessageListener(message, sender)
{
//Message came from options.js, updated whitelist
if (message.message === "whitelist updated")
{
//actualize current doNotBlockHosts from storage
browser.storage.sync.get(["nbsWhitelist"]).then(function(result){
doNotBlockHosts = result.nbsWhitelist;
});
}
//Mesage came from popup.js, whitelist this site
else if (message.message === "add site to whitelist")
{
//Obtain current hostname and whitelist it
var currentHost = message.site;
doNotBlockHosts[currentHost] = true;
browser.storage.sync.set({"nbsWhitelist":doNotBlockHosts});
}
//Message came from popup.js, remove whitelisted site
else if (message.message === "remove site from whitelist")
{
//Obtain current hostname and remove it
currentHost = message.site;
delete doNotBlockHosts[currentHost];
browser.storage.sync.set({"nbsWhitelist":doNotBlockHosts});
}
//HTTP request shield was turned on
else if (message.message === "turn request shield on")
{
//Hook up the listeners
browser.webRequest.onBeforeSendHeaders.addListener(
beforeSendHeadersListener,
{urls: [""]},
["blocking", "requestHeaders"]
);
if (typeof onResponseStartedListener === "function")
{
browser.webRequest.onResponseStarted.addListener(
onResponseStartedListener,
{urls: [""]},
["responseHeaders"]
);
}
}
//HTTP request shield was turned off
else if (message.message === "turn request shield off")
{
//Disconnect the listeners
browser.webRequest.onBeforeSendHeaders.removeListener(beforeSendHeadersListener);
if (typeof onResponseStartedListener === "function")
{
browser.webRequest.onResponseStarted.removeListener(onResponseStartedListener);
}
}
}