Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 'use strict';
7 this.EXPORTED_SYMBOLS = ['ErrorPage'];
9 const Cu = Components.utils;
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
12 const kErrorPageFrameScript = 'chrome://b2g/content/ErrorPage.js';
14 Cu.import('resource://gre/modules/Services.jsm');
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
17 XPCOMUtils.defineLazyGetter(this, "CertOverrideService", function () {
18 return Cc["@mozilla.org/security/certoverride;1"]
19 .getService(Ci.nsICertOverrideService);
20 });
22 /**
23 * A class to add exceptions to override SSL certificate problems.
24 * The functionality itself is borrowed from exceptionDialog.js.
25 */
26 function SSLExceptions(aCallback, aUri, aWindow) {
27 this._finishCallback = aCallback;
28 this._uri = aUri;
29 this._window = aWindow;
30 };
32 SSLExceptions.prototype = {
33 _finishCallback: null,
34 _window: null,
35 _uri: null,
36 _temporary: null,
37 _sslStatus: null,
39 getInterface: function SSLE_getInterface(aIID) {
40 return this.QueryInterface(aIID);
41 },
43 QueryInterface: XPCOMUtils.generateQI([Ci.nsIBadCertListener2]),
45 /**
46 * To collect the SSL status we intercept the certificate error here
47 * and store the status for later use.
48 */
49 notifyCertProblem: function SSLE_notifyCertProblem(aSocketInfo,
50 aSslStatus,
51 aTargetHost) {
52 this._sslStatus = aSslStatus.QueryInterface(Ci.nsISSLStatus);
53 Services.tm.currentThread.dispatch({
54 run: this._addOverride.bind(this)
55 }, Ci.nsIThread.DISPATCH_NORMAL);
56 return true; // suppress error UI
57 },
59 /**
60 * Attempt to download the certificate for the location specified to get
61 * the SSLState for the certificate and the errors.
62 */
63 _checkCert: function SSLE_checkCert() {
64 this._sslStatus = null;
65 if (!this._uri) {
66 return;
67 }
68 let req = new this._window.XMLHttpRequest();
69 try {
70 req.open("GET", this._uri.prePath, true);
71 req.channel.notificationCallbacks = this;
72 let xhrHandler = (function() {
73 req.removeEventListener("load", xhrHandler);
74 req.removeEventListener("error", xhrHandler);
75 if (!this._sslStatus) {
76 // Got response from server without an SSL error.
77 if (this._finishCallback) {
78 this._finishCallback();
79 }
80 }
81 }).bind(this);
82 req.addEventListener("load", xhrHandler);
83 req.addEventListener("error", xhrHandler);
84 req.send(null);
85 } catch (e) {
86 // We *expect* exceptions if there are problems with the certificate
87 // presented by the site. Log it, just in case, but we can proceed here,
88 // with appropriate sanity checks
89 Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " +
90 "This results in a (mostly harmless) exception being thrown. " +
91 "Logged for information purposes only: " + e);
92 }
93 },
95 /**
96 * Internal method to create an override.
97 */
98 _addOverride: function SSLE_addOverride() {
99 let SSLStatus = this._sslStatus;
100 let uri = this._uri;
101 let flags = 0;
103 if (SSLStatus.isUntrusted) {
104 flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED;
105 }
106 if (SSLStatus.isDomainMismatch) {
107 flags |= Ci.nsICertOverrideService.ERROR_MISMATCH;
108 }
109 if (SSLStatus.isNotValidAtThisTime) {
110 flags |= Ci.nsICertOverrideService.ERROR_TIME;
111 }
113 CertOverrideService.rememberValidityOverride(
114 uri.asciiHost,
115 uri.port,
116 SSLStatus.serverCert,
117 flags,
118 this._temporary);
120 if (this._finishCallback) {
121 this._finishCallback();
122 }
123 },
125 /**
126 * Creates a permanent exception to override all overridable errors for
127 * the given URL.
128 */
129 addException: function SSLE_addException(aTemporary) {
130 this._temporary = aTemporary;
131 this._checkCert();
132 }
133 };
135 let ErrorPage = {
136 _addCertException: function(aMessage) {
137 let frameLoaderOwner = aMessage.target.QueryInterface(Ci.nsIFrameLoaderOwner);
138 let win = frameLoaderOwner.ownerDocument.defaultView;
139 let mm = frameLoaderOwner.frameLoader.messageManager;
141 let uri = Services.io.newURI(aMessage.data.url, null, null);
142 let sslExceptions = new SSLExceptions((function() {
143 mm.sendAsyncMessage('ErrorPage:ReloadPage');
144 }).bind(this), uri, win);
145 try {
146 sslExceptions.addException(!aMessage.data.isPermanent);
147 } catch (e) {
148 dump("Failed to set cert exception: " + e + "\n");
149 }
150 },
152 _listenError: function(frameLoader) {
153 let self = this;
154 let frameElement = frameLoader.ownerElement;
155 let injectErrorPageScript = function() {
156 let mm = frameLoader.messageManager;
157 try {
158 mm.loadFrameScript(kErrorPageFrameScript, true, true);
159 } catch (e) {
160 dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
161 }
162 mm.addMessageListener('ErrorPage:AddCertException', self._addCertException.bind(self));
163 frameElement.removeEventListener('mozbrowsererror', injectErrorPageScript, true);
164 };
166 frameElement.addEventListener('mozbrowsererror',
167 injectErrorPageScript,
168 true // use capture
169 );
170 },
172 init: function errorPageInit() {
173 Services.obs.addObserver(this, 'inprocess-browser-shown', false);
174 Services.obs.addObserver(this, 'remote-browser-shown', false);
175 },
177 observe: function errorPageObserve(aSubject, aTopic, aData) {
178 let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
179 // Ignore notifications that aren't from a BrowserOrApp
180 if (!frameLoader.ownerIsBrowserOrAppFrame) {
181 return;
182 }
183 this._listenError(frameLoader);
184 }
185 };
187 ErrorPage.init();