/* * * This file is part of Privacy Badger * Copyright (C) 2016 Electronic Frontier Foundation * * Derived from Adblock Plus * Copyright (C) 2006-2013 Eyeo GmbH * * Derived from Chameleon * Copyright (C) 2015 ghostwords * * Privacy Badger is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * Privacy Badger 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 Privacy Badger. If not, see . */ /* globals badger:false, log:false */ require.scopes.webrequest = (function () { /*********************** webrequest scope **/ let constants = require("constants"), getSurrogateURI = require("surrogates").getSurrogateURI, incognito = require("incognito"), utils = require("utils"); /************ Local Variables *****************/ let tempAllowlist = {}; /***************** Blocking Listener Functions **************/ /** * Event handling of http requests, main logic to collect data what to block * * @param {Object} details The event details * @returns {Object} Can cancel requests */ function onBeforeRequest(details) { let frame_id = details.frameId, tab_id = details.tabId, type = details.type, url = details.url; if (type == "main_frame") { let oldTabData = badger.getFrameData(tab_id), is_reload = oldTabData && oldTabData.url == url; forgetTab(tab_id, is_reload); badger.recordFrame(tab_id, frame_id, url); initializeAllowedWidgets(tab_id, badger.getFrameData(tab_id).host); return {}; } if (type == "sub_frame") { badger.recordFrame(tab_id, frame_id, url); } // Block ping requests sent by navigator.sendBeacon (see, #587) // tabId for pings are always -1 due to Chrome bugs #522124 and #522129 // Once these bugs are fixed, PB will treat pings as any other request if (type == "ping" && tab_id < 0) { return {cancel: true}; } if (_isTabChromeInternal(tab_id)) { return {}; } let tab_host = getHostForTab(tab_id); let request_host = window.extractHostFromURL(url); if (!utils.isThirdPartyDomain(request_host, tab_host)) { return {}; } let action = checkAction(tab_id, request_host, frame_id); if (!action) { return {}; } badger.logThirdPartyOriginOnTab(tab_id, request_host, action); if (!badger.isPrivacyBadgerEnabled(tab_host)) { return {}; } if (action != constants.BLOCK && action != constants.USER_BLOCK) { return {}; } if (type == 'script') { let surrogate = getSurrogateURI(url, request_host); if (surrogate) { return {redirectUrl: surrogate}; } } // notify the widget replacement content script chrome.tabs.sendMessage(tab_id, { replaceWidget: true, trackerDomain: request_host }); // if this is a heuristically- (not user-) blocked domain if (action == constants.BLOCK && incognito.learningEnabled(tab_id)) { // check for DNT policy asynchronously setTimeout(function () { badger.checkForDNTPolicy(request_host); }, 0); } if (type == 'sub_frame') { setTimeout(function () { hideBlockedFrame(tab_id, details.parentFrameId, url, request_host); }, 0); } return {cancel: true}; } /** * Filters outgoing cookies and referer * Injects DNT * * @param {Object} details Event details * @returns {Object} modified headers */ function onBeforeSendHeaders(details) { let frame_id = details.frameId, tab_id = details.tabId, type = details.type, url = details.url; if (_isTabChromeInternal(tab_id)) { // DNT policy requests: strip cookies if (type == "xmlhttprequest" && url.endsWith("/.well-known/dnt-policy.txt")) { // remove Cookie headers let newHeaders = []; for (let i = 0, count = details.requestHeaders.length; i < count; i++) { let header = details.requestHeaders[i]; if (header.name.toLowerCase() != "cookie") { newHeaders.push(header); } } return { requestHeaders: newHeaders }; } return {}; } let tab_host = getHostForTab(tab_id); let request_host = window.extractHostFromURL(url); if (!utils.isThirdPartyDomain(request_host, tab_host)) { if (badger.isPrivacyBadgerEnabled(tab_host)) { // Still sending Do Not Track even if HTTP and cookie blocking are disabled if (badger.isDNTSignalEnabled()) { details.requestHeaders.push({name: "DNT", value: "1"}, {name: "Sec-GPC", value: "1"}); } return {requestHeaders: details.requestHeaders}; } else { return {}; } } let action = checkAction(tab_id, request_host, frame_id); if (action) { badger.logThirdPartyOriginOnTab(tab_id, request_host, action); } if (!badger.isPrivacyBadgerEnabled(tab_host)) { return {}; } // handle cookieblocked requests if (action == constants.COOKIEBLOCK || action == constants.USER_COOKIEBLOCK) { let newHeaders; // GET requests: remove cookie headers, reduce referrer header to origin if (details.method == "GET") { newHeaders = details.requestHeaders.filter(header => { return (header.name.toLowerCase() != "cookie"); }).map(header => { if (header.name.toLowerCase() == "referer") { header.value = header.value.slice( 0, header.value.indexOf('/', header.value.indexOf('://') + 3) ) + '/'; } return header; }); // remove cookie and referrer headers otherwise } else { newHeaders = details.requestHeaders.filter(header => { return (header.name.toLowerCase() != "cookie" && header.name.toLowerCase() != "referer"); }); } // add DNT header if (badger.isDNTSignalEnabled()) { newHeaders.push({name: "DNT", value: "1"}, {name: "Sec-GPC", value: "1"}); } return {requestHeaders: newHeaders}; } // if we are here, we're looking at a third-party request // that's not yet blocked or cookieblocked if (badger.isDNTSignalEnabled()) { details.requestHeaders.push({name: "DNT", value: "1"}, {name: "Sec-GPC", value: "1"}); } return {requestHeaders: details.requestHeaders}; } /** * Filters incoming cookies out of the response header * * @param {Object} details The event details * @returns {Object} The new response headers */ function onHeadersReceived(details) { let tab_id = details.tabId, url = details.url; if (_isTabChromeInternal(tab_id)) { // DNT policy responses: strip cookies, reject redirects if (details.type == "xmlhttprequest" && url.endsWith("/.well-known/dnt-policy.txt")) { // if it's a redirect, cancel it if (details.statusCode >= 300 && details.statusCode < 400) { return { cancel: true }; } // remove Set-Cookie headers let headers = details.responseHeaders, newHeaders = []; for (let i = 0, count = headers.length; i < count; i++) { if (headers[i].name.toLowerCase() != "set-cookie") { newHeaders.push(headers[i]); } } return { responseHeaders: newHeaders }; } return {}; } let tab_host = getHostForTab(tab_id); let response_host = window.extractHostFromURL(url); if (!utils.isThirdPartyDomain(response_host, tab_host)) { return {}; } let action = checkAction(tab_id, response_host, details.frameId); if (!action) { return {}; } badger.logThirdPartyOriginOnTab(tab_id, response_host, action); if (!badger.isPrivacyBadgerEnabled(tab_host)) { return {}; } if (action == constants.COOKIEBLOCK || action == constants.USER_COOKIEBLOCK) { let newHeaders = details.responseHeaders.filter(function(header) { return (header.name.toLowerCase() != "set-cookie"); }); return {responseHeaders: newHeaders}; } } /*************** Non-blocking listener functions ***************/ /** * Event handler when a tab gets removed * * @param {Integer} tabId Id of the tab */ function onTabRemoved(tabId) { forgetTab(tabId); } /** * Update internal db on tabs when a tab gets replaced * due to prerendering or instant search. * * @param {Integer} addedTabId The new tab id that replaces * @param {Integer} removedTabId The tab id that gets removed */ function onTabReplaced(addedTabId, removedTabId) { forgetTab(removedTabId); // Update the badge of the added tab, which was probably used for prerendering. badger.updateBadge(addedTabId); } /** * We don't always get a "main_frame" details object in onBeforeRequest, * so we need a fallback for (re)initializing tabData. */ function onNavigate(details) { const tab_id = details.tabId, url = details.url; // main (top-level) frames only if (details.frameId !== 0) { return; } let oldTabData = badger.getFrameData(tab_id), is_reload = oldTabData && oldTabData.url == url; forgetTab(tab_id, is_reload); // forget but don't initialize on special browser/extension pages if (utils.isRestrictedUrl(url)) { return; } badger.recordFrame(tab_id, 0, url); let tab_host = badger.getFrameData(tab_id).host; initializeAllowedWidgets(tab_id, tab_host); // initialize tab data bookkeeping used by heuristicBlockingAccounting() // to avoid missing or misattributing learning // when there is no "main_frame" webRequest callback // (such as on Service Worker pages) // // see the tabOrigins TODO in heuristicblocking.js // as to why we don't just use tabData let base = window.getBaseDomain(tab_host); badger.heuristicBlocking.tabOrigins[tab_id] = base; badger.heuristicBlocking.tabUrls[tab_id] = url; } /******** Utility Functions **********/ /** * Messages collapser.js content script to hide blocked frames. */ function hideBlockedFrame(tab_id, parent_frame_id, frame_url, frame_host) { // don't hide if hiding is disabled if (!badger.getSettings().getItem('hideBlockedElements')) { return; } // don't hide widget frames if (badger.isWidgetReplacementEnabled()) { let exceptions = badger.getSettings().getItem('widgetReplacementExceptions'); for (let widget of badger.widgetList) { if (exceptions.includes(widget.name)) { continue; } for (let domain of widget.domains) { if (domain == frame_host) { return; } else if (domain[0] == "*") { // leading wildcard domain if (frame_host.endsWith(domain.slice(1))) { return; } } } } } // message content script chrome.tabs.sendMessage(tab_id, { hideFrame: true, url: frame_url }, { frameId: parent_frame_id }, function (response) { if (response) { // content script was ready and received our message return; } // content script was not ready if (chrome.runtime.lastError) { // ignore } // record frame_url and parent_frame_id // for when content script becomes ready let tabData = badger.tabData[tab_id]; if (!tabData.blockedFrameUrls.hasOwnProperty(parent_frame_id)) { tabData.blockedFrameUrls[parent_frame_id] = []; } tabData.blockedFrameUrls[parent_frame_id].push(frame_url); }); } /** * Gets the host name for a given tab id * @param {Integer} tabId chrome tab id * @return {String} the host name for the tab */ function getHostForTab(tabId) { let mainFrameIdx = 0; if (!badger.tabData[tabId]) { return ''; } // TODO what does this actually do? // meant to address https://github.com/EFForg/privacybadger/issues/136 if (_isTabAnExtension(tabId)) { // If the tab is an extension get the url of the first frame for its implied URL // since the url of frame 0 will be the hash of the extension key mainFrameIdx = Object.keys(badger.tabData[tabId].frames)[1] || 0; } let frameData = badger.getFrameData(tabId, mainFrameIdx); if (!frameData) { return ''; } return frameData.host; } /** * Record "supercookie" tracking * * @param {Integer} tab_id browser tab ID * @param {String} frame_url URL of the frame with supercookie */ function recordSupercookie(tab_id, frame_url) { const frame_host = window.extractHostFromURL(frame_url), page_host = badger.getFrameData(tab_id).host; if (!utils.isThirdPartyDomain(frame_host, page_host)) { // Only happens on the start page for google.com return; } badger.heuristicBlocking.updateTrackerPrevalence( frame_host, window.getBaseDomain(frame_host), window.getBaseDomain(page_host) ); } /** * Record canvas fingerprinting * * @param {Integer} tabId the tab ID * @param {Object} msg specific fingerprinting data */ function recordFingerprinting(tabId, msg) { // Abort if we failed to determine the originating script's URL // TODO find and fix where this happens if (!msg.scriptUrl) { return; } // Ignore first-party scripts let script_host = window.extractHostFromURL(msg.scriptUrl), document_host = badger.getFrameData(tabId).host; if (!utils.isThirdPartyDomain(script_host, document_host)) { return; } let CANVAS_WRITE = { fillText: true, strokeText: true }; let CANVAS_READ = { getImageData: true, toDataURL: true }; if (!badger.tabData[tabId].hasOwnProperty('fpData')) { badger.tabData[tabId].fpData = {}; } let script_origin = window.getBaseDomain(script_host); // Initialize script TLD-level data if (!badger.tabData[tabId].fpData.hasOwnProperty(script_origin)) { badger.tabData[tabId].fpData[script_origin] = { canvas: { fingerprinting: false, write: false } }; } let scriptData = badger.tabData[tabId].fpData[script_origin]; if (msg.extra.hasOwnProperty('canvas')) { if (scriptData.canvas.fingerprinting) { return; } // If this script already had a canvas write... if (scriptData.canvas.write) { // ...and if this is a canvas read... if (CANVAS_READ.hasOwnProperty(msg.prop)) { // ...and it got enough data... if (msg.extra.width > 16 && msg.extra.height > 16) { // ...we will classify it as fingerprinting scriptData.canvas.fingerprinting = true; log(script_host, 'caught fingerprinting on', document_host); // Mark this as a strike badger.heuristicBlocking.updateTrackerPrevalence( script_host, script_origin, window.getBaseDomain(document_host)); } } // This is a canvas write } else if (CANVAS_WRITE.hasOwnProperty(msg.prop)) { scriptData.canvas.write = true; } } } /** * Cleans up tab-specific data. * * @param {Integer} tab_id the ID of the tab * @param {Boolean} is_reload whether the page is simply being reloaded */ function forgetTab(tab_id, is_reload) { delete badger.tabData[tab_id]; if (!is_reload) { delete tempAllowlist[tab_id]; } } /** * Determines the action to take on a specific FQDN. * * @param {Integer} tabId The relevant tab * @param {String} requestHost The FQDN * @param {Integer} frameId The id of the frame * @returns {(String|Boolean)} false or the action to take */ function checkAction(tabId, requestHost, frameId) { // Ignore requests from temporarily unblocked widgets. // Someone clicked the widget, so let it load. if (allowedOnTab(tabId, requestHost, frameId)) { return false; } // Ignore requests from private domains. if (window.isPrivateDomain(requestHost)) { return false; } return badger.storage.getBestAction(requestHost); } /** * Checks if the tab is chrome internal * * @param {Integer} tabId Id of the tab to test * @returns {boolean} Returns true if the tab is chrome internal * @private */ function _isTabChromeInternal(tabId) { if (tabId < 0) { return true; } let frameData = badger.getFrameData(tabId); if (!frameData || !frameData.url.startsWith("http")) { return true; } return false; } /** * Checks if the tab is a chrome-extension tab * * @param {Integer} tabId Id of the tab to test * @returns {boolean} Returns true if the tab is from a chrome-extension * @private */ function _isTabAnExtension(tabId) { let frameData = badger.getFrameData(tabId); return (frameData && ( frameData.url.startsWith("chrome-extension://") || frameData.url.startsWith("moz-extension://") )); } /** * Provides the widget replacing content script with list of widgets to replace. * * @param {Integer} tab_id the ID of the tab we're replacing widgets in * * @returns {Object} dict containing the complete list of widgets * as well as a mapping to indicate which ones should be replaced */ let getWidgetList = (function () { // cached translations let translations; // inputs to chrome.i18n.getMessage() const widgetTranslations = [ { key: "social_tooltip_pb_has_replaced", placeholders: ["XXX"] }, { key: "widget_placeholder_pb_has_replaced", placeholders: ["XXX"] }, { key: "allow_once" }, { key: "allow_on_site" }, ]; return function (tab_id) { // an object with keys set to widget names that should be replaced let widgetsToReplace = {}, widgetList = [], tabData = badger.tabData[tab_id], tabOrigins = tabData && tabData.origins && Object.keys(tabData.origins), exceptions = badger.getSettings().getItem('widgetReplacementExceptions'); // optimize translation lookups by doing them just once, // the first time they are needed if (!translations) { translations = widgetTranslations.reduce((memo, data) => { memo[data.key] = chrome.i18n.getMessage(data.key, data.placeholders); return memo; }, {}); // TODO duplicated in src/lib/i18n.js const RTL_LOCALES = ['ar', 'he', 'fa'], UI_LOCALE = chrome.i18n.getMessage('@@ui_locale'); translations.rtl = RTL_LOCALES.indexOf(UI_LOCALE) > -1; } for (let widget of badger.widgetList) { // replace only if the widget is not on the 'do not replace' list // also don't send widget data used later for dynamic replacement if (exceptions.includes(widget.name)) { continue; } widgetList.push(widget); // replace only if at least one of the associated domains was blocked if (!tabOrigins || !tabOrigins.length) { continue; } let replace = widget.domains.some(domain => { // leading wildcard domain if (domain[0] == "*") { domain = domain.slice(1); // get all domains in tabData.origins that end with this domain let matches = tabOrigins.filter(origin => { return origin.endsWith(domain); }); // do we have any matches and are they all blocked? return matches.length && matches.every(origin => { const action = tabData.origins[origin]; return ( action == constants.BLOCK || action == constants.USER_BLOCK ); }); } // regular, non-leading wildcard domain if (!tabData.origins.hasOwnProperty(domain)) { return false; } const action = tabData.origins[domain]; return ( action == constants.BLOCK || action == constants.USER_BLOCK ); }); if (replace) { widgetsToReplace[widget.name] = true; } } return { translations, widgetList, widgetsToReplace }; }; }()); /** * Checks if given request FQDN is temporarily unblocked on a tab. * * The request is allowed if any of the following is true: * * - 1a) Request FQDN matches an entry on the exception list for the tab * - 1b) Request FQDN ends with a wildcard entry from the exception list * - 2a) Request is from a subframe whose FQDN matches an entry on the list * - 2b) Same but subframe's FQDN ends with a wildcard entry * * @param {Integer} tab_id the ID of the tab to check * @param {String} request_host the request FQDN to check * @param {Integer} frame_id the frame ID to check * * @returns {Boolean} true if FQDN is on the temporary allow list */ function allowedOnTab(tab_id, request_host, frame_id) { if (!tempAllowlist.hasOwnProperty(tab_id)) { return false; } let exceptions = tempAllowlist[tab_id]; for (let exception of exceptions) { if (exception == request_host) { return true; // 1a // leading wildcard } else if (exception[0] == "*") { if (request_host.endsWith(exception.slice(1))) { return true; // 1b } } } if (!frame_id) { return false; } let frameData = badger.getFrameData(tab_id, frame_id); if (!frameData || !frameData.host) { return false; } let frame_host = frameData.host; for (let exception of exceptions) { if (exception == frame_host) { return true; // 2a // leading wildcard } else if (exception[0] == "*") { if (frame_host.endsWith(exception.slice(1))) { return true; // 2b } } } return false; } /** * @returns {Array|Boolean} the list of associated domains or false */ function getWidgetDomains(widget_name) { let widgetData = badger.widgetList.find( widget => widget.name == widget_name); if (!widgetData || !widgetData.hasOwnProperty("replacementButton") || !widgetData.replacementButton.unblockDomains) { return false; } return widgetData.replacementButton.unblockDomains; } /** * Marks a set of (widget) domains to be (temporarily) allowed on a tab. * * @param {Integer} tab_id the ID of the tab * @param {Array} domains the domains */ function allowOnTab(tab_id, domains) { if (!tempAllowlist.hasOwnProperty(tab_id)) { tempAllowlist[tab_id] = []; } for (let domain of domains) { if (!tempAllowlist[tab_id].includes(domain)) { tempAllowlist[tab_id].push(domain); } } } /** * Called upon navigation to prepopulate the temporary allowlist * with domains for widgets marked as always allowed on a given site. */ function initializeAllowedWidgets(tab_id, tab_host) { let allowedWidgets = badger.getSettings().getItem('widgetSiteAllowlist'); if (allowedWidgets.hasOwnProperty(tab_host)) { for (let widget_name of allowedWidgets[tab_host]) { let widgetDomains = getWidgetDomains(widget_name); if (widgetDomains) { allowOnTab(tab_id, widgetDomains); } } } } // NOTE: sender.tab is available for content script (not popup) messages only function dispatcher(request, sender, sendResponse) { // messages from content scripts are to be treated with greater caution: // https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/0ei-UCHNm34/lDaXwQhzBAAJ if (!sender.url.startsWith(chrome.runtime.getURL(""))) { // reject unless it's a known content script message const KNOWN_CONTENT_SCRIPT_MESSAGES = [ "allowWidgetOnSite", "checkDNT", "checkEnabled", "checkLocation", "checkWidgetReplacementEnabled", "detectFingerprinting", "fpReport", "getBlockedFrameUrls", "getReplacementButton", "inspectLocalStorage", "supercookieReport", "unblockWidget", ]; if (!KNOWN_CONTENT_SCRIPT_MESSAGES.includes(request.type)) { console.error("Rejected unknown message %o from %s", request, sender.url); return sendResponse(); } } switch (request.type) { case "checkEnabled": { sendResponse(badger.isPrivacyBadgerEnabled( window.extractHostFromURL(sender.tab.url) )); break; } case "checkLocation": { if (!badger.isPrivacyBadgerEnabled(window.extractHostFromURL(sender.tab.url))) { return sendResponse(); } // Ignore requests from internal Chrome tabs. if (_isTabChromeInternal(sender.tab.id)) { return sendResponse(); } let frame_host = window.extractHostFromURL(request.frameUrl), tab_host = window.extractHostFromURL(sender.tab.url); // Ignore requests that aren't from a third party. if (!frame_host || !utils.isThirdPartyDomain(frame_host, tab_host)) { return sendResponse(); } let action = checkAction(sender.tab.id, frame_host); sendResponse(action == constants.COOKIEBLOCK || action == constants.USER_COOKIEBLOCK); break; } case "getBlockedFrameUrls": { if (!badger.isPrivacyBadgerEnabled(window.extractHostFromURL(sender.tab.url))) { return sendResponse(); } let tab_id = sender.tab.id, frame_id = sender.frameId, tabData = badger.tabData.hasOwnProperty(tab_id) && badger.tabData[tab_id], blockedFrameUrls = tabData && tabData.blockedFrameUrls.hasOwnProperty(frame_id) && tabData.blockedFrameUrls[frame_id]; sendResponse(blockedFrameUrls); break; } case "unblockWidget": { let widgetDomains = getWidgetDomains(request.widgetName); if (!widgetDomains) { return sendResponse(); } allowOnTab(sender.tab.id, widgetDomains); sendResponse(); break; } case "allowWidgetOnSite": { // record that we always want to activate this widget on this site let tab_host = window.extractHostFromURL(sender.tab.url), allowedWidgets = badger.getSettings().getItem('widgetSiteAllowlist'); if (!allowedWidgets.hasOwnProperty(tab_host)) { allowedWidgets[tab_host] = []; } if (!allowedWidgets[tab_host].includes(request.widgetName)) { allowedWidgets[tab_host].push(request.widgetName); badger.getSettings().setItem('widgetSiteAllowlist', allowedWidgets); } sendResponse(); break; } case "getReplacementButton": { let widgetData = badger.widgetList.find( widget => widget.name == request.widgetName); if (!widgetData || !widgetData.hasOwnProperty("replacementButton") || !widgetData.replacementButton.imagePath) { return sendResponse(); } let button_path = chrome.runtime.getURL( "skin/socialwidgets/" + widgetData.replacementButton.imagePath); let image_type = button_path.slice(button_path.lastIndexOf('.') + 1); let xhrOptions = {}; if (image_type != "svg") { xhrOptions.responseType = "arraybuffer"; } // fetch replacement button image data utils.xhrRequest(button_path, function (err, response) { // one data URI for SVGs if (image_type == "svg") { return sendResponse('data:image/svg+xml;charset=utf-8,' + encodeURIComponent(response)); } // another data URI for all other image formats sendResponse( 'data:image/' + image_type + ';base64,' + utils.arrayBufferToBase64(response) ); }, "GET", xhrOptions); // indicate this is an async response to chrome.runtime.onMessage return true; } case "fpReport": { if (Array.isArray(request.data)) { request.data.forEach(function (msg) { recordFingerprinting(sender.tab.id, msg); }); } else { recordFingerprinting(sender.tab.id, request.data); } break; } case "supercookieReport": { if (request.frameUrl && badger.hasSupercookie(request.data)) { recordSupercookie(sender.tab.id, request.frameUrl); } break; } case "inspectLocalStorage": { let tab_host = window.extractHostFromURL(sender.tab.url), frame_host = window.extractHostFromURL(request.frameUrl); sendResponse(frame_host && badger.isLearningEnabled(sender.tab.id) && badger.isPrivacyBadgerEnabled(tab_host) && utils.isThirdPartyDomain(frame_host, tab_host)); break; } case "detectFingerprinting": { let tab_host = window.extractHostFromURL(sender.tab.url); sendResponse( badger.isLearningEnabled(sender.tab.id) && badger.isPrivacyBadgerEnabled(tab_host)); break; } case "checkWidgetReplacementEnabled": { let response = false, tab_host = window.extractHostFromURL(sender.tab.url); if (badger.isPrivacyBadgerEnabled(tab_host) && badger.isWidgetReplacementEnabled()) { response = getWidgetList(sender.tab.id); } sendResponse(response); break; } case "getPopupData": { let tab_id = request.tabId; if (!badger.tabData.hasOwnProperty(tab_id)) { sendResponse({ criticalError: badger.criticalError, noTabData: true, seenComic: true, }); break; } let tab_host = window.extractHostFromURL(request.tabUrl), origins = badger.tabData[tab_id].origins, cookieblocked = {}; for (let origin in origins) { // see if origin would be cookieblocked if not for user override if (badger.storage.wouldGetCookieblocked(origin)) { cookieblocked[origin] = true; } } sendResponse({ cookieblocked, criticalError: badger.criticalError, enabled: badger.isPrivacyBadgerEnabled(tab_host), errorText: badger.tabData[tab_id].errorText, learnLocally: badger.getSettings().getItem("learnLocally"), noTabData: false, origins, seenComic: badger.getSettings().getItem("seenComic"), showLearningPrompt: badger.getPrivateSettings().getItem("showLearningPrompt"), showNonTrackingDomains: badger.getSettings().getItem("showNonTrackingDomains"), tabHost: tab_host, tabId: tab_id, tabUrl: request.tabUrl, trackerCount: badger.getTrackerCount(tab_id) }); break; } case "getOptionsData": { let origins = badger.storage.getTrackingDomains(); let cookieblocked = {}; for (let origin in origins) { // see if origin would be cookieblocked if not for user override if (badger.storage.wouldGetCookieblocked(origin)) { cookieblocked[origin] = true; } } sendResponse({ cookieblocked, isWidgetReplacementEnabled: badger.isWidgetReplacementEnabled(), origins, settings: badger.getSettings().getItemClones(), webRTCAvailable: badger.webRTCAvailable, widgets: badger.widgetList.map(widget => widget.name), }); break; } case "resetData": { badger.storage.clearTrackerData(); badger.loadSeedData(err => { if (err) { console.error(err); } badger.blockWidgetDomains(); sendResponse(); }); // indicate this is an async response to chrome.runtime.onMessage return true; } case "removeAllData": { badger.storage.clearTrackerData(); sendResponse(); break; } case "seenComic": { badger.getSettings().setItem("seenComic", true); sendResponse(); break; } case "seenLearningPrompt": { badger.getPrivateSettings().setItem("showLearningPrompt", false); sendResponse(); break; } case "activateOnSite": { badger.enablePrivacyBadgerForOrigin(request.tabHost); badger.updateIcon(request.tabId, request.tabUrl); sendResponse(); break; } case "deactivateOnSite": { badger.disablePrivacyBadgerForOrigin(request.tabHost); badger.updateIcon(request.tabId, request.tabUrl); sendResponse(); break; } case "revertDomainControl": { badger.storage.revertUserAction(request.origin); sendResponse({ origins: badger.storage.getTrackingDomains() }); break; } case "downloadCloud": { chrome.storage.sync.get("disabledSites", function (store) { if (chrome.runtime.lastError) { sendResponse({success: false, message: chrome.runtime.lastError.message}); } else if (store.hasOwnProperty("disabledSites")) { let disabledSites = _.union( badger.getDisabledSites(), store.disabledSites ); badger.getSettings().setItem("disabledSites", disabledSites); sendResponse({ success: true, disabledSites }); } else { sendResponse({ success: false, message: chrome.i18n.getMessage("download_cloud_no_data") }); } }); // indicate this is an async response to chrome.runtime.onMessage return true; } case "uploadCloud": { let obj = {}; obj.disabledSites = badger.getDisabledSites(); chrome.storage.sync.set(obj, function () { if (chrome.runtime.lastError) { sendResponse({success: false, message: chrome.runtime.lastError.message}); } else { sendResponse({success: true}); } }); // indicate this is an async response to chrome.runtime.onMessage return true; } case "savePopupToggle": { let domain = request.origin, action = request.action; badger.saveAction(action, domain); // update cached tab data so that a reopened popup displays correct state badger.tabData[request.tabId].origins[domain] = "user_" + action; break; } case "saveOptionsToggle": { // called when the user manually sets a slider on the options page badger.saveAction(request.action, request.origin); sendResponse({ origins: badger.storage.getTrackingDomains() }); break; } case "mergeUserData": { // called when a user uploads data exported from another Badger instance badger.mergeUserData(request.data); badger.blockWidgetDomains(); sendResponse({ disabledSites: badger.getDisabledSites(), origins: badger.storage.getTrackingDomains(), }); break; } case "updateSettings": { const settings = badger.getSettings(); for (let key in request.data) { if (badger.defaultSettings.hasOwnProperty(key)) { settings.setItem(key, request.data[key]); } else { console.error("Unknown Badger setting:", key); } } sendResponse(); break; } case "updateBadge": { let tab_id = request.tab_id; badger.updateBadge(tab_id); sendResponse(); break; } case "disablePrivacyBadgerForOrigin": { badger.disablePrivacyBadgerForOrigin(request.domain); sendResponse({ disabledSites: badger.getDisabledSites() }); break; } case "enablePrivacyBadgerForOriginList": { request.domains.forEach(function (domain) { badger.enablePrivacyBadgerForOrigin(domain); }); sendResponse({ disabledSites: badger.getDisabledSites() }); break; } case "removeOrigin": { badger.storage.getStore("snitch_map").deleteItem(request.origin); badger.storage.getStore("action_map").deleteItem(request.origin); sendResponse({ origins: badger.storage.getTrackingDomains() }); break; } case "saveErrorText": { let activeTab = badger.tabData[request.tabId]; activeTab.errorText = request.errorText; break; } case "removeErrorText": { let activeTab = badger.tabData[request.tabId]; delete activeTab.errorText; break; } case "checkDNT": { // called from contentscripts/dnt.js to check if we should enable it sendResponse( badger.isDNTSignalEnabled() && badger.isPrivacyBadgerEnabled( window.extractHostFromURL(sender.tab.url) ) ); break; } } } /*************** Event Listeners *********************/ function startListeners() { chrome.webNavigation.onBeforeNavigate.addListener(onNavigate); chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); let extraInfoSpec = ['requestHeaders', 'blocking']; if (chrome.webRequest.OnBeforeSendHeadersOptions.hasOwnProperty('EXTRA_HEADERS')) { extraInfoSpec.push('extraHeaders'); } chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {urls: ["http://*/*", "https://*/*"]}, extraInfoSpec); extraInfoSpec = ['responseHeaders', 'blocking']; if (chrome.webRequest.OnHeadersReceivedOptions.hasOwnProperty('EXTRA_HEADERS')) { extraInfoSpec.push('extraHeaders'); } chrome.webRequest.onHeadersReceived.addListener(onHeadersReceived, {urls: [""]}, extraInfoSpec); chrome.tabs.onRemoved.addListener(onTabRemoved); chrome.tabs.onReplaced.addListener(onTabReplaced); chrome.runtime.onMessage.addListener(dispatcher); } /************************************** exports */ let exports = { startListeners }; return exports; /************************************** exports */ })();