/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs", }); import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs"; export class NetErrorChild extends RemotePageChild { actorCreated() { super.actorCreated(); // If you add a new function, remember to add it to RemotePageAccessManager.sys.mjs // to allow content-privileged about:neterror or about:certerror to use it. const exportableFunctions = [ "RPMGetAppBuildID", "RPMGetHostForDisplay", "RPMGetInnermostAsciiHost", "RPMRecordGleanEvent", "RPMCheckAlternateHostAvailable", "RPMGetHttpResponseHeader", "RPMIsTRROnlyFailure", "RPMIsFirefox", "RPMOpenPreferences", "RPMHasConnectivity", "RPMGetTRRSkipReason", "RPMGetTRRDomain", "RPMIsSiteSpecificTRRError", "RPMSetTRRDisabledLoadFlags", "RPMGetCurrentTRRMode", "RPMShowOSXLocalNetworkPermissionWarning", ]; this.exportFunctions(exportableFunctions); } getHandshakeCertificates(docShell) { let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo; if (!securityInfo) { return []; } return securityInfo.handshakeCertificates.map(cert => cert.getBase64DERString() ); } handleEvent(aEvent) { // Documents have a null ownerDocument. let doc = aEvent.originalTarget.ownerDocument || aEvent.originalTarget; switch (aEvent.type) { case "click": { let elem = aEvent.originalTarget; if (elem.id == "viewCertificate") { // Call through the superclass to avoid the security check. this.sendAsyncMessage("Browser:CertExceptionError", { location: doc.location.href, elementId: elem.id, handshakeCertificates: this.getHandshakeCertificates( doc.defaultView.docShell ), }); } break; } } } RPMGetHostForDisplay(document) { // Note: not document.documentURIObject, which will be the network error // page's URI - we want the URI of the page that failed to load. let uri = document.mozDocumentURIIfNotForErrorPages; return lazy.BrowserUtils.formatURIForDisplay(uri); } /** * Use this to get the ascii host for the load that showed an error. * Do NOT rely on `document.location.href` or similar as it will not work * reliably for nested URLs like view-source. * * @returns {string} ASCII (potentially punycode) version of the hostname. */ RPMGetInnermostAsciiHost() { let uri = this.contentWindow.document.mozDocumentURIIfNotForErrorPages; if (uri instanceof Ci.nsINestedURI) { uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI; } return uri.asciiHost; } RPMGetAppBuildID() { return Services.appinfo.appBuildID; } RPMRecordGleanEvent(category, name, extra) { Glean[category]?.[name]?.record(extra); } RPMCheckAlternateHostAvailable() { const host = this.contentWindow.location.host.trim(); // Adapted from UrlbarUtils::looksLikeSingleWordHost // https://searchfox.org/mozilla-central/rev/a26af613a476fafe6c3eba05a81bef63dff3c9f1/browser/components/urlbar/UrlbarUtils.sys.mjs#893 const REGEXP_SINGLE_WORD = /^[^\s@:/?#]+(:\d+)?$/; if (!REGEXP_SINGLE_WORD.test(host)) { return; } let info = Services.uriFixup.forceHttpFixup( this.contentWindow.location.href ); if (!info.fixupCreatedAlternateURI && !info.fixupChangedProtocol) { return; } let { displayHost, displaySpec, pathQueryRef } = info.fixedURI; if (pathQueryRef.endsWith("/")) { pathQueryRef = pathQueryRef.slice(0, pathQueryRef.length - 1); } let weakDoc = Cu.getWeakReference(this.contentWindow.document); let onLookupCompleteListener = { onLookupComplete(request, record, status) { let doc = weakDoc.get(); if (!doc || !Components.isSuccessCode(status)) { return; } let link = doc.createElement("a"); link.href = displaySpec; link.setAttribute("data-l10n-name", "website"); let span = doc.createElement("span"); span.appendChild(link); doc.l10n.setAttributes(span, "neterror-dns-not-found-with-suggestion", { hostAndPath: displayHost + pathQueryRef, }); const shortDesc = doc.getElementById("errorShortDesc"); if (shortDesc) { shortDesc.textContent += " "; shortDesc.appendChild(span); } // For Felt Privacy experience (net-error-card), also update the learn more link // to point to the corrected domain instead of the SUMO support page const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject; if (netErrorCard) { const learnMoreLink = netErrorCard.learnMoreLink; if (learnMoreLink) { learnMoreLink.href = displaySpec; } } }, }; try { Services.uriFixup.checkHost( info.fixedURI, onLookupCompleteListener, this.document.nodePrincipal.originAttributes ); } catch (ex) { // Ignore errors. } } // Get the header from the http response of the failed channel. This function // is used in the 'about:neterror' page. RPMGetHttpResponseHeader(responseHeader) { let channel = this.contentWindow.docShell.failedChannel; if (!channel) { return ""; } let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); if (!httpChannel) { return ""; } try { return httpChannel.getResponseHeader(responseHeader); } catch (e) {} return ""; } RPMIsTRROnlyFailure() { // We will only show this in Firefox because the options may direct users to settings only available on Firefox Desktop let channel = this.contentWindow?.docShell?.failedChannel?.QueryInterface( Ci.nsIHttpChannelInternal ); if (!channel) { return false; } return channel.effectiveTRRMode == Ci.nsIRequest.TRR_ONLY_MODE; } RPMIsFirefox() { return lazy.AppInfo.isFirefox; } RPMHasConnectivity() { // Whether the browser has active network interfaces or not. return Services.io.connectivity; } _getTRRSkipReason() { let channel = this.contentWindow?.docShell?.failedChannel?.QueryInterface( Ci.nsIHttpChannelInternal ); return channel?.trrSkipReason ?? Ci.nsITRRSkipReason.TRR_UNSET; } RPMGetTRRSkipReason() { let skipReason = this._getTRRSkipReason(); return Services.dns.getTRRSkipReasonName(skipReason); } RPMGetTRRDomain() { return Services.dns.trrDomain; } RPMIsSiteSpecificTRRError() { let skipReason = this._getTRRSkipReason(); switch (skipReason) { case Ci.nsITRRSkipReason.TRR_NXDOMAIN: case Ci.nsITRRSkipReason.TRR_RCODE_FAIL: case Ci.nsITRRSkipReason.TRR_NO_ANSWERS: return true; } return false; } RPMSetTRRDisabledLoadFlags() { this.contentWindow.docShell.browsingContext.defaultLoadFlags |= Ci.nsIRequest.LOAD_TRR_DISABLED_MODE; } RPMShowOSXLocalNetworkPermissionWarning() { if (!lazy.AppInfo.isMac) { return false; } // Ideally we'd only show this error for local network loads // but right now it's difficult to determine if the socket // was blocked by the OS or if the target port was closed. (bug 1919889) // For now we err on the side of displaying the warning message. let version = parseInt(Services.sysinfo.getProperty("version")); // We only show this error on Sequoia or later return version >= 24; } }