exceptionDialog.js (10735B)
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 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { setText, viewCertHelper, checkCertHelper } = ChromeUtils.importESModule( 8 "resource://gre/modules/psm/pippki.sys.mjs" 9 ); 10 11 var gDialog; 12 var gSecInfo; 13 var gCert; 14 var gChecking; 15 var gBroken; 16 var gNeedReset; 17 18 const { PrivateBrowsingUtils } = ChromeUtils.importESModule( 19 "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" 20 ); 21 22 function initExceptionDialog() { 23 gNeedReset = false; 24 gDialog = document.getElementById("exceptiondialog"); 25 let warningText = document.getElementById("warningText"); 26 document.l10n.setAttributes(warningText, "add-exception-branded-warning"); 27 let confirmButton = gDialog.getButton("extra1"); 28 let l10nUpdatedElements = [confirmButton, warningText]; 29 confirmButton.disabled = true; 30 31 document 32 .getElementById("locationTextBox") 33 .addEventListener("input", () => handleTextChange()); 34 document 35 .getElementById("viewCertButton") 36 .addEventListener("command", () => viewCertButtonClick()); 37 38 var args = window.arguments; 39 if (args && args[0]) { 40 if (args[0].location) { 41 // We were pre-seeded with a location. 42 document.getElementById("locationTextBox").value = args[0].location; 43 document.getElementById("checkCertButton").disabled = false; 44 45 if (args[0].securityInfo) { 46 gSecInfo = args[0].securityInfo; 47 gCert = gSecInfo.serverCert; 48 gBroken = true; 49 l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus()); 50 } else if (args[0].prefetchCert) { 51 // We can optionally pre-fetch the certificate too. Don't do this 52 // synchronously, since it would prevent the window from appearing 53 // until the fetch is completed, which could be multiple seconds. 54 // Instead, let's use a timer to spawn the actual fetch, but update 55 // the dialog to "checking..." state right away, so that the UI 56 // is appropriately responsive. Bug 453855 57 document.getElementById("checkCertButton").disabled = true; 58 gChecking = true; 59 l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus()); 60 61 window.setTimeout(checkCert, 0); 62 } 63 } 64 65 // Set out parameter to false by default 66 args[0].exceptionAdded = false; 67 } 68 69 for (let id of [ 70 "warningSupplemental", 71 "certLocationLabel", 72 "checkCertButton", 73 "statusDescription", 74 "statusLongDescription", 75 "viewCertButton", 76 "permanent", 77 ]) { 78 let element = document.getElementById(id); 79 l10nUpdatedElements.push(element); 80 } 81 82 document.l10n 83 .translateElements(l10nUpdatedElements) 84 .then(() => window.sizeToContent()); 85 86 document.addEventListener("dialogextra1", addException); 87 document.addEventListener("dialogextra2", checkCert); 88 } 89 90 /** 91 * Helper function for checkCert. Set as the onerror/onload callbacks for an 92 * XMLHttpRequest. Sets gSecInfo, gCert, gBroken, and gChecking according to 93 * the load information from the request. Probably should not be used directly. 94 * 95 * @param {XMLHttpRequest} req 96 * The XMLHttpRequest created and sent by checkCert. 97 * @param {Event} evt 98 * The load or error event. 99 */ 100 function grabCert(req, evt) { 101 if (req.channel && req.channel.securityInfo) { 102 gSecInfo = req.channel.securityInfo; 103 gCert = gSecInfo ? gSecInfo.serverCert : null; 104 } 105 gBroken = evt.type == "error"; 106 gChecking = false; 107 document.l10n 108 .translateElements(updateCertStatus()) 109 .then(() => window.sizeToContent()); 110 } 111 112 /** 113 * Attempt to download the certificate for the location specified, and populate 114 * the Certificate Status section with the result. 115 */ 116 async function checkCert() { 117 gCert = null; 118 gSecInfo = null; 119 gChecking = true; 120 gBroken = false; 121 await document.l10n.translateElements(updateCertStatus()); 122 window.sizeToContent(); 123 124 let uri = getURI(); 125 126 if (uri) { 127 checkCertHelper(uri, grabCert); 128 } else { 129 gChecking = false; 130 await document.l10n.translateElements(updateCertStatus()); 131 window.sizeToContent(); 132 } 133 } 134 135 /** 136 * Build and return a URI, based on the information supplied in the 137 * Certificate Location fields 138 * 139 * @returns {nsIURI} 140 * URI constructed from the information supplied on success, null 141 * otherwise. 142 */ 143 function getURI() { 144 // Use fixup service instead of just ioservice's newURI since it's quite 145 // likely that the host will be supplied without a protocol prefix, resulting 146 // in malformed uri exceptions being thrown. 147 let locationTextBox = document.getElementById("locationTextBox"); 148 let { preferredURI: uri } = Services.uriFixup.getFixupURIInfo( 149 locationTextBox.value 150 ); 151 152 if (!uri) { 153 return null; 154 } 155 156 let mutator = uri.mutate(); 157 if (uri.scheme == "http") { 158 mutator.setScheme("https"); 159 } 160 161 if (uri.port == -1) { 162 mutator.setPort(443); 163 } 164 165 return mutator.finalize(); 166 } 167 168 function resetDialog() { 169 document.getElementById("viewCertButton").disabled = true; 170 document.getElementById("permanent").disabled = true; 171 gDialog.getButton("extra1").disabled = true; 172 setText(document, "headerDescription", ""); 173 setText(document, "statusDescription", ""); 174 setText(document, "statusLongDescription", ""); 175 setText(document, "status2Description", ""); 176 setText(document, "status2LongDescription", ""); 177 setText(document, "status3Description", ""); 178 setText(document, "status3LongDescription", ""); 179 window.sizeToContent(); 180 } 181 182 /** 183 * Called by input textboxes to manage UI state 184 */ 185 function handleTextChange() { 186 var checkCertButton = document.getElementById("checkCertButton"); 187 checkCertButton.disabled = !document.getElementById("locationTextBox").value; 188 if (gNeedReset) { 189 gNeedReset = false; 190 resetDialog(); 191 } 192 } 193 194 function updateCertStatus() { 195 var shortDesc, longDesc; 196 let l10nUpdatedElements = []; 197 if (gCert) { 198 if (gBroken) { 199 var mms = "add-exception-domain-mismatch-short"; 200 var mml = "add-exception-domain-mismatch-long"; 201 var exs = "add-exception-expired-short"; 202 var exl = "add-exception-expired-long"; 203 var uts = "add-exception-unverified-or-bad-signature-short"; 204 var utl = "add-exception-unverified-or-bad-signature-long"; 205 if ( 206 gSecInfo.overridableErrorCategory == 207 Ci.nsITransportSecurityInfo.ERROR_TRUST 208 ) { 209 shortDesc = uts; 210 longDesc = utl; 211 } else if ( 212 gSecInfo.overridableErrorCategory == 213 Ci.nsITransportSecurityInfo.ERROR_DOMAIN 214 ) { 215 shortDesc = mms; 216 longDesc = mml; 217 } else if ( 218 gSecInfo.overridableErrorCategory == 219 Ci.nsITransportSecurityInfo.ERROR_TIME 220 ) { 221 shortDesc = exs; 222 longDesc = exl; 223 } 224 // In these cases, we do want to enable the "Add Exception" button 225 gDialog.getButton("extra1").disabled = false; 226 227 // If the Private Browsing service is available and the mode is active, 228 // don't store permanent exceptions, since they would persist after 229 // private browsing mode was disabled. 230 var inPrivateBrowsing = inPrivateBrowsingMode(); 231 var pe = document.getElementById("permanent"); 232 pe.disabled = inPrivateBrowsing; 233 pe.checked = !inPrivateBrowsing; 234 235 let headerDescription = document.getElementById("headerDescription"); 236 document.l10n.setAttributes( 237 headerDescription, 238 "add-exception-invalid-header" 239 ); 240 l10nUpdatedElements.push(headerDescription); 241 } else { 242 shortDesc = "add-exception-valid-short"; 243 longDesc = "add-exception-valid-long"; 244 gDialog.getButton("extra1").disabled = true; 245 document.getElementById("permanent").disabled = true; 246 } 247 248 // We're done checking the certificate, so allow the user to check it again. 249 document.getElementById("checkCertButton").disabled = false; 250 document.getElementById("viewCertButton").disabled = false; 251 252 // Notify observers about the availability of the certificate 253 Services.obs.notifyObservers(window, "cert-exception-ui-ready"); 254 } else if (gChecking) { 255 shortDesc = "add-exception-checking-short"; 256 longDesc = "add-exception-checking-long"; 257 // We're checking the certificate, so we disable the Get Certificate 258 // button to make sure that the user can't interrupt the process and 259 // trigger another certificate fetch. 260 document.getElementById("checkCertButton").disabled = true; 261 document.getElementById("viewCertButton").disabled = true; 262 gDialog.getButton("extra1").disabled = true; 263 document.getElementById("permanent").disabled = true; 264 } else { 265 shortDesc = "add-exception-no-cert-short"; 266 longDesc = "add-exception-no-cert-long"; 267 // We're done checking the certificate, so allow the user to check it again. 268 document.getElementById("checkCertButton").disabled = false; 269 document.getElementById("viewCertButton").disabled = true; 270 gDialog.getButton("extra1").disabled = true; 271 document.getElementById("permanent").disabled = true; 272 } 273 let statusDescription = document.getElementById("statusDescription"); 274 let statusLongDescription = document.getElementById("statusLongDescription"); 275 document.l10n.setAttributes(statusDescription, shortDesc); 276 document.l10n.setAttributes(statusLongDescription, longDesc); 277 l10nUpdatedElements.push(statusDescription); 278 l10nUpdatedElements.push(statusLongDescription); 279 280 gNeedReset = true; 281 return l10nUpdatedElements; 282 } 283 284 /** 285 * Handle user request to display certificate details 286 */ 287 function viewCertButtonClick() { 288 if (gCert) { 289 viewCertHelper(this, gCert); 290 } 291 } 292 293 /** 294 * Handle user request to add an exception for the specified cert 295 */ 296 function addException() { 297 if (!gCert || !gSecInfo) { 298 return; 299 } 300 301 var overrideService = Cc["@mozilla.org/security/certoverride;1"].getService( 302 Ci.nsICertOverrideService 303 ); 304 var permanentCheckbox = document.getElementById("permanent"); 305 var shouldStorePermanently = 306 permanentCheckbox.checked && !inPrivateBrowsingMode(); 307 var uri = getURI(); 308 overrideService.rememberValidityOverride( 309 uri.asciiHost, 310 uri.port, 311 {}, 312 gCert, 313 !shouldStorePermanently 314 ); 315 316 let args = window.arguments; 317 if (args && args[0]) { 318 args[0].exceptionAdded = true; 319 } 320 321 gDialog.acceptDialog(); 322 } 323 324 /** 325 * @returns {boolean} Whether this dialog is in private browsing mode. 326 */ 327 function inPrivateBrowsingMode() { 328 return window.isChromeWindow 329 ? PrivateBrowsingUtils.isWindowPrivate(window) 330 : PrivateBrowsingUtils.isContentWindowPrivate(window); 331 } 332 333 window.addEventListener("load", () => initExceptionDialog());