michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: this.EXPORTED_SYMBOLS = ['ErrorPage']; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const kErrorPageFrameScript = 'chrome://b2g/content/ErrorPage.js'; michael@0: michael@0: Cu.import('resource://gre/modules/Services.jsm'); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "CertOverrideService", function () { michael@0: return Cc["@mozilla.org/security/certoverride;1"] michael@0: .getService(Ci.nsICertOverrideService); michael@0: }); michael@0: michael@0: /** michael@0: * A class to add exceptions to override SSL certificate problems. michael@0: * The functionality itself is borrowed from exceptionDialog.js. michael@0: */ michael@0: function SSLExceptions(aCallback, aUri, aWindow) { michael@0: this._finishCallback = aCallback; michael@0: this._uri = aUri; michael@0: this._window = aWindow; michael@0: }; michael@0: michael@0: SSLExceptions.prototype = { michael@0: _finishCallback: null, michael@0: _window: null, michael@0: _uri: null, michael@0: _temporary: null, michael@0: _sslStatus: null, michael@0: michael@0: getInterface: function SSLE_getInterface(aIID) { michael@0: return this.QueryInterface(aIID); michael@0: }, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIBadCertListener2]), michael@0: michael@0: /** michael@0: * To collect the SSL status we intercept the certificate error here michael@0: * and store the status for later use. michael@0: */ michael@0: notifyCertProblem: function SSLE_notifyCertProblem(aSocketInfo, michael@0: aSslStatus, michael@0: aTargetHost) { michael@0: this._sslStatus = aSslStatus.QueryInterface(Ci.nsISSLStatus); michael@0: Services.tm.currentThread.dispatch({ michael@0: run: this._addOverride.bind(this) michael@0: }, Ci.nsIThread.DISPATCH_NORMAL); michael@0: return true; // suppress error UI michael@0: }, michael@0: michael@0: /** michael@0: * Attempt to download the certificate for the location specified to get michael@0: * the SSLState for the certificate and the errors. michael@0: */ michael@0: _checkCert: function SSLE_checkCert() { michael@0: this._sslStatus = null; michael@0: if (!this._uri) { michael@0: return; michael@0: } michael@0: let req = new this._window.XMLHttpRequest(); michael@0: try { michael@0: req.open("GET", this._uri.prePath, true); michael@0: req.channel.notificationCallbacks = this; michael@0: let xhrHandler = (function() { michael@0: req.removeEventListener("load", xhrHandler); michael@0: req.removeEventListener("error", xhrHandler); michael@0: if (!this._sslStatus) { michael@0: // Got response from server without an SSL error. michael@0: if (this._finishCallback) { michael@0: this._finishCallback(); michael@0: } michael@0: } michael@0: }).bind(this); michael@0: req.addEventListener("load", xhrHandler); michael@0: req.addEventListener("error", xhrHandler); michael@0: req.send(null); michael@0: } catch (e) { michael@0: // We *expect* exceptions if there are problems with the certificate michael@0: // presented by the site. Log it, just in case, but we can proceed here, michael@0: // with appropriate sanity checks michael@0: Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " + michael@0: "This results in a (mostly harmless) exception being thrown. " + michael@0: "Logged for information purposes only: " + e); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Internal method to create an override. michael@0: */ michael@0: _addOverride: function SSLE_addOverride() { michael@0: let SSLStatus = this._sslStatus; michael@0: let uri = this._uri; michael@0: let flags = 0; michael@0: michael@0: if (SSLStatus.isUntrusted) { michael@0: flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED; michael@0: } michael@0: if (SSLStatus.isDomainMismatch) { michael@0: flags |= Ci.nsICertOverrideService.ERROR_MISMATCH; michael@0: } michael@0: if (SSLStatus.isNotValidAtThisTime) { michael@0: flags |= Ci.nsICertOverrideService.ERROR_TIME; michael@0: } michael@0: michael@0: CertOverrideService.rememberValidityOverride( michael@0: uri.asciiHost, michael@0: uri.port, michael@0: SSLStatus.serverCert, michael@0: flags, michael@0: this._temporary); michael@0: michael@0: if (this._finishCallback) { michael@0: this._finishCallback(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Creates a permanent exception to override all overridable errors for michael@0: * the given URL. michael@0: */ michael@0: addException: function SSLE_addException(aTemporary) { michael@0: this._temporary = aTemporary; michael@0: this._checkCert(); michael@0: } michael@0: }; michael@0: michael@0: let ErrorPage = { michael@0: _addCertException: function(aMessage) { michael@0: let frameLoaderOwner = aMessage.target.QueryInterface(Ci.nsIFrameLoaderOwner); michael@0: let win = frameLoaderOwner.ownerDocument.defaultView; michael@0: let mm = frameLoaderOwner.frameLoader.messageManager; michael@0: michael@0: let uri = Services.io.newURI(aMessage.data.url, null, null); michael@0: let sslExceptions = new SSLExceptions((function() { michael@0: mm.sendAsyncMessage('ErrorPage:ReloadPage'); michael@0: }).bind(this), uri, win); michael@0: try { michael@0: sslExceptions.addException(!aMessage.data.isPermanent); michael@0: } catch (e) { michael@0: dump("Failed to set cert exception: " + e + "\n"); michael@0: } michael@0: }, michael@0: michael@0: _listenError: function(frameLoader) { michael@0: let self = this; michael@0: let frameElement = frameLoader.ownerElement; michael@0: let injectErrorPageScript = function() { michael@0: let mm = frameLoader.messageManager; michael@0: try { michael@0: mm.loadFrameScript(kErrorPageFrameScript, true, true); michael@0: } catch (e) { michael@0: dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n'); michael@0: } michael@0: mm.addMessageListener('ErrorPage:AddCertException', self._addCertException.bind(self)); michael@0: frameElement.removeEventListener('mozbrowsererror', injectErrorPageScript, true); michael@0: }; michael@0: michael@0: frameElement.addEventListener('mozbrowsererror', michael@0: injectErrorPageScript, michael@0: true // use capture michael@0: ); michael@0: }, michael@0: michael@0: init: function errorPageInit() { michael@0: Services.obs.addObserver(this, 'inprocess-browser-shown', false); michael@0: Services.obs.addObserver(this, 'remote-browser-shown', false); michael@0: }, michael@0: michael@0: observe: function errorPageObserve(aSubject, aTopic, aData) { michael@0: let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader); michael@0: // Ignore notifications that aren't from a BrowserOrApp michael@0: if (!frameLoader.ownerIsBrowserOrAppFrame) { michael@0: return; michael@0: } michael@0: this._listenError(frameLoader); michael@0: } michael@0: }; michael@0: michael@0: ErrorPage.init();