Bug 989197 - Show alternate UI in cert error pages when a captive portal is active. r=Gijs
MozReview-Commit-ID: DnWdwcx8r9S
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -25,16 +25,18 @@
toolkit/components/places/src/nsFaviconService.h should be updated. -->
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
<script type="application/javascript"><![CDATA[
// The following parameters are parsed from the error URL:
// e - the error code
// s - custom CSS class to allow alternate styling/favicons
// d - error description
+ // captive - "true" to indicate we're behind a captive portal.
+ // Any other value is ignored.
// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.
let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
@@ -52,16 +54,20 @@
return searchParams.get("s");
}
function getDescription()
{
return searchParams.get("d");
}
+ function isCaptive() {
+ return searchParams.get("captive") == "true";
+ }
+
function retryThis(buttonEl)
{
// Note: The application may wish to handle switching off "offline mode"
// before this event handler runs, but using a capturing event handler.
// Session history has the URL of the page that failed
// to load, not the one of the error page. So, just call
// reload(), which will also repost POST data correctly.
@@ -100,17 +106,17 @@
panel.style.display = "block";
document.getElementById("netErrorButtonContainer").style.display = "none";
document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
document.dispatchEvent(event);
});
}
- function showAdvancedButton(allowOverride) {
+ function setupAdvancedButton(allowOverride) {
// Get the hostname and add it to the panel
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
var panel = document.getElementById(panelId);
for (var span of panel.querySelectorAll("span.hostname")) {
span.textContent = document.location.hostname;
}
if (!gIsCertError) {
panel.replaceChild(document.getElementById("errorLongDesc"),
@@ -136,107 +142,90 @@
}
});
if (allowOverride) {
document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
var overrideLink = document.getElementById("overrideWeakCrypto");
overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
}
- }
- function initPageCertError() {
- document.body.className = "certerror";
- document.title = document.getElementById("certErrorPageTitle").textContent;
- for (let host of document.querySelectorAll(".hostname")) {
- host.textContent = document.location.hostname;
+ if (!gIsCertError) {
+ return;
}
- showAdvancedButton(true);
-
- var cssClass = getCSSClass();
- if (cssClass == "expertBadCert") {
+ if (getCSSClass() == "expertBadCert") {
toggleDisplay(document.getElementById("badCertAdvancedPanel"));
// Toggling the advanced panel must ensure that the debugging
// information panel is hidden as well, since it's opened by the
// error code link in the advanced panel.
var div = document.getElementById("certificateErrorDebugInformation");
div.style.display = "none";
}
- document.getElementById("learnMoreContainer").style.display = "block";
-
- var checkbox = document.getElementById("automaticallyReportInFuture");
- checkbox.addEventListener("change", function({target: {checked}}) {
- document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
- detail: checked,
- bubbles: true
- }));
- });
+ disallowCertOverridesIfNeeded();
- addEventListener("AboutNetErrorOptions", function(event) {
- var options = JSON.parse(event.detail);
- if (options && options.enabled) {
- // Display error reporting UI
- document.getElementById("certificateErrorReporting").style.display = "block";
+ document.getElementById("badCertTechnicalInfo").textContent = getDescription();
+ }
- // set the checkbox
- checkbox.checked = !!options.automatic;
- }
- }, true, true);
-
+ function disallowCertOverridesIfNeeded() {
+ var cssClass = getCSSClass();
// Disallow overrides if this is a Strict-Transport-Security
// host and the cert is bad (STS Spec section 7.3) or if the
// certerror is in a frame (bug 633691).
if (cssClass == "badStsCert" || window != top) {
document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
}
if (cssClass == "badStsCert") {
document.getElementById("badStsCertExplanation").removeAttribute("hidden");
}
-
- document.getElementById("badCertTechnicalInfo").textContent = getDescription();
-
- var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
- document.getElementById("advancedButton").dispatchEvent(event);
-
- addDomainErrorLinks();
}
function initPage()
{
var err = getErrorCode();
gIsCertError = (err == "nssBadCert");
+ // Only worry about captive portals if this is a cert error.
+ let showCaptivePortalUI = isCaptive() && gIsCertError;
+ if (showCaptivePortalUI) {
+ err = "captivePortal";
+ }
// if it's an unknown error or there's no title or description
// defined, get the generic message
var errTitle = document.getElementById("et_" + err);
var errDesc = document.getElementById("ed_" + err);
if (!errTitle || !errDesc)
{
errTitle = document.getElementById("et_generic");
errDesc = document.getElementById("ed_generic");
}
document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
var sd = document.getElementById("errorShortDescText");
if (sd) {
if (gIsCertError) {
- sd.innerHTML = document.getElementById("ed_nssBadCert").innerHTML;
+ sd.innerHTML = errDesc.innerHTML;
}
else {
sd.textContent = getDescription();
}
}
+ if (showCaptivePortalUI) {
+ initPageCaptivePortal();
+ return;
+ }
if (gIsCertError) {
initPageCertError();
return;
}
+ document.body.className = "neterror";
+
var ld = document.getElementById("errorLongDesc");
if (ld)
{
ld.innerHTML = errDesc.innerHTML;
}
if (err == "sslv3Used") {
document.getElementById("learnMoreContainer").style.display = "block";
@@ -320,17 +309,17 @@
"SSL_ERROR_NO_CIPHERS_SUPPORTED"
].some((substring) => getDescription().includes(substring));
// If it looks like an error that is user config based
if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
showPrefChangeContainer();
}
}
if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
- showAdvancedButton(getErrorCode() == "weakCryptoUsed");
+ setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
}
}.bind(this), true, true);
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
document.dispatchEvent(event);
if (err == "inadequateSecurityError") {
// Remove the "Try again" button for HTTP/2 inadequate security as it
@@ -341,16 +330,65 @@
for (var span of container.querySelectorAll("span.hostname")) {
span.textContent = document.location.hostname;
}
}
addDomainErrorLinks();
}
+ function initPageCaptivePortal()
+ {
+ document.body.className = "captiveportal";
+ document.title = document.getElementById("captivePortalPageTitle").textContent;
+
+ document.getElementById("openPortalLoginPageButton")
+ .addEventListener("click", () => {
+ let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
+ document.dispatchEvent(event);
+ });
+
+ setupAdvancedButton(true);
+
+ addDomainErrorLinks();
+ }
+
+ function initPageCertError() {
+ document.body.className = "certerror";
+ document.title = document.getElementById("certErrorPageTitle").textContent;
+
+ setupAdvancedButton(true);
+
+ document.getElementById("learnMoreContainer").style.display = "block";
+
+ let checkbox = document.getElementById("automaticallyReportInFuture");
+ checkbox.addEventListener("change", function({target: {checked}}) {
+ document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
+ detail: checked,
+ bubbles: true
+ }));
+ });
+
+ addEventListener("AboutNetErrorOptions", function(event) {
+ var options = JSON.parse(event.detail);
+ if (options && options.enabled) {
+ // Display error reporting UI
+ document.getElementById("certificateErrorReporting").style.display = "block";
+
+ // set the checkbox
+ checkbox.checked = !!options.automatic;
+ }
+ }, true, true);
+
+ let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+ document.getElementById("advancedButton").dispatchEvent(event);
+
+ addDomainErrorLinks();
+ }
+
/* Try to preserve the links contained in the error description, like
the error code.
Also, in the case of SSL error pages about domain mismatch, see if
we can hyperlink the user to the correct site. We don't want
to do this generically since it allows MitM attacks to redirect
users to a site under attacker control, but in certain cases
it is safe (and helpful!) to do so. Bug 402210
@@ -471,21 +509,23 @@
el.appendChild(anchorEl);
}
]]></script>
</head>
<body dir="&locale.dir;">
<!-- Contains an alternate page title set on page init for cert errors. -->
<div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
+ <div id="captivePortalPageTitle" style="display: none;">&captivePortal.title;</div>
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
<div id="errorContainer">
<div id="errorTitlesContainer">
<h1 id="et_generic">&generic.title;</h1>
+ <h1 id="et_captivePortal">&captivePortal.title;</h1>
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
<h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
<h1 id="et_malformedURI">&malformedURI.title;</h1>
<h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
<h1 id="et_netTimeout">&netTimeout.title;</h1>
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
@@ -505,16 +545,17 @@
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
<h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
<h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
</div>
<div id="errorDescriptionsContainer">
<div id="ed_generic">&generic.longDesc;</div>
+ <div id="ed_captivePortal">&captivePortal.longDesc;</div>
<div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
<div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
<div id="ed_malformedURI">&malformedURI.longDesc;</div>
<div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
<div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
<div id="ed_netTimeout">&netTimeout.longDesc;</div>
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
@@ -567,18 +608,19 @@
<p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
</div>
<div id="prefChangeContainer" class="button-container">
<p>&prefReset.longDesc;</p>
<button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
</div>
- <div id="certErrorButtonContainer" class="button-container">
+ <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
<button id="returnButton" class="primary" autocomplete="off" autofocus="true">&returnToPreviousPage.label;</button>
+ <button id="openPortalLoginPageButton" class="primary" autocomplete="off" autofocus="true">&openPortalLoginPage.label;</button>
<div class="button-spacer"></div>
<button id="advancedButton" autocomplete="off" autofocus="true">&advanced.label;</button>
</div>
</div>
<div id="netErrorButtonContainer" class="button-container">
<button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
</div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -146,17 +146,16 @@ XPCOMUtils.defineLazyGetter(this, "Win7F
onCloseWindow: function() {
AeroPeek.onCloseWindow(window);
}
};
}
return null;
});
-
const nsIWebNavigation = Ci.nsIWebNavigation;
var gLastBrowserCharset = null;
var gLastValidURLStr = "";
var gInPrintPreviewMode = false;
var gContextMenu = null; // nsContextMenu instance
var gMultiProcessBrowser =
window.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -2723,16 +2722,17 @@ const PREF_SSL_IMPACT = PREF_SSL_IMPACT_
* Handle command events bubbling up from error page content
* or from about:newtab or from remote error pages that invoke
* us via async messaging.
*/
var BrowserOnClick = {
init: function() {
let mm = window.messageManager;
mm.addMessageListener("Browser:CertExceptionError", this);
+ mm.addMessageListener("Browser:OpenCaptivePortalPage", this);
mm.addMessageListener("Browser:SiteBlockedError", this);
mm.addMessageListener("Browser:EnableOnlineMode", this);
mm.addMessageListener("Browser:SendSSLErrorReport", this);
mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
mm.addMessageListener("Browser:ResetSSLPreferences", this);
mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
mm.addMessageListener("Browser:OverrideWeakCrypto", this);
mm.addMessageListener("Browser:SSLErrorGoBack", this);
@@ -2771,16 +2771,19 @@ var BrowserOnClick = {
receiveMessage: function(msg) {
switch (msg.name) {
case "Browser:CertExceptionError":
this.onCertError(msg.target, msg.data.elementId,
msg.data.isTopFrame, msg.data.location,
msg.data.securityInfoAsString);
break;
+ case "Browser:OpenCaptivePortalPage":
+ this.onOpenCaptivePortalPage();
+ break;
case "Browser:SiteBlockedError":
this.onAboutBlocked(msg.data.elementId, msg.data.reason,
msg.data.isTopFrame, msg.data.location);
break;
case "Browser:EnableOnlineMode":
if (Services.io.offline) {
// Reset network state and refresh the page.
Services.io.offline = false;
@@ -2905,16 +2908,38 @@ var BrowserOnClick = {
let detailedInfo = getDetailedCertErrorInfo(location,
securityInfo);
gClipboardHelper.copyString(detailedInfo);
break;
}
},
+ onOpenCaptivePortalPage: function() {
+ // Open a new tab with the canonical URL that we use to check for a captive portal.
+ // It will be redirected to the login page.
+ let canonicalURL = Services.prefs.getCharPref("captivedetect.canonicalURL");
+ let tab = gBrowser.addTab(canonicalURL);
+ let canonicalURI = makeURI(canonicalURL);
+ gBrowser.selectedTab = tab;
+
+ // When we are no longer captive, close the tab if it's at the canonical URL.
+ let tabCloser = () => {
+ Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
+ Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
+ if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
+ !tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
+ return;
+ }
+ gBrowser.removeTab(tab);
+ }
+ Services.obs.addObserver(tabCloser, "captive-portal-login-abort", false);
+ Services.obs.addObserver(tabCloser, "captive-portal-login-success", false);
+ },
+
onAboutBlocked: function(elementId, reason, isTopFrame, location) {
// Depending on what page we are displaying here (malware/phishing/unwanted)
// use the right strings and links for each.
let bucketName = "";
let sendTelemetry = false;
if (reason === 'malware') {
sendTelemetry = true;
bucketName = "WARNING_MALWARE_PAGE_";
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -260,16 +260,17 @@ function getSerializedSecurityInfo(docSh
return serhelper.serializeToString(securityInfo);
}
var AboutNetAndCertErrorListener = {
init: function(chromeGlobal) {
addMessageListener("CertErrorDetails", this);
chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
+ chromeGlobal.addEventListener('AboutNetErrorOpenCaptivePortal', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorResetPreferences', this, false, true);
},
get isAboutNetError() {
return content.document.documentURI.startsWith("about:neterror");
},
@@ -343,16 +344,19 @@ var AboutNetAndCertErrorListener = {
if (!this.isAboutNetError && !this.isAboutCertError) {
return;
}
switch (aEvent.type) {
case "AboutNetErrorLoad":
this.onPageLoad(aEvent);
break;
+ case "AboutNetErrorOpenCaptivePortal":
+ this.openCaptivePortalPage(aEvent);
+ break;
case "AboutNetErrorSetAutomatic":
this.onSetAutomatic(aEvent);
break;
case "AboutNetErrorOverride":
this.onOverride(aEvent);
break;
case "AboutNetErrorResetPreferences":
this.onResetPreferences(aEvent);
@@ -385,16 +389,20 @@ var AboutNetAndCertErrorListener = {
automatic: automatic
})
}));
sendAsyncMessage("Browser:SSLErrorReportTelemetry",
{reportStatus: TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN});
},
+ openCaptivePortalPage: function(evt) {
+ sendAsyncMessage("Browser:OpenCaptivePortalPage");
+ },
+
onResetPreferences: function(evt) {
sendAsyncMessage("Browser:ResetSSLPreferences");
},
onSetAutomatic: function(evt) {
sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
automatic: evt.detail
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -34,42 +34,54 @@ button:disabled {
#prefChangeContainer {
display: none;
}
#learnMoreContainer {
display: none;
}
-#certErrorButtonContainer {
+#certErrorAndCaptivePortalButtonContainer {
display: none;
}
-body.certerror #certErrorButtonContainer {
+body:not(.neterror) #certErrorAndCaptivePortalButtonContainer {
display: flex;
}
-body.certerror #netErrorButtonContainer {
+body:not(.neterror) #netErrorButtonContainer {
display: none;
}
#errorTryAgain {
margin-top: 1.2em;
min-width: 150px;
}
#returnButton {
min-width: 250px;
}
#advancedButton {
display: none;
}
-body.certerror #advancedButton {
+body.captiveportal #returnButton {
+ display: none;
+}
+
+body:not(.captiveportal) #openPortalLoginPageButton {
+ display: none;
+}
+
+#openPortalLoginPageButton {
+ margin-inline-start: 0;
+}
+
+body:not(.neterror) #advancedButton {
display: block;
}
#certificateErrorReporting {
display: none;
}
.container {