diff options
Diffstat (limited to 'data/extensions/https-everywhere-eff@eff.org/components/https-everywhere.js')
-rw-r--r-- | data/extensions/https-everywhere-eff@eff.org/components/https-everywhere.js | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/data/extensions/https-everywhere-eff@eff.org/components/https-everywhere.js b/data/extensions/https-everywhere-eff@eff.org/components/https-everywhere.js new file mode 100644 index 0000000..5cd2d74 --- /dev/null +++ b/data/extensions/https-everywhere-eff@eff.org/components/https-everywhere.js @@ -0,0 +1,874 @@ +// LOG LEVELS +let VERB=1; +let DBUG=2; +let INFO=3; +let NOTE=4; +let WARN=5; + +// PREFERENCE BRANCHES +let PREFBRANCH_ROOT=0; +let PREFBRANCH_RULE_TOGGLE=1; +let PREFBRANCH_NONE=2; + +// maps domain patterns (with at most one wildcard) to RuleSets +let https_domains = {}; +// URLs we've given up on rewriting because of redirection loops +let https_everywhere_blacklist = {}; +// domains for which there is at least one blacklisted URL +let https_blacklist_domains = {}; + +const CI = Components.interfaces; +const CC = Components.classes; +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); + +const SERVICE_CTRID = "@eff.org/https-everywhere;1"; +const SERVICE_ID=Components.ID("{32c165b4-fe5e-4964-9250-603c410631b4}"); +const SERVICE_NAME = "Encrypts your communications with a number of major websites"; + +const LLVAR = "LogLevel"; + +const IOS = CC["@mozilla.org/network/io-service;1"].getService(CI.nsIIOService); +const OS = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService); +const LOADER = CC["@mozilla.org/moz/jssubscript-loader;1"].getService(CI.mozIJSSubScriptLoader); +const _INCLUDED = {}; + +// NoScript uses this blob to include js constructs that stored in the chrome/ +// directory, but are not attached to the Firefox UI (normally, js located +// there is attached to an Overlay and therefore is part of the UI). + +// Reasons for this: things in components/ directory cannot be split into +// separate files; things in chrome/ can be + +const INCLUDE = function(name) { + if (arguments.length > 1) + for (var j = 0, len = arguments.length; j < len; j++) + INCLUDE(arguments[j]); + else if (!_INCLUDED[name]) { + // we used to try/catch here, but that was less useful because it didn't + // produce line numbers for syntax errors + LOADER.loadSubScript("chrome://https-everywhere/content/code/" + + name + ".js"); + _INCLUDED[name] = true; + } +}; + +const WP_STATE_START = CI.nsIWebProgressListener.STATE_START; +const WP_STATE_STOP = CI.nsIWebProgressListener.STATE_STOP; +const WP_STATE_DOC = CI.nsIWebProgressListener.STATE_IS_DOCUMENT; +const WP_STATE_START_DOC = WP_STATE_START | WP_STATE_DOC; +const WP_STATE_RESTORING = CI.nsIWebProgressListener.STATE_RESTORING; + +const LF_VALIDATE_ALWAYS = CI.nsIRequest.VALIDATE_ALWAYS; +const LF_LOAD_BYPASS_ALL_CACHES = CI.nsIRequest.LOAD_BYPASS_CACHE | CI.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE; + +const NS_OK = 0; +const NS_BINDING_ABORTED = 0x804b0002; +const NS_BINDING_REDIRECTED = 0x804b0003; +const NS_ERROR_UNKNOWN_HOST = 0x804b001e; +const NS_ERROR_REDIRECT_LOOP = 0x804b001f; +const NS_ERROR_CONNECTION_REFUSED = 0x804b000e; +const NS_ERROR_NOT_AVAILABLE = 0x804b0111; + +const LOG_CONTENT_BLOCK = 1; +const LOG_CONTENT_CALL = 2; +const LOG_CONTENT_INTERCEPT = 4; +const LOG_CHROME_WIN = 8; +const LOG_XSS_FILTER = 16; +const LOG_INJECTION_CHECK = 32; +const LOG_DOM = 64; +const LOG_JS = 128; +const LOG_LEAKS = 1024; +const LOG_SNIFF = 2048; +const LOG_CLEARCLICK = 4096; + +const HTML_NS = "http://www.w3.org/1999/xhtml"; + +const WHERE_UNTRUSTED = 1; +const WHERE_TRUSTED = 2; +const ANYWHERE = 3; + +const N_COHORTS = 1000; + +const DUMMY_OBJ = {}; +DUMMY_OBJ.wrappedJSObject = DUMMY_OBJ; +const DUMMY_FUNC = function() {}; +const DUMMY_ARRAY = []; + +const EARLY_VERSION_CHECK = !("nsISessionStore" in CI && typeof(/ /) === "object"); + +// This is probably obsolete since the switch to the channel.redirectTo API +const OBSERVER_TOPIC_URI_REWRITE = "https-everywhere-uri-rewrite"; + +function xpcom_checkInterfaces(iid,iids,ex) { + for (var j = iids.length; j-- >0;) { + if (iid.equals(iids[j])) return true; + } + throw ex; +} + +INCLUDE('IOUtil', 'HTTPSRules', 'HTTPS', 'ApplicableList'); + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function HTTPSEverywhere() { + + // Set up logging in each component: + HTTPS.log = HTTPSRules.log = RuleWriter.log = this.log = https_everywhereLog; + + this.expandoMap = new WeakMap(); + + this.log = https_everywhereLog; + this.wrappedJSObject = this; + this.https_rules = HTTPSRules; + this.rw = RuleWriter; // currently used for some file IO helpers, though that + // should probably be refactored + this.INCLUDE=INCLUDE; + this.ApplicableList = ApplicableList; + this.browser_initialised = false; // the browser is completely loaded + + + this.prefs = this.get_prefs(); + this.rule_toggle_prefs = this.get_prefs(PREFBRANCH_RULE_TOGGLE); + + this.httpNowhereEnabled = this.prefs.getBoolPref("http_nowhere.enabled"); + this.isMobile = this.doMobileCheck(); + + // We need to use observers instead of categories for FF3.0 for these: + // https://developer.mozilla.org/en/Observer_Notifications + // https://developer.mozilla.org/en/nsIObserverService. + // https://developer.mozilla.org/en/nsIObserver + // We also use the observer service to let other extensions know about URIs + // we rewrite. + this.obsService = CC["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + + if (this.prefs.getBoolPref("globalEnabled")) { + this.obsService.addObserver(this, "profile-before-change", false); + this.obsService.addObserver(this, "profile-after-change", false); + this.obsService.addObserver(this, "sessionstore-windows-restored", false); + this.obsService.addObserver(this, "browser:purge-session-history", false); + } else { + // Need this to initialize FF for Android UI even when HTTPS-E is off + if (this.isMobile) { + this.obsService.addObserver(this, "sessionstore-windows-restored", false); + } + } + + var pref_service = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranchInternal); + var branch = pref_service.QueryInterface(Components.interfaces.nsIPrefBranchInternal); + + branch.addObserver("extensions.https_everywhere.enable_mixed_rulesets", + this, false); + branch.addObserver("security.mixed_content.block_active_content", + this, false); + + return; +} + + +/* +In recent versions of Firefox and HTTPS Everywhere, the call stack for performing an HTTP -> HTTPS rewrite looks like this: + +1. HTTPSEverywhere.observe() gets a callback with the "http-on-modify-request" topic, and the channel as a subject + +1. HTTPSEverywhere.shouldIgnoreURI() checks for very quick reasons to ignore a +request, such as redirection loops, non-HTTP[S] URIs, and OCSP + + 2. HTTPS.replaceChannel() + + 3. HTTPSRules.rewrittenURI() + + 4. HTTPSRules.potentiallyApplicableRulesets uses <target host=""> elements to identify relevant rulesets + + foreach RuleSet: + + 4. RuleSet.transformURI() + + 5. RuleSet.apply() does the tests and rewrites with RegExps, returning a string + + 4. RuleSet.transformURI() makes a new uri object for the destination string, if required + + 2. HTTPS.replaceChannel() calls channel.redirectTo() if a redirect is needed + + +In addition, the following other important tasks happen along the way: + +HTTPSEverywhere.observe() finds a reference to the ApplicableList or alist that represents the toolbar context menu + +HTTPS.replaceChannel() notices redirect loops (and used to do much more complex XPCOM API work in the NoScript-based past) + +HTTPSRules.rewrittenURI() works around weird URI types like about: and http://user:pass@example.com/ + and notifies the alist of what it should display for each ruleset + +*/ + +// This defines for Mozilla what stuff HTTPSEverywhere will implement. + +// ChannelEventSink used to be necessary in order to handle redirects (eg +// HTTP redirects) correctly. It may now be obsolete? XXX + +HTTPSEverywhere.prototype = { + prefs: null, + // properties required for XPCOM registration: + classDescription: SERVICE_NAME, + classID: SERVICE_ID, + contractID: SERVICE_CTRID, + + _xpcom_factory: { + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + if (!HTTPSEverywhere.instance) + HTTPSEverywhere.instance = new HTTPSEverywhere(); + return HTTPSEverywhere.instance.QueryInterface(iid); + }, + + QueryInterface: XPCOMUtils.generateQI( + [ Components.interfaces.nsISupports, + Components.interfaces.nsIModule, + Components.interfaces.nsIFactory ]) + }, + + // [optional] an array of categories to register this component in. + _xpcom_categories: [ + { + category: "app-startup", + } + ], + + // QueryInterface implementation, e.g. using the generateQI helper + QueryInterface: XPCOMUtils.generateQI( + [ Components.interfaces.nsIObserver, + Components.interfaces.nsISupports, + Components.interfaces.nsISupportsWeakReference, + Components.interfaces.nsIChannelEventSink ]), + + wrappedJSObject: null, // Initialized by constructor + + getWeakReference: function () { + return Components.utils.getWeakReference(this); + }, + + // An "expando" is an attribute glued onto something. From NoScript. + getExpando: function(browser, key) { + let obj = this.expandoMap.get(browser); + if (!obj) { + if (browser.currentURI) { + this.log(NOTE, "No expando for " + browser.currentURI.spec); + } + return null; + } + return obj[key]; + }, + + setExpando: function(browser, key, value) { + if (!this.expandoMap.has(browser)) { + this.expandoMap.set(browser, {}); + } + let obj = this.expandoMap.get(browser); + obj[key] = value; + }, + + // We use resetApplicableList to make a fresh list of rulesets that could have + // applied to the content in the current page (the "applicable list" is used + // for the context menu in the UI). This will be appended to as various + // content is embedded / requested by JavaScript. + resetApplicableList: function(browser) { + if (!this.prefs.getBoolPref("globalEnabled")) { + return; + } + try { + this.newApplicableListForBrowser(browser); + } catch (e) { + this.log(WARN, "Couldn't make applicable list"+e); + } + }, + + // Given an nsIChannel (essentially, a container for an HTTP or similar + // resource request), try to find the relevant tab if there is one. + // Specifically, find the XUL <browser> element for that tab. Note + // there are multiple meanings for the word 'browser' in Firefox, described at: + // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser + // We're looking for this one: + // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/browser + // Also note some requests, like Safe Browsing requests, will have no + // associated tab. + getBrowserForChannel: function(channel) { + let loadContext, topFrameElement, associatedWindow; + let spec = channel.URI.spec; + try { + loadContext = channel.notificationCallbacks.getInterface(CI.nsILoadContext); + } catch(e) { + } + + if (!loadContext) { + try { + loadContext = channel.loadGroup.notificationCallbacks + .getInterface(CI.nsILoadContext); + } catch(e) { + // Lots of requests have no notificationCallbacks, mostly background + // ones like OCSP checks or smart browsing fetches. + this.log(DBUG, "getBrowserForChannel: no loadContext for " + spec); + return null; + } + } + + if (loadContext) { + topFrameElement = loadContext.topFrameElement; + try { + // If loadContext is an nsDocShell, associatedWindow is present. + // Otherwise, if it's just a LoadContext, accessing it will throw + // NS_ERROR_UNEXPECTED. + associatedWindow = loadContext.associatedWindow; + } catch (e) { + } + } + + // On e10s (multiprocess, aka electrolysis) Firefox, + // loadContext.topFrameElement gives us a reference to the XUL <browser> + // element we need. However, on non-e10s Firefox, topFrameElement is null. + if (topFrameElement) { + return topFrameElement; + } else if (associatedWindow) { + // For non-e10s Firefox, get the XUL <browser> element using this rather + // magical / opaque code cribbed from + // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser#Getting_the_browser_that_fires_the_http-on-modify-request_notification_(example_code_updated_for_loadContext) + + // this is the HTML DOM window of the page that just loaded + var contentWindow = loadContext.associatedWindow; + // aDOMWindow this is the firefox window holding the tab + var aDOMWindow = contentWindow.top.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + // this is the gBrowser object of the firefox window this tab is in + var gBrowser = aDOMWindow.gBrowser; + if (gBrowser && gBrowser._getTabForContentWindow) { + var aTab = gBrowser._getTabForContentWindow(contentWindow.top); + // this is the clickable tab xul element, the one found in the tab strip + // of the firefox window, aTab.linkedBrowser is same as browser var above + // this is the browser within the tab + if (aTab) { + return aTab.linkedBrowser; + } else { + this.log(NOTE, "getBrowserForChannel: aTab was null for " + spec); + return null; + } + } else if (aDOMWindow.BrowserApp) { + // gBrowser is unavailable in Firefox for Android, and in some desktop + // contexts, like the fetches for new tab tiles (which have an + // associatedWindow, but no gBrowser)? + // If available, try using the BrowserApp API: + // https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/BrowserApp + // TODO: We don't get the toolbar icon on android. Probably need to fix + // the gBrowser reference in toolbar_button.js. + // Also TODO: Where are these log messages going? They don't show up in + // remote debug console. + var mTab = aDOMWindow.BrowserApp.getTabForWindow(contentWindow.top); + if (mTab) { + return mTab.browser; + } else { + this.log(WARN, "getBrowserForChannel: mTab was null for " + spec); + return null; + } + } else { + this.log(INFO, "getBrowserForChannel: No gBrowser and no BrowserApp for " + spec); + return null; + } + } else { + this.log(NOTE, "getBrowserForChannel: No loadContext for " + spec); + return null; + } + }, + + // the lists get made when the urlbar is loading something new, but they + // need to be appended to with reference only to the channel + getApplicableListForChannel: function(channel) { + var browser = this.getBrowserForChannel(channel); + return this.getApplicableListForBrowser(browser); + }, + + newApplicableListForBrowser: function(browser) { + if (!browser) { + this.log(WARN, "Get alist without browser"); + return null; + } + var alist = new ApplicableList(this.log, browser.currentURI); + this.setExpando(browser,"applicable_rules",alist); + return alist; + }, + + getApplicableListForBrowser: function(browser) { + if (!browser) { + //this.log(WARN, "Get alist without browser"); + return null; + } + var alist= this.getExpando(browser,"applicable_rules"); + if (alist) { + return alist; + } else { + alist = new ApplicableList(this.log, browser.currentURI); + this.setExpando(browser,"applicable_rules",alist); + } + return alist; + }, + + // These are the highest level heuristics for figuring out whether + // we should consider rewriting a URI. Everything here should be simple + // and avoid dependence on the ruleset library + shouldIgnoreURI: function(channel, alist) { + var uri = channel.URI; + // Ignore all non-http(s) requests? + if (!(uri.schemeIs("http") || uri.schemeIs("https"))) { return true; } + + // If HTTP Nowhere is enabled, skip the rest of the shouldIgnoreURI checks + if (this.httpNowhereEnabled) { + return false; + } + + // These are URIs we've seen redirecting back in loops after we redirect them + if (uri.spec in https_everywhere_blacklist) { + this.log(DBUG, "Avoiding blacklisted " + uri.spec); + if (alist) { + alist.breaking_rule(https_everywhere_blacklist[uri.spec]); + } else { + this.log(NOTE,"Failed to indicate breakage in content menu"); + } + return true; + } + + // OCSP (currently) needs to be HTTP to avoid cert validation loops + // though someone should rev the spec to allow opportunistic encryption + if ("allowSTS" in channel) { + // Firefox 32+ lets us infer whether this is an OCSP request + if (!channel.allowSTS) { + this.log(INFO, "Channel with HTTPS rewrites forbidden, deeming OCSP, for " + channel.URI.spec); + return true; + } + } else { + // Firefox <32 requires a more hacky estimate + // load the list opportunistically to speed startup & FF 32+ + if (this.ocspList == undefined) { this.loadOCSPList(); } + if (this.ocspList.indexOf(uri.spec.replace(/\/$/,'')) !== -1) { + this.log(INFO, "Known ocsp request "+uri.spec); + return true; + } + } + + return false; + }, + + loadOCSPList: function() { + try { + var loc = "chrome://https-everywhere/content/code/commonOCSP.json"; + var file = CC["@mozilla.org/file/local;1"].createInstance(CI.nsILocalFile); + file.initWithPath(this.rw.chromeToPath(loc)); + var data = this.rw.read(file); + this.ocspList = JSON.parse(data); + } catch(e) { + this.log(WARN, "Failed to load OCSP list: " + e); + this.ocspList = []; + } + }, + + observe: function(subject, topic, data) { + // Top level glue for the nsIObserver API + var channel = subject; + //this.log(VERB,"Got observer topic: "+topic); + + if (topic == "http-on-modify-request") { + if (!(channel instanceof CI.nsIHttpChannel)) return; + + this.log(DBUG,"Got http-on-modify-request: "+channel.URI.spec); + // lst is null if no window is associated (ex: some XHR) + var lst = this.getApplicableListForChannel(channel); + if (this.shouldIgnoreURI(channel, lst)) return; + HTTPS.replaceChannel(lst, channel, this.httpNowhereEnabled); + } else if (topic == "http-on-examine-response") { + this.log(DBUG, "Got http-on-examine-response @ "+ (channel.URI ? channel.URI.spec : '') ); + HTTPS.handleSecureCookies(channel); + } else if (topic == "http-on-examine-merged-response") { + this.log(DBUG, "Got http-on-examine-merged-response "); + HTTPS.handleSecureCookies(channel); + } else if (topic == "cookie-changed") { + // Javascript can add cookies via document.cookie that are insecure. + if (data == "added" || data == "changed") { + // subject can also be an nsIArray! bleh. + try { + subject.QueryInterface(CI.nsIArray); + var elems = subject.enumerate(); + while (elems.hasMoreElements()) { + var cookie = elems.getNext() + .QueryInterface(CI.nsICookie2); + if (!cookie.isSecure) { + HTTPS.handleInsecureCookie(cookie); + } + } + } catch(e) { + subject.QueryInterface(CI.nsICookie2); + if(!subject.isSecure) { + HTTPS.handleInsecureCookie(subject); + } + } + } + } else if (topic == "profile-before-change") { + this.log(INFO, "Got profile-before-change"); + var catman = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + catman.deleteCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, true); + } else if (topic == "profile-after-change") { + this.log(DBUG, "Got profile-after-change"); + + if(this.prefs.getBoolPref("globalEnabled")){ + OS.addObserver(this, "cookie-changed", false); + OS.addObserver(this, "http-on-modify-request", false); + OS.addObserver(this, "http-on-examine-merged-response", false); + OS.addObserver(this, "http-on-examine-response", false); + + HTTPSRules.init(); + + var catman = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + // hook on redirections (non persistent, otherwise crashes on 1.8.x) + catman.addCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, + SERVICE_CTRID, false, true); + } + } else if (topic == "sessionstore-windows-restored") { + this.log(DBUG,"Got sessionstore-windows-restored"); + if (!this.isMobile) { + this.maybeShowObservatoryPopup(); + } else { + this.log(WARN, "Initializing Firefox for Android UI"); + Cu.import("chrome://https-everywhere/content/code/AndroidUI.jsm"); + AndroidUI.init(); + } + this.browser_initialised = true; + } else if (topic == "nsPref:changed") { + // If the user toggles the Mixed Content Blocker settings, reload the rulesets + // to enable/disable the mixedcontent ones + + // this pref gets set to false and then true during FF 26 startup! + // so do nothing if we're being notified during startup + if (!this.browser_initialised) + return; + switch (data) { + case "security.mixed_content.block_active_content": + case "extensions.https_everywhere.enable_mixed_rulesets": + var p = CC["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefBranch); + var val = p.getBoolPref("security.mixed_content.block_active_content"); + this.log(INFO,"nsPref:changed for "+data + " to " + val); + HTTPSRules.init(); + break; + } + } else if (topic == "browser:purge-session-history") { + // The list of rulesets that have been loaded from the sqlite DB + // constitutes a parallel history store, so we have to clear it. + this.log(DBUG, "History cleared, reloading HTTPSRules to avoid information leak."); + HTTPSRules.init(); + } + return; + }, + + maybeShowObservatoryPopup: function() { + // Show the popup at most once. Users who enabled the Observatory before + // a version that would have shown it to them, don't need to see it + // again. + var ssl_observatory = CC["@eff.org/ssl-observatory;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + var shown = ssl_observatory.myGetBoolPref("popup_shown"); + var enabled = ssl_observatory.myGetBoolPref("enabled"); + var that = this; + var obs_popup_callback = function(result) { + if (result) that.log(INFO, "Got positive proxy test."); + else that.log(INFO, "Got negative proxy text."); + // We are now ready to show the popup in its most informative state + that.chrome_opener("chrome://https-everywhere/content/observatory-popup.xul"); + }; + if (!shown && !enabled) + ssl_observatory.registerProxyTestNotification(obs_popup_callback); + + if (shown && enabled) + this.maybeCleanupObservatoryPrefs(ssl_observatory); + }, + + maybeCleanupObservatoryPrefs: function(ssl_observatory) { + // Recover from a past UI processing bug that would leave the Obsevatory + // accidentally disabled for some users + // https://trac.torproject.org/projects/tor/ticket/10728 + var clean = ssl_observatory.myGetBoolPref("clean_config"); + if (clean) return; + + // unchanged: returns true if a pref has not been modified + var unchanged = function(p){return !ssl_observatory.prefs.prefHasUserValue("extensions.https_everywhere._observatory."+p)}; + var cleanup_obsprefs_callback = function(tor_avail) { + // we only run this once + ssl_observatory.prefs.setBoolPref("extensions.https_everywhere._observatory.clean_config", true); + if (!tor_avail) { + // use_custom_proxy is the variable that is often false when it should be true; + if (!ssl_observatory.myGetBoolPref("use_custom_proxy")) { + // however don't do anything if any of the prefs have been set by the user + if (unchanged("alt_roots") && unchanged("self_signed") && unchanged ("send_asn") && unchanged("priv_dns")) { + ssl_observatory.prefs.setBoolPref("extensions.https_everywhere._observatory.use_custom_proxy", true); + } + } + } + } + ssl_observatory.registerProxyTestNotification(cleanup_obsprefs_callback); + }, + + + getExperimentalFeatureCohort: function() { + // This variable is used for gradually turning on features for testing and + // scalability purposes. It is a random integer [0,N_COHORTS) generated + // once and stored thereafter. + // + // This is not currently used/called in the development branch + var cohort; + try { + cohort = this.prefs.getIntPref("experimental_feature_cohort"); + } catch(e) { + cohort = Math.round(Math.random() * N_COHORTS); + this.prefs.setIntPref("experimental_feature_cohort", cohort); + } + return cohort; + }, + + // nsIChannelEventSink implementation + // XXX This was here for rewrites in the past. Do we still need it? + onChannelRedirect: function(oldChannel, newChannel, flags) { + const uri = newChannel.URI; + this.log(DBUG,"Got onChannelRedirect to "+uri.spec); + if (!(newChannel instanceof CI.nsIHttpChannel)) { + this.log(DBUG, newChannel + " is not an instance of nsIHttpChannel"); + return; + } + var alist = this.juggleApplicableListsDuringRedirection(oldChannel, newChannel); + HTTPS.replaceChannel(alist, newChannel, this.httpNowhereEnabled); + }, + + juggleApplicableListsDuringRedirection: function(oldChannel, newChannel) { + // If the new channel doesn't yet have a list of applicable rulesets, start + // with the old one because that's probably a better representation of how + // secure the load process was for this page + var browser = this.getBrowserForChannel(oldChannel); + var old_alist = null; + if (browser) + old_alist = this.getExpando(browser,"applicable_rules"); + browser = this.getBrowserForChannel(newChannel); + if (!browser) return null; + var new_alist = this.getExpando(browser,"applicable_rules"); + if (old_alist && !new_alist) { + new_alist = old_alist; + this.setExpando(browser,"applicable_rules",new_alist); + } else if (!new_alist) { + new_alist = new ApplicableList(this.log, browser.currentURI); + this.setExpando(browser,"applicable_rules",new_alist); + } + return new_alist; + }, + + asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { + this.onChannelRedirect(oldChannel, newChannel, flags); + callback.onRedirectVerifyCallback(0); + }, + + get_prefs: function(prefBranch) { + if(!prefBranch) prefBranch = PREFBRANCH_ROOT; + + // get our preferences branch object + // FIXME: Ugly hack stolen from https + var branch_name; + if(prefBranch === PREFBRANCH_RULE_TOGGLE) + branch_name = "extensions.https_everywhere.rule_toggle."; + else if (prefBranch === PREFBRANCH_NONE) + branch_name = ""; + else + branch_name = "extensions.https_everywhere."; + var o_prefs = false; + var o_branch = false; + // this function needs to be called from inside https_everywhereLog, so + // it needs to do its own logging... + var econsole = Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); + + o_prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + + if (!o_prefs) + { + econsole.logStringMessage("HTTPS Everywhere: Failed to get preferences-service!"); + return false; + } + + o_branch = o_prefs.getBranch(branch_name); + if (!o_branch) + { + econsole.logStringMessage("HTTPS Everywhere: Failed to get prefs branch!"); + return false; + } + + if(prefBranch == PREFBRANCH_ROOT) { + // make sure there's an entry for our log level + try { + o_branch.getIntPref(LLVAR); + } catch (e) { + econsole.logStringMessage("Creating new about:config https_everywhere.LogLevel variable"); + o_branch.setIntPref(LLVAR, WARN); + } + } + + return o_branch; + }, + + // Are we on Firefox for Android? + doMobileCheck: function() { + let appInfo = CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo); + let ANDROID_ID = "{aa3c5121-dab2-40e2-81ca-7ea25febc110}"; + return (appInfo.ID === ANDROID_ID); + }, + + chrome_opener: function(uri, args) { + // we don't use window.open, because we need to work around TorButton's + // state control + args = args || 'chrome,centerscreen'; + return CC['@mozilla.org/appshell/window-mediator;1'] + .getService(CI.nsIWindowMediator) + .getMostRecentWindow('navigator:browser') + .open(uri,'', args ); + }, + + tab_opener: function(uri) { + var gb = CC['@mozilla.org/appshell/window-mediator;1'] + .getService(CI.nsIWindowMediator) + .getMostRecentWindow('navigator:browser') + .gBrowser; + var tab = gb.addTab(uri); + gb.selectedTab = tab; + return tab; + }, + + toggleEnabledState: function() { + if (this.prefs.getBoolPref("globalEnabled")) { + try { + this.obsService.removeObserver(this, "profile-before-change"); + this.obsService.removeObserver(this, "profile-after-change"); + this.obsService.removeObserver(this, "sessionstore-windows-restored"); + OS.removeObserver(this, "cookie-changed"); + OS.removeObserver(this, "http-on-modify-request"); + OS.removeObserver(this, "http-on-examine-merged-response"); + OS.removeObserver(this, "http-on-examine-response"); + + var catman = CC["@mozilla.org/categorymanager;1"] + .getService(CI.nsICategoryManager); + catman.deleteCategoryEntry("net-channel-event-sinks", + SERVICE_CTRID, true); + + this.prefs.setBoolPref("globalEnabled", false); + } catch(e) { + this.log(WARN, "Couldn't remove observers: " + e); + } + } else { + try { + this.obsService.addObserver(this, "profile-before-change", false); + this.obsService.addObserver(this, "profile-after-change", false); + this.obsService.addObserver(this, "sessionstore-windows-restored", false); + OS.addObserver(this, "cookie-changed", false); + OS.addObserver(this, "http-on-modify-request", false); + OS.addObserver(this, "http-on-examine-merged-response", false); + OS.addObserver(this, "http-on-examine-response", false); + + var catman = CC["@mozilla.org/categorymanager;1"] + .getService(CI.nsICategoryManager); + // hook on redirections (non persistent, otherwise crashes on 1.8.x) + catman.addCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, + SERVICE_CTRID, false, true); + + HTTPSRules.init(); + this.prefs.setBoolPref("globalEnabled", true); + } catch(e) { + this.log(WARN, "Couldn't add observers: " + e); + } + } + }, + + toggleHttpNowhere: function() { + let prefService = Services.prefs; + let thisBranch = + prefService.getBranch("extensions.https_everywhere.http_nowhere."); + let securityBranch = prefService.getBranch("security."); + + // Whether cert is treated as invalid when OCSP connection fails + let OCSP_REQUIRED = "OCSP.require"; + + // Branch to save original settings + let ORIG_OCSP_REQUIRED = "orig.ocsp.required"; + + + if (thisBranch.getBoolPref("enabled")) { + // Restore original OCSP settings. TODO: What if user manually edits + // these while HTTP Nowhere is enabled? + let origOcspRequired = thisBranch.getBoolPref(ORIG_OCSP_REQUIRED); + securityBranch.setBoolPref(OCSP_REQUIRED, origOcspRequired); + + thisBranch.setBoolPref("enabled", false); + this.httpNowhereEnabled = false; + } else { + // Save original OCSP settings in HTTP Nowhere preferences branch. + let origOcspRequired = securityBranch.getBoolPref(OCSP_REQUIRED); + thisBranch.setBoolPref(ORIG_OCSP_REQUIRED, origOcspRequired); + + // Disable OCSP enforcement + securityBranch.setBoolPref(OCSP_REQUIRED, false); + + thisBranch.setBoolPref("enabled", true); + this.httpNowhereEnabled = true; + } + } +}; + +var prefs = 0; +var econsole = 0; +function https_everywhereLog(level, str) { + if (prefs == 0) { + prefs = HTTPSEverywhere.instance.get_prefs(); + econsole = Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); + } + try { + var threshold = prefs.getIntPref(LLVAR); + } catch (e) { + econsole.logStringMessage( "HTTPS Everywhere: Failed to read about:config LogLevel"); + threshold = WARN; + } + if (level >= threshold) { + var levelName = ["", "VERB", "DBUG", "INFO", "NOTE", "WARN"][level]; + var prefix = "HTTPS Everywhere " + levelName + ": "; + // dump() prints to browser stdout. That's sometimes undesireable, + // so only do it when a pref is set (running from test.sh enables + // this pref). + if (prefs.getBoolPref("log_to_stdout")) { + dump(prefix + str + "\n"); + } + econsole.logStringMessage(prefix + str); + } +} + +/** +* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4). +* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6). +*/ +if (XPCOMUtils.generateNSGetFactory) + var NSGetFactory = XPCOMUtils.generateNSGetFactory([HTTPSEverywhere]); +else + var NSGetModule = XPCOMUtils.generateNSGetModule([HTTPSEverywhere]); + +/* vim: set tabstop=4 expandtab: */ |