diff --git a/defaults/preferences/prefs.js b/defaults/preferences/prefs.js index 0a06b941882726c58e0f6157023e1c226ad02b75..cf3cabbdc7f2a1d107b7e93cad321150895adc0a 100644 --- a/defaults/preferences/prefs.js +++ b/defaults/preferences/prefs.js @@ -3,3 +3,4 @@ pref('extensions.jid1-BoFifL9Vbdl2zQ@jetpack.blockMissing', false); pref('extensions.jid1-BoFifL9Vbdl2zQ@jetpack.domainWhitelist', ''); pref('extensions.jid1-BoFifL9Vbdl2zQ@jetpack.amountInjected', 0); pref('extensions.jid1-BoFifL9Vbdl2zQ@jetpack.showReleaseNotes', true); +pref('extensions.jid1-BoFifL9Vbdl2zQ@jetpack.taintedDomainList', ''); diff --git a/lib/interceptor.js b/lib/interceptor.js index ea447f197467adb17264049558c3fdbc0b2e6d7d..0ae3d1054a85169cce4af25d137e902d8fc6cebb 100644 --- a/lib/interceptor.js +++ b/lib/interceptor.js @@ -21,15 +21,44 @@ var { Class } = require('sdk/core/heritage'); var { Unknown } = require('sdk/platform/xpcom'); var { Cc, Ci, Cr } = require('chrome'); +/** + * Gets and sets add-on specific preferences. + * @var {object} simplePreferences + */ +var simplePreferences = require('sdk/simple-prefs'); + //noinspection JSUnresolvedFunction var observerService = Cc['@mozilla.org/observer-service;1'] .getService(Ci.nsIObserverService); -var preferences = require('sdk/simple-prefs').prefs; - var requestAnalyzer = require('./request-analyzer'); var dataHandler = require('./data-handler'); +/** + * Constants + */ + +const VALUE_SEPARATOR = ';'; + +/** + * Variables + */ + +var preferences = require('sdk/simple-prefs').prefs; +var taintedDomains = {}; + +/** + * Initializations + */ + +_applyTaintPreference(); + +/** + * Event Handlers + */ + +simplePreferences.on('taintedDomainList', _applyTaintPreference); + /** * Interceptor Class */ @@ -69,16 +98,6 @@ var Interceptor = new Class({ // Remove referer header from request. httpChannel.setRequestHeader('Referer', null, false); - // Temporary fixes for reported edge-case issues with specific websites. - var initiatorDomain = - httpChannel.loadInfo && httpChannel.loadInfo.loadingDocument && httpChannel.loadInfo.loadingDocument.domain || - httpChannel.referrer && httpChannel.referrer.host; - - if (initiatorDomain === 'play.google.com' || initiatorDomain === 'passport.twitch.tv' || initiatorDomain === 'report-uri.io' - || initiatorDomain === 'minigames.mail.ru' || initiatorDomain === 'stefansundin.github.io') { - return this.handleMissingCandidate(httpChannel); - } - // Convert the original request URI to a local target. target = requestAnalyzer.getLocalTarget(httpChannel.URI.host, httpChannel.URI.path); @@ -95,6 +114,16 @@ var Interceptor = new Class({ return this.handleMissingCandidate(httpChannel); } + // Fix for reported edge-case issues with specific websites. + var initiatorDomain = + httpChannel.loadInfo && httpChannel.loadInfo.loadingDocument && httpChannel.loadInfo.loadingDocument.domain || + httpChannel.referrer && httpChannel.referrer.host; + + if (taintedDomains[initiatorDomain] === true) { + return this.handleMissingCandidate(httpChannel); + } + + // Redirect the HTTP channel to the the local destination. httpChannel.redirectTo(redirectionURI); //noinspection JSUnresolvedVariable @@ -114,6 +143,20 @@ var Interceptor = new Class({ } }); +/** + * Private Methods + */ + +function _applyTaintPreference() { + + taintedDomains = {}; + + //noinspection JSUnresolvedVariable + preferences.taintedDomainList.split(VALUE_SEPARATOR).forEach(function (domain) { + taintedDomains[domain] = true; + }); +} + /** * Exports */ diff --git a/lib/load-watcher.js b/lib/load-watcher.js new file mode 100644 index 0000000000000000000000000000000000000000..eae1beaf915733082c1b4bad5ee3bbaf4b551014 --- /dev/null +++ b/lib/load-watcher.js @@ -0,0 +1,175 @@ +/** + * Load Watcher + * Belongs to Decentraleyes. + * + * @author Thomas Rientjes + * @since 2016-02-04 + * @license MPL 2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +/** + * Imports + */ + +var { Class } = require('sdk/core/heritage'); +var { Unknown, Factory } = require('sdk/platform/xpcom'); +var { Cc, Ci, Cu } = require('chrome'); + +var xpcom = require('sdk/platform/xpcom'); + +/** + * Resource version mappings. + * @var {object} mappings + */ +var mappings = require('./mappings'); + +/** + * Gets and sets add-on specific preferences. + * @var {object} simplePreferences + */ +var simplePreferences = require('sdk/simple-prefs'); + +//noinspection JSUnresolvedFunction +var categoryManager = Cc['@mozilla.org/categorymanager;1'] + .getService(Ci.nsICategoryManager); + +/** + * Constants + */ + +const CONTRACT_ID = '@decentraleyes.org/load-watcher;1'; +const SCRIPT_CONTENT_TYPE = Ci.nsIContentPolicy.TYPE_SCRIPT; + +const SCRIPT_ELEMENT = Ci.nsIDOMHTMLScriptElement; +const HTML_DOCUMENT = Ci.nsIDOMHTMLDocument; + +const VALUE_SEPARATOR = ';'; + +/** + * Variables + */ + +var preferences = simplePreferences.prefs; +var taintedDomains = {}; + +/** + * Initializations + */ + +_applyTaintPreference(); + +/** + * Load Watcher Class + */ + +var LoadWatcher = new Class({ + + extends: Unknown, + interfaces: ['nsIContentPolicy'], + get wrappedJSObject() { return this }, + + register: function () { + + categoryManager.deleteCategoryEntry('content-policy', '@decentraleyes.org/load-watcher;1', false); + categoryManager.addCategoryEntry('content-policy', CONTRACT_ID, CONTRACT_ID, false, true); + }, + + shouldLoad: function (contentType, contentLocation, requestOrigin, node) { + + if (contentType == SCRIPT_CONTENT_TYPE && mappings[contentLocation.host]) { + + if (node instanceof SCRIPT_ELEMENT) { + + if (node.hasAttribute('crossorigin') || node.hasAttribute('integrity')) { + + // Add corresponding origin domain to the list of tainted domains. + this.saveTaintedDomain(requestOrigin.host); + } + + } else if (node instanceof HTML_DOCUMENT) { + + if (node.defaultView && node.defaultView.frameElement && node.defaultView.frameElement.tagName === 'IFRAME') { + + // Add corresponding origin domain to the list of tainted domains. + this.saveTaintedDomain(requestOrigin.host); + } + } + } + + // Accept the resource load request. + return Ci.nsIContentPolicy.ACCEPT; + }, + + saveTaintedDomain: function (taintedDomain) { + + if (taintedDomains[taintedDomain] !== true) { + + taintedDomains[taintedDomain] = true; + preferences.taintedDomainList = Object.keys(taintedDomains).join(VALUE_SEPARATOR); + } + } +}); + +/** + * Load Watcher Factory + */ + +var factory = Factory({ + + contract: CONTRACT_ID, + Component: LoadWatcher, + unregister: false +}); + +/** + * Unregister + */ + +var unload = require('sdk/system/unload'); + +unload.when(function () { + + function trueUnregister() { + + categoryManager.deleteCategoryEntry('content-policy', CONTRACT_ID, false); + + try { + xpcom.unregister(factory); + } catch (exception) { + Cu.reportError(exception); + } + } + + if ('dispatch' in Cu) { + Cu.dispatch(trueUnregister, trueUnregister); + } else { + Cu.import('resource://gre/modules/Services.jsm'); + Services.tm.mainThread.dispatch(trueUnregister, 0); + } +}); + +/** + * Private Methods + */ + +function _applyTaintPreference() { + + taintedDomains = {}; + + //noinspection JSUnresolvedVariable + preferences.taintedDomainList.split(VALUE_SEPARATOR).forEach(function (domain) { + taintedDomains[domain] = true; + }); +} + +/** + * Exports + */ + +module.exports = LoadWatcher; diff --git a/lib/main.js b/lib/main.js index 3df5b771edd3c47cbf3ce43aaa9d18d928abdfc4..c8f88e6bc691fd800b2aa1bd65280d6e3d75295d 100644 --- a/lib/main.js +++ b/lib/main.js @@ -18,6 +18,8 @@ */ var Interceptor = require('./interceptor'); +var LoadWatcher = require('./load-watcher'); + var preferences = require('sdk/simple-prefs').prefs; var tabs = require("sdk/tabs"); @@ -26,12 +28,14 @@ var tabs = require("sdk/tabs"); */ var interceptor = new Interceptor(); +var loadWatcher = new LoadWatcher(); // Executed as soon as the add-on is loaded. exports.main = function (options) { // Initialize add-on state. interceptor.register(); + loadWatcher.register(); if (preferences.showReleaseNotes && (options.loadReason === 'install' || options.loadReason === 'upgrade')) {