From 7ab8abdd16532710b454ade006c1f0c9f3e65c37 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Thu, 13 Dec 2018 13:47:39 +0000 Subject: [PATCH] Bug 1499092 - move bulk of registerProtocolHandler checks into compiled code so we don't need a dedicated component in the child process, r=nika Differential Revision: https://phabricator.services.mozilla.com/D13697 --HG-- extra : moz-landing-system : lando --- .../components/feeds/BrowserFeeds.manifest | 4 +- .../components/feeds/WebContentConverter.js | 279 +++--------------- .../feeds/test/test_registerHandler.html | 3 +- browser/components/nsBrowserGlue.js | 2 - browser/modules/Feeds.jsm | 23 -- browser/modules/moz.build | 1 - dom/base/Navigator.cpp | 182 +++++++++++- dom/base/Navigator.h | 3 + .../sidebar/nsIWebContentHandlerRegistrar.idl | 5 +- dom/ipc/PBrowser.ipdl | 4 + dom/ipc/TabParent.cpp | 14 + dom/ipc/TabParent.h | 4 + dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/webidl/Navigator.webidl | 4 + .../protocol.https.html.ini | 180 ----------- 15 files changed, 251 insertions(+), 459 deletions(-) delete mode 100644 browser/modules/Feeds.jsm delete mode 100644 testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html.ini diff --git a/browser/components/feeds/BrowserFeeds.manifest b/browser/components/feeds/BrowserFeeds.manifest index b079ebc00075..924b00bbd551 100644 --- a/browser/components/feeds/BrowserFeeds.manifest +++ b/browser/components/feeds/BrowserFeeds.manifest @@ -1,2 +1,2 @@ -component {792a7e82-06a0-437c-af63-b2d12e808acc} WebContentConverter.js -contract @mozilla.org/embeddor.implemented/web-content-handler-registrar;1 {792a7e82-06a0-437c-af63-b2d12e808acc} +component {792a7e82-06a0-437c-af63-b2d12e808acc} WebContentConverter.js process=main +contract @mozilla.org/embeddor.implemented/web-content-handler-registrar;1 {792a7e82-06a0-437c-af63-b2d12e808acc} process=main diff --git a/browser/components/feeds/WebContentConverter.js b/browser/components/feeds/WebContentConverter.js index c313cae2a089..5205c69ba329 100644 --- a/browser/components/feeds/WebContentConverter.js +++ b/browser/components/feeds/WebContentConverter.js @@ -5,135 +5,11 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); - -function LOG(str) { - // dump("*** " + str + "\n"); -} const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}"); -const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}"); -const WCC_CLASSNAME = "Web Service Handler"; - -const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external"; - const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties"; -const NS_ERROR_MODULE_DOM = 2152923136; -const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12; - -const Utils = { - makeURI(aURL, aOriginCharset, aBaseURI) { - return Services.io.newURI(aURL, aOriginCharset, aBaseURI); - }, - - checkAndGetURI(aURIString, aContentWindow) { - let uri; - try { - let baseURI = aContentWindow.document.baseURIObject; - uri = this.makeURI(aURIString, null, baseURI); - } catch (ex) { - throw NS_ERROR_DOM_SYNTAX_ERR; - } - - // For security reasons we reject non-http(s) urls (see bug 354316), - // we may need to revise this once we support more content types - if (uri.scheme != "http" && uri.scheme != "https") { - throw this.getSecurityError( - "Permission denied to add " + uri.spec + " as a content or protocol handler", - aContentWindow); - } - - // We also reject handlers registered from a different host (see bug 402287) - if (!["http:", "https:"].includes(aContentWindow.location.protocol) || - aContentWindow.location.hostname != uri.host) { - throw this.getSecurityError( - "Permission denied to add " + uri.spec + " as a content or protocol handler", - aContentWindow); - } - - // If the uri doesn't contain '%s', it won't be a good handler - if (!uri.spec.includes("%s")) - throw NS_ERROR_DOM_SYNTAX_ERR; - - return uri; - }, - - // This list should be kept up-to-date with the spec: - // https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers - _safeProtocols: new Set([ - "bitcoin", - "geo", - "im", - "irc", - "ircs", - "magnet", - "mailto", - "mms", - "news", - "nntp", - "openpgp4fpr", - "sip", - "sms", - "smsto", - "ssh", - "tel", - "urn", - "webcal", - "wtai", - "xmpp", - ]), - - // NB: Throws if aProtocol is not allowed. - checkProtocolHandlerAllowed(aProtocol, aURIString, aWindowOrNull) { - if (aProtocol.startsWith("web+")) { - if (!/[a-z]+/.test(aProtocol.substring(4 /* web+ */))) { - throw this.getSecurityError( - `Permission denied to add a protocol handler for ${aProtocol}`, - aWindowOrNull); - } - } else if (!this._safeProtocols.has(aProtocol)) { - throw this.getSecurityError( - `Permission denied to add a protocol handler for ${aProtocol}`, - aWindowOrNull); - } - - // First, check to make sure this isn't already handled internally (we don't - // want to let them take over, say "chrome"). - let handler = Services.io.getProtocolHandler(aProtocol); - if (!(handler instanceof Ci.nsIExternalProtocolHandler)) { - // This is handled internally, so we don't want them to register - throw this.getSecurityError( - `Permission denied to add ${aURIString} as a protocol handler`, - aWindowOrNull); - } - - // check if it is in the black list - let pb = Services.prefs; - let allowed = - pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol, - pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default")); - if (!allowed) { - throw this.getSecurityError( - `Not allowed to register a protocol handler for ${aProtocol}`, - aWindowOrNull); - } - }, - - // Return a SecurityError exception from the given Window if one is given. If - // none is given, just return the given error string, for lack of anything - // better. - getSecurityError(errorString, aWindowOrNull) { - if (!aWindowOrNull) { - return errorString; - } - - return new aWindowOrNull.DOMException(errorString, "SecurityError"); - }, -}; - function WebContentConverterRegistrar() { } @@ -191,8 +67,9 @@ WebContentConverterRegistrar.prototype = { for (let i = 0; i < handlers.length; i++) { try { // We only want to test web handlers let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); - if (handler.uriTemplate == aURITemplate) + if (handler.uriTemplate == aURITemplate) { return true; + } } catch (e) { /* it wasn't a web handler */ } } return false; @@ -201,79 +78,67 @@ WebContentConverterRegistrar.prototype = { /** * See nsIWebContentHandlerRegistrar */ - registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) { + registerProtocolHandler(aProtocol, aURI, aTitle, aDocumentURI, aBrowserOrWindow) { aProtocol = (aProtocol || "").toLowerCase(); - LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")"); - let haveWindow = (aBrowserOrWindow instanceof Ci.nsIDOMWindow); - let uri; - if (haveWindow) { - uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow); - } else { - // aURIString must not be a relative URI. - uri = Utils.makeURI(aURIString, null); + if (!aURI || !aDocumentURI) { + return; + } + + let browser = aBrowserOrWindow; // This is the e10s case. + if (aBrowserOrWindow instanceof Ci.nsIDOMWindow) { + // In the non-e10s case, grab the browser off the same-process window. + let rootDocShell = aBrowserOrWindow.docShell.sameTypeRootTreeItem; + browser = rootDocShell.QueryInterface(Ci.nsIDocShell).chromeEventHandler; + } + + let browserWindow = browser.ownerGlobal; + try { + browserWindow.navigator.checkProtocolHandlerAllowed(aProtocol, aURI, aDocumentURI); + } catch (ex) { + // We should have already shown the user an error. + return; } // If the protocol handler is already registered, just return early. - if (this._protocolHandlerRegistered(aProtocol, uri.spec)) { + if (this._protocolHandlerRegistered(aProtocol, aURI.spec)) { return; } - let browser; - if (haveWindow) { - let browserWindow = - this._getBrowserWindowForContentWindow(aBrowserOrWindow); - browser = this._getBrowserForContentWindow(browserWindow, - aBrowserOrWindow); - } else { - browser = aBrowserOrWindow; - } - if (PrivateBrowsingUtils.isBrowserPrivate(browser)) { - // Inside the private browsing mode, we don't want to alert the user to save - // a protocol handler. We log it to the error console so that web developers - // would have some way to tell what's going wrong. - Services.console. - logStringMessage("Web page denied access to register a protocol handler inside private browsing mode"); - return; - } - - Utils.checkProtocolHandlerAllowed(aProtocol, aURIString, - haveWindow ? aBrowserOrWindow : null); - // Now Ask the user and provide the proper callback let message = this._getFormattedString("addProtocolHandlerMessage", - [uri.host, aProtocol]); + [aURI.host, aProtocol]); - let notificationIcon = uri.prePath + "/favicon.ico"; + let notificationIcon = aURI.prePath + "/favicon.ico"; let notificationValue = "Protocol Registration: " + aProtocol; let addButton = { label: this._getString("addProtocolHandlerAddButton"), accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"), - protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle }, + protocolInfo: { protocol: aProtocol, uri: aURI.spec, name: aTitle }, callback(aNotification, aButtonInfo) { - let protocol = aButtonInfo.protocolInfo.protocol; - let name = aButtonInfo.protocolInfo.name; + let protocol = aButtonInfo.protocolInfo.protocol; + let name = aButtonInfo.protocolInfo.name; - let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. - createInstance(Ci.nsIWebHandlerApp); - handler.name = name; - handler.uriTemplate = aButtonInfo.protocolInfo.uri; + let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. + createInstance(Ci.nsIWebHandlerApp); + handler.name = name; + handler.uriTemplate = aButtonInfo.protocolInfo.uri; - let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. - getService(Ci.nsIExternalProtocolService); - let handlerInfo = eps.getProtocolHandlerInfo(protocol); - handlerInfo.possibleApplicationHandlers.appendElement(handler); + let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. + getService(Ci.nsIExternalProtocolService); + let handlerInfo = eps.getProtocolHandlerInfo(protocol); + handlerInfo.possibleApplicationHandlers.appendElement(handler); - // Since the user has agreed to add a new handler, chances are good - // that the next time they see a handler of this type, they're going - // to want to use it. Reset the handlerInfo to ask before the next - // use. - handlerInfo.alwaysAskBeforeHandling = true; + // Since the user has agreed to add a new handler, chances are good + // that the next time they see a handler of this type, they're going + // to want to use it. Reset the handlerInfo to ask before the next + // use. + handlerInfo.alwaysAskBeforeHandling = true; - let hs = Cc["@mozilla.org/uriloader/handler-service;1"]. - getService(Ci.nsIHandlerService); - hs.store(handlerInfo); - }, + let hs = Cc["@mozilla.org/uriloader/handler-service;1"]. + getService(Ci.nsIHandlerService); + hs.store(handlerInfo); + }, }; let notificationBox = browser.getTabBrowser().getNotificationBox(browser); notificationBox.appendNotification(message, @@ -283,60 +148,6 @@ WebContentConverterRegistrar.prototype = { [addButton]); }, - /** - * Returns the browser chrome window in which the content window is in - */ - _getBrowserWindowForContentWindow(aContentWindow) { - return aContentWindow.docShell.rootTreeItem.domWindow - .wrappedJSObject; - }, - - /** - * Returns the element associated with the given content - * window. - * - * @param aBrowserWindow - * The browser window in which the content window is in. - * @param aContentWindow - * The content window. It's possible to pass a child content window - * (i.e. the content window of a frame/iframe). - */ - _getBrowserForContentWindow(aBrowserWindow, aContentWindow) { - // This depends on pseudo APIs of browser.js and tabbrowser.xml - aContentWindow = aContentWindow.top; - return aBrowserWindow.gBrowser.browsers.find((browser) => - browser.contentWindow == aContentWindow); - }, - - classID: WCCR_CLASSID, - - /** - * See nsISupports - */ - QueryInterface: ChromeUtils.generateQI([Ci.nsIWebContentHandlerRegistrar]), -}; - -function WebContentConverterRegistrarContent() { -} - -WebContentConverterRegistrarContent.prototype = { - registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) { - aProtocol = (aProtocol || "").toLowerCase(); - // aBrowserOrWindow must be a window. - let messageManager = aBrowserOrWindow.docShell - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsITabChild) - .messageManager; - - let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow); - Utils.checkProtocolHandlerAllowed(aProtocol, aURIString, aBrowserOrWindow); - - messageManager.sendAsyncMessage("WCCR:registerProtocolHandler", - { protocol: aProtocol, - uri: uri.spec, - title: aTitle }); - }, - classID: WCCR_CLASSID, /** @@ -346,6 +157,4 @@ WebContentConverterRegistrarContent.prototype = { }; this.NSGetFactory = - (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ? - XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrarContent]) : - XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]); + XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]); diff --git a/browser/components/feeds/test/test_registerHandler.html b/browser/components/feeds/test/test_registerHandler.html index 7e921c9b0d23..f33219b137ab 100644 --- a/browser/components/feeds/test/test_registerHandler.html +++ b/browser/components/feeds/test/test_registerHandler.html @@ -51,8 +51,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788 is(testRegisterHandler(true, "fweb+oo", "http://remotehost:8888/%s", "Foo handler"), false, "registering a web+foo protocol handler with a different host should not work"); // restriction to http(s) for the uri of the handler (bug 401343) - // https should work (http already tested in the generic case) - is(testRegisterHandler(true, "web+foo", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a web+foo protocol handler with https scheme should work"); + // http is already tested in the generic case // ftp should not work is(testRegisterHandler(true, "web+foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a web+foo protocol handler with ftp scheme should not work"); // chrome should not work diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 5aa50a8b667f..c8610589446e 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -403,7 +403,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { DateTimePickerParent: "resource://gre/modules/DateTimePickerParent.jsm", Discovery: "resource:///modules/Discovery.jsm", ExtensionsUI: "resource:///modules/ExtensionsUI.jsm", - Feeds: "resource:///modules/Feeds.jsm", FileSource: "resource://gre/modules/L10nRegistry.jsm", FormValidationHandler: "resource:///modules/FormValidationHandler.jsm", FxAccounts: "resource://gre/modules/FxAccounts.jsm", @@ -532,7 +531,6 @@ const listeners = { "RemoteLogins:removeLogin": ["LoginManagerParent"], "RemoteLogins:insecureLoginFormPresent": ["LoginManagerParent"], // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js - "WCCR:registerProtocolHandler": ["Feeds"], "rtcpeer:CancelRequest": ["webrtcUI"], "rtcpeer:Request": ["webrtcUI"], "webrtc:CancelRequest": ["webrtcUI"], diff --git a/browser/modules/Feeds.jsm b/browser/modules/Feeds.jsm deleted file mode 100644 index a2dfc76e75ec..000000000000 --- a/browser/modules/Feeds.jsm +++ /dev/null @@ -1,23 +0,0 @@ -/* 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/. */ - -"use strict"; - -var EXPORTED_SYMBOLS = [ "Feeds" ]; - -var Feeds = { - // Listeners are added in nsBrowserGlue.js - receiveMessage(aMessage) { - let data = aMessage.data; - switch (aMessage.name) { - case "WCCR:registerProtocolHandler": { - let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. - getService(Ci.nsIWebContentHandlerRegistrar); - registrar.registerProtocolHandler(data.protocol, data.uri, data.title, - aMessage.target); - break; - } - } - }, -}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index 73e644400d00..cd0e115c30a2 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -136,7 +136,6 @@ EXTRA_JS_MODULES += [ 'Discovery.jsm', 'ExtensionsUI.jsm', 'FaviconLoader.jsm', - 'Feeds.jsm', 'FormValidationHandler.jsm', 'HomePage.jsm', 'LaterRun.jsm', diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 0a12a40454de..0e3aa5cd29af 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -74,6 +74,10 @@ #include "nsIPresentationService.h" #include "nsIScriptError.h" +#include "nsIExternalProtocolHandler.h" +#include "TabChild.h" +#include "URIUtils.h" + #include "mozilla/dom/MediaDevices.h" #include "MediaManager.h" @@ -821,27 +825,179 @@ void Navigator::RegisterContentHandler(const nsAString& aMIMEType, const nsAString& aTitle, ErrorResult& aRv) {} -void Navigator::RegisterProtocolHandler(const nsAString& aProtocol, - const nsAString& aURI, - const nsAString& aTitle, - ErrorResult& aRv) { - if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) { +// This list should be kept up-to-date with the spec: +// https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers +static const char* const kSafeSchemes[] = { + "bitcoin", "geo", "im", "irc", "ircs", "magnet", "mailto", + "mms", "news", "nntp", "openpgp4fpr", "sip", "sms", "smsto", + "ssh", "tel", "urn", "webcal", "wtai", "xmpp"}; + +void Navigator::CheckProtocolHandlerAllowed(const nsAString& aScheme, + nsIURI* aHandlerURI, + nsIURI* aDocumentURI, + ErrorResult& aRv) { + auto raisePermissionDeniedHandler = [&] { + nsAutoCString spec; + aHandlerURI->GetSpec(spec); + nsPrintfCString message("Permission denied to add %s as a protocol handler", + spec.get()); + aRv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message); + }; + + auto raisePermissionDeniedScheme = [&] { + nsPrintfCString message( + "Permission denied to add a protocol handler for %s", + NS_ConvertUTF16toUTF8(aScheme).get()); + aRv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message); + }; + + if (!aDocumentURI || !aHandlerURI) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return; } - if (!mWindow->IsSecureContext() && mWindow->GetDoc()) { - mWindow->GetDoc()->WarnOnceAbout( - nsIDocument::eRegisterProtocolHandlerInsecure); + nsCString spec; + aHandlerURI->GetSpec(spec); + // If the uri doesn't contain '%s', it won't be a good handler - the %s + // gets replaced with the handled URI. + if (!FindInReadable(NS_LITERAL_CSTRING("%s"), spec)) { + aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + // For security reasons we reject non-http(s) urls (see bug 354316), + nsAutoCString docScheme; + nsAutoCString handlerScheme; + aDocumentURI->GetScheme(docScheme); + aHandlerURI->GetScheme(handlerScheme); + if ((!docScheme.EqualsLiteral("https") && !docScheme.EqualsLiteral("http")) || + (!handlerScheme.EqualsLiteral("https") && + !handlerScheme.EqualsLiteral("http"))) { + raisePermissionDeniedHandler(); + return; + } + + // Should be same-origin: + nsAutoCString handlerHost; + aHandlerURI->GetHostPort(handlerHost); + nsAutoCString documentHost; + aDocumentURI->GetHostPort(documentHost); + if (!handlerHost.Equals(documentHost) || !handlerScheme.Equals(docScheme)) { + raisePermissionDeniedHandler(); + return; + } + + // Having checked the handler URI, check the scheme: + nsAutoCString scheme; + ToLowerCase(NS_ConvertUTF16toUTF8(aScheme), scheme); + if (StringBeginsWith(scheme, NS_LITERAL_CSTRING("web+"))) { + // Check for non-ascii + nsReadingIterator iter; + nsReadingIterator iterEnd; + auto remainingScheme = Substring(scheme, 4 /* web+ */); + remainingScheme.BeginReading(iter); + remainingScheme.EndReading(iterEnd); + // Scheme suffix must be non-empty + if (iter == iterEnd) { + raisePermissionDeniedScheme(); + return; + } + for (; iter != iterEnd; iter++) { + if (*iter < 'a' || *iter > 'z') { + raisePermissionDeniedScheme(); + return; + } + } + } else { + bool matches = false; + for (const char* safeScheme : kSafeSchemes) { + if (scheme.Equals(safeScheme)) { + matches = true; + break; + } + } + if (!matches) { + raisePermissionDeniedScheme(); + return; + } + } + + nsCOMPtr handler; + nsCOMPtr io = services::GetIOService(); + if (NS_FAILED( + io->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)))) { + raisePermissionDeniedScheme(); + return; + } + + // Check to make sure this isn't already handled internally (we don't + // want to let them take over, say "chrome"). In theory, the checks above + // should have already taken care of this. + nsCOMPtr externalHandler = + do_QueryInterface(handler); + MOZ_RELEASE_ASSERT( + externalHandler, + "We should never allow overriding a builtin protocol handler"); + + // check if we have prefs set saying not to add this. + bool defaultExternal = + Preferences::GetBool("network.protocol-handler.external-default"); + nsPrintfCString specificPref("network.protocol-handler.external.%s", + scheme.get()); + if (!Preferences::GetBool(specificPref.get(), defaultExternal)) { + raisePermissionDeniedScheme(); + return; + } +} + +void Navigator::RegisterProtocolHandler(const nsAString& aScheme, + const nsAString& aURI, + const nsAString& aTitle, + ErrorResult& aRv) { + if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell() || + !mWindow->GetDoc()) { + return; + } + nsCOMPtr loadContext = do_GetInterface(mWindow); + if (loadContext->UsePrivateBrowsing()) { + // If we're a private window, don't alert the user or webpage. We log to the + // console so that web developers have some way to tell what's going wrong. + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), + mWindow->GetDoc(), nsContentUtils::eDOM_PROPERTIES, + "RegisterProtocolHandlerPrivateBrowsingWarning"); + return; + } + + nsCOMPtr doc = mWindow->GetDoc(); + if (!mWindow->IsSecureContext()) { + doc->WarnOnceAbout(nsIDocument::eRegisterProtocolHandlerInsecure); + } + + // Determine if doc is allowed to assign this handler + nsIURI* docURI = doc->GetDocumentURIObject(); + nsCOMPtr handlerURI; + NS_NewURI(getter_AddRefs(handlerURI), NS_ConvertUTF16toUTF8(aURI), + doc->GetDocumentCharacterSet(), docURI); + CheckProtocolHandlerAllowed(aScheme, handlerURI, docURI, aRv); + if (aRv.Failed()) { + return; + } + + if (XRE_IsContentProcess()) { + nsAutoString scheme(aScheme); + nsAutoString title(aTitle); + RefPtr tabChild = TabChild::GetFrom(mWindow); + tabChild->SendRegisterProtocolHandler(scheme, handlerURI, title, docURI); + return; } nsCOMPtr registrar = do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID); - if (!registrar) { - return; + if (registrar) { + aRv = registrar->RegisterProtocolHandler(aScheme, handlerURI, aTitle, + docURI, mWindow->GetOuterWindow()); } - - aRv = registrar->RegisterProtocolHandler(aProtocol, aURI, aTitle, - mWindow->GetOuterWindow()); } Geolocation* Navigator::GetGeolocation(ErrorResult& aRv) { diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index b25029498285..1cbda0cf06a7 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -113,6 +113,9 @@ class Navigator final : public nsISupports, public nsWrapperCache { void GetUserAgent(nsAString& aUserAgent, CallerType aCallerType, ErrorResult& aRv) const; bool OnLine(); + void CheckProtocolHandlerAllowed(const nsAString& aScheme, + nsIURI* aHandlerURI, nsIURI* aDocumentURI, + ErrorResult& aRv); void RegisterProtocolHandler(const nsAString& aScheme, const nsAString& aURL, const nsAString& aTitle, ErrorResult& aRv); void RegisterContentHandler(const nsAString& aMIMEType, const nsAString& aURL, diff --git a/dom/interfaces/sidebar/nsIWebContentHandlerRegistrar.idl b/dom/interfaces/sidebar/nsIWebContentHandlerRegistrar.idl index dd6a41d1f60f..703075eb2057 100644 --- a/dom/interfaces/sidebar/nsIWebContentHandlerRegistrar.idl +++ b/dom/interfaces/sidebar/nsIWebContentHandlerRegistrar.idl @@ -6,6 +6,8 @@ #include "nsISupports.idl" +interface nsIURI; + /** * nsIWebContentHandlerRegistrar * @@ -26,8 +28,9 @@ interface nsIWebContentHandlerRegistrar : nsISupports * content window from which the method has been called. */ void registerProtocolHandler(in AString protocol, - in AString uri, + in nsIURI uri, in AString title, + in nsIURI documentURI, in nsISupports windowOrBrowser); /** * Removes a registered protocol handler diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 83e3c040fa47..5b4f268ce92f 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -88,6 +88,7 @@ using mozilla::FontRange from "ipc/nsGUIEventIPC.h"; using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h"; using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h"; +using refcounted class nsIURI from "mozilla/ipc/URIUtils.h"; namespace mozilla { namespace dom { @@ -521,6 +522,9 @@ parent: async SetHasBeforeUnload(bool aHasBeforeUnload); + async RegisterProtocolHandler(nsString scheme, nsIURI handlerURI, nsString title, + nsIURI documentURI); + child: async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index f4c55ec788aa..ed41462167d5 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -63,6 +63,7 @@ #include "nsIURI.h" #include "nsIWindowWatcher.h" #include "nsIWebBrowserChrome.h" +#include "nsIWebContentHandlerRegistrar.h" #include "nsIXULBrowserWindow.h" #include "nsIXULWindow.h" #include "nsViewManager.h" @@ -2099,6 +2100,19 @@ mozilla::ipc::IPCResult TabParent::RecvSetHasBeforeUnload( return IPC_OK(); } +mozilla::ipc::IPCResult TabParent::RecvRegisterProtocolHandler( + const nsString& aScheme, nsIURI* aHandlerURI, const nsString& aTitle, + nsIURI* aDocURI) { + nsCOMPtr registrar = + do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID); + if (registrar) { + registrar->RegisterProtocolHandler(aScheme, aHandlerURI, aTitle, aDocURI, + mFrameElement); + } + + return IPC_OK(); +} + bool TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) { nsCOMPtr widget = GetWidget(); if (!widget) { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 7784bf794bf8..f40e78c717b4 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -156,6 +156,10 @@ class TabParent final : public PBrowserParent, virtual mozilla::ipc::IPCResult RecvSetHasBeforeUnload( const bool& aHasBeforeUnload) override; + virtual mozilla::ipc::IPCResult RecvRegisterProtocolHandler( + const nsString& aScheme, nsIURI* aHandlerURI, const nsString& aTitle, + nsIURI* aDocURI) override; + virtual mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow( PBrowserParent* aOpener, const nsString& aURL, const nsString& aName, const nsString& aFeatures, diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 6c7441daccc3..03edbbe7abb4 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -344,6 +344,8 @@ InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid accor ReadableStreamReadingFailed=Failed to read data from the ReadableStream: “%S”. # LOCALIZATION NOTE: Do not translate "registerProtocolHandler". RegisterProtocolHandlerInsecureWarning=Use of the registerProtocolHandler for insecure connections will be removed in version 62. +# LOCALIZATION NOTE: Do not translate "registerProtocolHandler" +RegisterProtocolHandlerPrivateBrowsingWarning=Can’t use registerProtocolHandler inside private browsing mode. MixedDisplayObjectSubrequestWarning=Loading insecure content within a plugin embedded in a secure connection is going to be removed. MotionEventWarning=Use of the motion sensor is deprecated. OrientationEventWarning=Use of the orientation sensor is deprecated. diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 8a12b147e160..954c8d222147 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -23,6 +23,8 @@ * and create derivative works of this document. */ +interface URI; + // http://www.whatwg.org/specs/web-apps/current-work/#the-navigator-object [HeaderFile="Navigator.h"] interface Navigator { @@ -80,6 +82,8 @@ interface NavigatorOnLine { [NoInterfaceObject] interface NavigatorContentUtils { // content handler registration + [Throws, ChromeOnly] + void checkProtocolHandlerAllowed(DOMString scheme, URI handlerURI, URI documentURI); [Throws, Func="nsGlobalWindowInner::RegisterProtocolHandlerAllowedForContext"] void registerProtocolHandler(DOMString scheme, DOMString url, DOMString title); [Pref="dom.registerContentHandler.enabled", Throws] diff --git a/testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html.ini b/testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html.ini deleted file mode 100644 index 26607c8a3f22..000000000000 --- a/testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html.ini +++ /dev/null @@ -1,180 +0,0 @@ -[protocol.https.html] - [a url argument pointing to a different domain name, without %s should throw SYNTAX_ERR] - expected: FAIL - - [a protocol argument containing non-alphanumeric characters (like a cyrillic “а”) should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the mocha protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the res protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the view-source protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the blob protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [%s instead of domain name should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing :// should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the opera protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the cid protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument pointing to a non-http[s\] scheme should throw SECURITY_ERR due to not being of the same origin] - expected: - if os == "android": FAIL - - [a protocol argument containing an unrecognized scheme should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument pointing to a different domain name should throw SECURITY_ERR (2)] - expected: - if os == "android": FAIL - - [attempting to override the javascript protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the operamail protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument without %s (but with %) should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing : should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the http protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [an empty url argument should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the wyciwyg protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing a backspace character should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the tcl protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument without %s should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the about protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the chrome protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing http:// should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the wss protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing a LF character should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the mid protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument pointing to a different domain name should throw SECURITY_ERR (3)] - expected: - if os == "android": FAIL - - [attempting to override the ftp protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [%s instead of subdomain name should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the file protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the vbscript protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [looping handlers should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument without %s (but with %a) should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the attachment protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the shttp protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a protocol argument containing a null character should throw SYNTAX_ERR] - expected: - if os == "android": FAIL - - [attempting to override the livescript protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the https protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [a url argument pointing to a different domain name should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the data protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the resource protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL - - [attempting to override the ws protocol should throw SECURITY_ERR] - expected: - if os == "android": FAIL -