Bug 1793463 - Part 5: Stop using contractids to fetch protocol handlers, r=necko-reviewers,xpcom-reviewers,webdriver-reviewers,whimboo,valentin,kmag

This patch replaces the previous ContractID-based lookup system for protocol
handlers, and replaces it with a new custom system in nsIOService. It will be
pre-populated with non-overridable static protocol handlers using the
StaticComponents infrastructure added in the previous part, and callers can
also dynamically register new protocol handlers at runtime.

This new system is intended to provide access to the default port and
non-dynamic protocol flags off-main-thread, by requiring these values to be
provided up-front as constants, rather than getting them from the xpcom
interface. The data is then guarded by an RWLock.

Callers which look up specific handlers by their contractID are not changed, as
the contract IDs for existing handlers have not been changed, so the lookup
will still succeed.

This change as-implemented breaks the nsGIOProtocolHandler on Linux, as it
removes the special code which would try to use that handler for some
protocols. This will be fixed in a later part by making the
nsGIOProtocolHandler use the dynamic registration APIs to register and
un-register protocol handlers at runtime in response to the GIO pref.

Differential Revision: https://phabricator.services.mozilla.com/D162804
This commit is contained in:
Nika Layzell 2022-12-01 15:43:19 +00:00
parent bca0a6965b
commit 98304d1200
20 changed files with 449 additions and 309 deletions

View File

@ -104,13 +104,7 @@ function getTopWin({ skipPopups, forceNonPrivate } = {}) {
}
function doGetProtocolFlags(aURI) {
let handler = Services.io.getProtocolHandler(aURI.scheme);
// see DoGetProtocolFlags in nsIProtocolHandler.idl
return handler instanceof Ci.nsIProtocolHandlerWithDynamicFlags
? handler
.QueryInterface(Ci.nsIProtocolHandlerWithDynamicFlags)
.getFlagsForURI(aURI)
: handler.protocolFlags;
return Services.io.getDynamicProtocolFlags(aURI);
}
/**

View File

@ -7,14 +7,11 @@
var manifests = [do_get_file("data/test_no_remote_registration.manifest")];
registerManifests(manifests);
function ProtocolHandler(aScheme, aFlags) {
function ProtocolHandler(aScheme) {
this.scheme = aScheme;
this.protocolFlags = aFlags;
this.contractID = "@mozilla.org/network/protocol;1?name=" + aScheme;
}
ProtocolHandler.prototype = {
defaultPort: -1,
allowPort: () => false,
newChannel() {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
@ -28,7 +25,6 @@ var testProtocols = [
{
scheme: "moz-protocol-ui-resource",
flags: Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE,
CID: Components.ID("{d6dedc93-558f-44fe-90f4-3b4bba8a0b14}"),
shouldRegister: false,
},
// It doesn't matter if it has this flag - the only flag we accept is
@ -36,21 +32,18 @@ var testProtocols = [
{
scheme: "moz-protocol-local-file",
flags: Ci.nsIProtocolHandler.URI_IS_LOCAL_FILE,
CID: Components.ID("{ee30d594-0a2d-4f47-89cc-d4cde320ab69}"),
shouldRegister: false,
},
// This clearly is non-local
{
scheme: "moz-protocol-loadable-by-anyone",
flags: Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
CID: Components.ID("{c3735f23-3b0c-4a33-bfa0-79436dcd40b2}"),
shouldRegister: false,
},
// This should always be last (unless we add more flags that are OK)
{
scheme: "moz-protocol-local-resource",
flags: Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
CID: Components.ID("{b79e977c-f840-469a-b413-0125cc1b62a5}"),
shouldRegister: true,
},
];
@ -77,35 +70,17 @@ function run_test() {
},
};
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
// Create factories
let factories = [];
for (let i = 0; i < testProtocols.length; i++) {
factories[i] = {
scheme: testProtocols[i].scheme,
flags: testProtocols[i].flags,
CID: testProtocols[i].CID,
contractID:
"@mozilla.org/network/protocol;1?name=" + testProtocols[i].scheme,
createInstance(aIID) {
let handler = new ProtocolHandler(this.scheme, this.flags, this.CID);
return handler.QueryInterface(aIID);
},
};
}
// Register our factories
for (let i = 0; i < factories.length; i++) {
let factory = factories[i];
registrar.registerFactory(
factory.CID,
"test-" + factory.scheme,
factory.contractID,
factory
for (let protocol of testProtocols) {
Services.io.registerProtocolHandler(
protocol.scheme,
new ProtocolHandler(protocol.scheme),
protocol.flags,
-1
);
}
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
// Register the XULAppInfoFactory
// Make sure the class ID has not already been registered
let old_factory = { CID: "", factory: null };
@ -209,10 +184,9 @@ function run_test() {
}
}
// Unregister our factories so we do not leak
for (let i = 0; i < factories.length; i++) {
let factory = factories[i];
registrar.unregisterFactory(factory.CID, factory);
// Unregister our protocol handlers so we do not leak
for (let protocol of testProtocols) {
Services.io.unregisterProtocolHandler(protocol.scheme);
}
// Unregister XULAppInfoFactory

View File

@ -99,14 +99,6 @@ CustomProtocol.prototype = {
get scheme() {
return PROTOCOL_SCHEME;
},
get protocolFlags() {
return (Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD);
},
get defaultPort() {
return -1;
},
allowPort: function allowPort() {
return false;
},
@ -118,17 +110,17 @@ CustomProtocol.prototype = {
var gFactory = {
register() {
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
var classID = Components.ID("{ed064287-1e76-49ba-a28d-dc74394a8334}");
var description = PROTOCOL_SCHEME + ": protocol";
var contractID = "@mozilla.org/network/protocol;1?name=" + PROTOCOL_SCHEME;
var factory = ComponentUtils.generateSingletonFactory(CustomProtocol);
registrar.registerFactory(classID, description, contractID, factory);
Services.io.registerProtocolHandler(
PROTOCOL_SCHEME,
new CustomProtocol(),
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
-1
);
this.unregister = function() {
registrar.unregisterFactory(classID, factory);
Services.io.unregisterProtocolHandler(PROTOCOL_SCHEME);
delete this.unregister;
};
},

View File

@ -18,10 +18,7 @@ var uris = [
function run_test() {
for (let i = 0; i < uris.length; i++) {
let uri = ios.newURI(uris[i].uri);
let handler = ios
.getProtocolHandler(uri.scheme)
.QueryInterface(Ci.nsIProtocolHandler);
let flags = handler.protocolFlags;
let flags = ios.getDynamicProtocolFlags(uri);
Assert.equal(
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE & flags,

View File

@ -1261,19 +1261,11 @@ nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIProtocolHandler> handler;
rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t flags;
rv = handler->DoGetProtocolFlags(uri, &flags);
rv = ios->GetDynamicProtocolFlags(uri, &flags);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t securityFlagsSet = 0;

View File

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "ProtocolHandlerInfo.h"
#include "StaticComponents.h"
#include "nsIProtocolHandler.h"
namespace mozilla::net {
uint32_t ProtocolHandlerInfo::StaticProtocolFlags() const {
uint32_t flags = mInner.match(
[&](const xpcom::StaticProtocolHandler* handler) {
return handler->mProtocolFlags;
},
[&](const RuntimeProtocolHandler& handler) {
return handler.mProtocolFlags;
});
#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED
MOZ_RELEASE_ASSERT(!(flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC),
"ORIGIN_IS_FULL_SPEC is unsupported but used");
#endif
return flags;
}
int32_t ProtocolHandlerInfo::DefaultPort() const {
return mInner.match(
[&](const xpcom::StaticProtocolHandler* handler) {
return handler->mDefaultPort;
},
[&](const RuntimeProtocolHandler& handler) {
return handler.mDefaultPort;
});
}
nsresult ProtocolHandlerInfo::DynamicProtocolFlags(nsIURI* aURI,
uint32_t* aFlags) const {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
// If we're querying dynamic flags, we'll need to fetch the actual xpcom
// component in order to check them.
if (HasDynamicFlags()) {
nsCOMPtr<nsIProtocolHandler> handler = Handler();
if (nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dynamic =
do_QueryInterface(handler)) {
nsresult rv = dynamic->GetFlagsForURI(aURI, aFlags);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_DIAGNOSTIC_ASSERT(
(StaticProtocolFlags() & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS) ==
(*aFlags & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS),
"only DYNAMIC_URI_FLAGS may be changed by a "
"nsIProtocolHandlerWithDynamicFlags implementation");
return NS_OK;
}
}
// Otherwise, just check against static flags.
*aFlags = StaticProtocolFlags();
return NS_OK;
}
bool ProtocolHandlerInfo::HasDynamicFlags() const {
return mInner.match(
[&](const xpcom::StaticProtocolHandler* handler) {
return handler->mHasDynamicFlags;
},
[&](const RuntimeProtocolHandler&) { return false; });
}
already_AddRefed<nsIProtocolHandler> ProtocolHandlerInfo::Handler() const {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIProtocolHandler> retval;
mInner.match(
[&](const xpcom::StaticProtocolHandler* handler) {
retval = handler->Module().GetService();
},
[&](const RuntimeProtocolHandler& handler) {
retval = handler.mHandler.get();
});
return retval.forget();
}
} // namespace mozilla::net

View File

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_net_ProtocolHandlerInfo_h
#define mozilla_net_ProtocolHandlerInfo_h
#include "mozilla/Variant.h"
#include "nsProxyRelease.h"
#include "nsIProtocolHandler.h"
namespace mozilla {
namespace xpcom {
struct StaticProtocolHandler;
}
namespace net {
struct RuntimeProtocolHandler {
nsMainThreadPtrHandle<nsIProtocolHandler> mHandler;
uint32_t mProtocolFlags;
int32_t mDefaultPort;
};
// Information about a specific protocol handler.
class ProtocolHandlerInfo {
public:
explicit ProtocolHandlerInfo(const xpcom::StaticProtocolHandler& aStatic)
: mInner(AsVariant(&aStatic)) {}
explicit ProtocolHandlerInfo(RuntimeProtocolHandler aDynamic)
: mInner(AsVariant(std::move(aDynamic))) {}
// Returns the statically known protocol-specific flags.
// See `nsIProtocolHandler` for valid values.
uint32_t StaticProtocolFlags() const;
// The port that this protocol normally uses.
// If a port does not make sense for the protocol (e.g., "about:") then -1
// will be returned.
int32_t DefaultPort() const;
// If true, `DynamicProtocolFlags()` may return a different value than
// `StaticProtocolFlags()` for flags in `DYNAMIC_URI_FLAGS`, due to a
// `nsIProtocolHandlerWithDynamicFlags` implementation.
bool HasDynamicFlags() const;
// Like `StaticProtocolFlags()` but also checks
// `nsIProtocolHandlerWithDynamicFlags` for uri-specific flags.
//
// NOTE: Only safe to call from the main thread.
nsresult DynamicProtocolFlags(nsIURI* aURI, uint32_t* aFlags) const
MOZ_REQUIRES(sMainThreadCapability);
// Get the main-thread-only nsIProtocolHandler instance.
already_AddRefed<nsIProtocolHandler> Handler() const
MOZ_REQUIRES(sMainThreadCapability);
private:
Variant<const xpcom::StaticProtocolHandler*, RuntimeProtocolHandler> mInner;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_ProtocolHandlerInfo_h

View File

@ -166,6 +166,7 @@ EXPORTS.mozilla.net += [
"NetworkConnectivityService.h",
"Predictor.h",
"PrivateBrowsingChannel.h",
"ProtocolHandlerInfo.h",
"RedirectChannelRegistrar.h",
"RequestContextService.h",
"SimpleChannelParent.h",
@ -231,6 +232,7 @@ UNIFIED_SOURCES += [
"nsUDPSocket.cpp",
"PollableEvent.cpp",
"Predictor.cpp",
"ProtocolHandlerInfo.cpp",
"ProxyAutoConfig.cpp",
"RedirectChannelRegistrar.cpp",
"RequestContextService.cpp",
@ -310,6 +312,7 @@ include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"
LOCAL_INCLUDES += [
"!/xpcom/components",
"/docshell/base",
"/dom/base",
"/netwerk/dns",
@ -318,6 +321,7 @@ LOCAL_INCLUDES += [
"/netwerk/socket",
"/netwerk/url-classifier",
"/security/manager/ssl",
"/xpcom/components",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":

View File

@ -54,17 +54,30 @@ interface nsIIOService : nsISupports
* Returns the protocol flags for a given scheme.
*
* @param aScheme the URI scheme
* @return value of corresponding nsIProtocolHandler::protocolFlags
* @return protocol flags for the corresponding protocol
*/
unsigned long getProtocolFlags(in string aScheme);
/**
* This method constructs a new URI by determining the scheme of the
* URI spec, and then delegating the construction of the URI to the
* protocol handler for that scheme. QueryInterface can be used on
* the resulting URI object to obtain a more specific type of URI.
* Returns the dynamic protocol flags for a given URI.
*
* @see nsIProtocolHandler::newURI
* @param aURI the URI to get all dynamic flags for
* @return protocol flags for that URI
*/
unsigned long getDynamicProtocolFlags(in nsIURI aURI);
/**
* Returns the default port for a given scheme.
*
* @param aScheme the URI scheme
* @return default port for the corresponding protocol
*/
long getDefaultPort(in string aScheme);
/**
* This method constructs a new URI based on the scheme of the URI spec.
* QueryInterface can be used on the resulting URI object to obtain a more
* specific type of URI.
*/
nsIURI newURI(in AUTF8String aSpec,
[optional] in string aOriginCharset,
@ -270,6 +283,35 @@ interface nsIIOService : nsISupports
* The pid for socket process.
*/
readonly attribute unsigned long long socketProcessId;
/**
* Register a protocol handler at runtime, given protocol flags and a
* default port.
*
* Statically registered protocol handlers cannot be overridden, and an
* error will be returned if that is attempted.
*
* Runtime registered protocol handlers are never QueryInterface-ed into
* `nsIProtocolHandlerWithDynamicFlags`, so that interface will be ignored.
*
* @param aScheme the scheme handled by the protocol handler.
* @param aHandler the protocol handler instance.
* @param aProtocolFlags protocol flags for this protocol, see
* nsIProtocolHandler for values.
* @param aDefaultPort default port for this scheme, or -1.
*/
void registerProtocolHandler(in ACString aScheme,
in nsIProtocolHandler aHandler,
in unsigned long aProtocolFlags,
in long aDefaultPort);
/**
* Unregister a protocol handler which was previously registered using
* registerProtocolHandler.
*
* @param aScheme the scheme to unregister a handler for.
*/
void unregisterProtocolHandler(in ACString aScheme);
};
%{C++

View File

@ -23,6 +23,7 @@
#include "nsNetCID.h"
#include "nsCRT.h"
#include "nsSimpleNestedURI.h"
#include "nsSocketTransport2.h"
#include "nsTArray.h"
#include "nsIConsoleService.h"
#include "nsIUploadChannel2.h"
@ -64,10 +65,7 @@
#include "mozilla/StaticPrefs_security.h"
#include "nsNSSComponent.h"
#include "ssl.h"
#ifdef MOZ_WIDGET_GTK
# include "nsGIOProtocolHandler.h"
#endif
#include "StaticComponents.h"
namespace mozilla {
namespace net {
@ -88,6 +86,7 @@ using mozilla::dom::ServiceWorkerDescriptor;
#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
#define WEBRTC_PREF_PREFIX "media.peerconnection."
#define NETWORK_DNS_PREF "network.dns."
#define FORCE_EXTERNAL_PREF_PREFIX "network.protocol-handler.external."
#define MAX_RECURSION_COUNT 50
@ -213,6 +212,7 @@ static const char* gCallbackPrefs[] = {
NECKO_BUFFER_CACHE_COUNT_PREF,
NECKO_BUFFER_CACHE_SIZE_PREF,
NETWORK_CAPTIVE_PORTAL_PREF,
FORCE_EXTERNAL_PREF_PREFIX,
nullptr,
};
@ -846,121 +846,65 @@ nsresult nsIOService::AsyncOnChannelRedirect(
return NS_OK;
}
nsresult nsIOService::CacheProtocolHandler(const char* scheme,
nsIProtocolHandler* handler) {
MOZ_ASSERT(NS_IsMainThread());
for (unsigned int i = 0; i < NS_N(gScheme); i++) {
if (!nsCRT::strcasecmp(scheme, gScheme[i])) {
nsresult rv;
NS_ASSERTION(!mWeakHandler[i], "Protocol handler already cached");
// Make sure the handler supports weak references.
nsCOMPtr<nsISupportsWeakReference> factoryPtr =
do_QueryInterface(handler, &rv);
if (!factoryPtr) {
// Don't cache handlers that don't support weak reference as
// there is real danger of a circular reference.
#ifdef DEBUG_dp
printf(
"DEBUG: %s protcol handler doesn't support weak ref. Not cached.\n",
scheme);
#endif /* DEBUG_dp */
return NS_ERROR_FAILURE;
}
mWeakHandler[i] = do_GetWeakReference(handler);
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
nsresult nsIOService::GetCachedProtocolHandler(const char* scheme,
nsIProtocolHandler** result,
uint32_t start, uint32_t end) {
MOZ_ASSERT(NS_IsMainThread());
uint32_t len = end - start - 1;
for (unsigned int i = 0; i < NS_N(gScheme); i++) {
if (!mWeakHandler[i]) continue;
// handle unterminated strings
// start is inclusive, end is exclusive, len = end - start - 1
if (end ? (!nsCRT::strncasecmp(scheme + start, gScheme[i], len) &&
gScheme[i][len] == '\0')
: (!nsCRT::strcasecmp(scheme, gScheme[i]))) {
return CallQueryReferent(mWeakHandler[i].get(), result);
}
}
return NS_ERROR_FAILURE;
}
static bool UsesExternalProtocolHandler(const char* aScheme) {
if ("file"_ns.Equals(aScheme) || "chrome"_ns.Equals(aScheme) ||
"resource"_ns.Equals(aScheme)) {
bool nsIOService::UsesExternalProtocolHandler(const nsACString& aScheme) {
if (aScheme == "file"_ns || aScheme == "chrome"_ns ||
aScheme == "resource"_ns) {
// Don't allow file:, chrome: or resource: URIs to be handled with
// nsExternalProtocolHandler, since internally we rely on being able to
// use and read from these URIs.
return false;
}
for (const auto& forcedExternalScheme : gForcedExternalSchemes) {
if (!nsCRT::strcasecmp(forcedExternalScheme, aScheme)) {
if (aScheme == "place"_ns || aScheme == "fake-favicon-uri"_ns ||
aScheme == "favicon"_ns || aScheme == "moz-nullprincipal"_ns) {
// Force place: fake-favicon-uri: favicon: and moz-nullprincipal: URIs to be
// handled with nsExternalProtocolHandler, and not with a dynamically
// registered handler.
return true;
}
// If prefs configure the URI to be handled externally, do so.
for (const auto& scheme : mForceExternalSchemes) {
if (aScheme == scheme) {
return true;
}
}
return false;
}
nsAutoCString pref("network.protocol-handler.external.");
pref += aScheme;
ProtocolHandlerInfo nsIOService::LookupProtocolHandler(
const nsACString& aScheme) {
// Look-ups are ASCII-case-insensitive, so lower-case the string before
// continuing.
nsAutoCString scheme(aScheme);
ToLowerCase(scheme);
return Preferences::GetBool(pref.get(), false);
// NOTE: If we could get rid of mForceExternalSchemes (or prevent them from
// disabling static protocols), we could avoid locking mLock until we need to
// check `mRuntimeProtocolHandlers.
AutoReadLock lock(mLock);
if (!UsesExternalProtocolHandler(scheme)) {
// Try the static protocol handler first - they cannot be overridden by
// dynamic protocols.
if (const xpcom::StaticProtocolHandler* handler =
xpcom::StaticProtocolHandler::Lookup(scheme)) {
return ProtocolHandlerInfo(*handler);
}
if (auto handler = mRuntimeProtocolHandlers.Lookup(scheme)) {
return ProtocolHandlerInfo(handler.Data());
}
}
return ProtocolHandlerInfo(xpcom::StaticProtocolHandler::Default());
}
NS_IMETHODIMP
nsIOService::GetProtocolHandler(const char* scheme,
nsIProtocolHandler** result) {
nsresult rv;
AssertIsOnMainThread();
NS_ENSURE_ARG_POINTER(scheme);
// XXX we may want to speed this up by introducing our own protocol
// scheme -> protocol handler mapping, avoiding the string manipulation
// and service manager stuff
rv = GetCachedProtocolHandler(scheme, result);
if (NS_SUCCEEDED(rv)) return rv;
if (scheme[0] != '\0' && !UsesExternalProtocolHandler(scheme)) {
nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
contractID += scheme;
ToLowerCase(contractID);
rv = CallGetService(contractID.get(), result);
if (NS_SUCCEEDED(rv)) {
CacheProtocolHandler(scheme, *result);
return rv;
}
#ifdef MOZ_WIDGET_GTK
// check to see whether GVFS can handle this URI scheme. otherwise, we
// failover to using the default protocol handler.
RefPtr<nsGIOProtocolHandler> gioHandler =
nsGIOProtocolHandler::GetSingleton();
if (gioHandler->IsSupportedProtocol(nsCString(scheme))) {
gioHandler.forget(result);
return NS_OK;
}
#endif
}
// Okay we don't have a protocol handler to handle this url type, so use
// the default protocol handler. This will cause urls to get dispatched
// out to the OS ('cause we can't do anything with them) when we try to
// read from a channel created by the default protocol handler.
rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "default", result);
if (NS_FAILED(rv)) return NS_ERROR_UNKNOWN_PROTOCOL;
return rv;
*result = LookupProtocolHandler(nsDependentCString(scheme)).Handler().take();
return *result ? NS_OK : NS_ERROR_UNKNOWN_PROTOCOL;
}
NS_IMETHODIMP
@ -1016,19 +960,32 @@ nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) {
NS_IMETHODIMP
nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) {
nsCOMPtr<nsIProtocolHandler> handler;
nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
if (NS_FAILED(rv)) return rv;
NS_ENSURE_ARG_POINTER(scheme);
// We can't call DoGetProtocolFlags here because we don't have a URI. This
// API is used by (and only used by) extensions, which is why it's still
// around. Calling this on a scheme with dynamic flags will throw.
rv = handler->GetProtocolFlags(flags);
#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED
MOZ_RELEASE_ASSERT(!(*flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC),
"ORIGIN_IS_FULL_SPEC is unsupported but used");
#endif
return rv;
*flags =
LookupProtocolHandler(nsDependentCString(scheme)).StaticProtocolFlags();
return NS_OK;
}
NS_IMETHODIMP
nsIOService::GetDynamicProtocolFlags(nsIURI* uri, uint32_t* flags) {
AssertIsOnMainThread();
NS_ENSURE_ARG(uri);
nsAutoCString scheme;
nsresult rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
return LookupProtocolHandler(scheme).DynamicProtocolFlags(uri, flags);
}
NS_IMETHODIMP
nsIOService::GetDefaultPort(const char* scheme, int32_t* defaultPort) {
NS_ENSURE_ARG_POINTER(scheme);
*defaultPort =
LookupProtocolHandler(nsDependentCString(scheme)).DefaultPort();
return NS_OK;
}
class AutoIncrement {
@ -1156,10 +1113,6 @@ nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
if (NS_FAILED(rv)) return rv;
uint32_t protoFlags;
rv = handler->DoGetProtocolFlags(aURI, &protoFlags);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
if (pph) {
@ -1464,7 +1417,7 @@ nsIOService::AllowPort(int32_t inPort, const char* scheme, bool* _retval) {
nsTArray<int32_t> restrictedPortList;
{
MutexAutoLock lock(mMutex);
AutoReadLock lock(mLock);
restrictedPortList.Assign(mRestrictedPortList);
}
// first check to see if the port is in our blacklist:
@ -1558,13 +1511,30 @@ void nsIOService::PrefsChanged(const char* pref) {
}
}
}
if (!pref || strncmp(pref, FORCE_EXTERNAL_PREF_PREFIX,
strlen(FORCE_EXTERNAL_PREF_PREFIX)) == 0) {
nsTArray<nsCString> prefs;
if (nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch()) {
prefRootBranch->GetChildList(FORCE_EXTERNAL_PREF_PREFIX, prefs);
}
nsTArray<nsCString> forceExternalSchemes;
for (const auto& pref : prefs) {
if (Preferences::GetBool(pref.get(), false)) {
forceExternalSchemes.AppendElement(
Substring(pref, strlen(FORCE_EXTERNAL_PREF_PREFIX)));
}
}
AutoWriteLock lock(mLock);
mForceExternalSchemes = std::move(forceExternalSchemes);
}
}
void nsIOService::ParsePortList(const char* pref, bool remove) {
nsAutoCString portList;
nsTArray<int32_t> restrictedPortList;
{
MutexAutoLock lock(mMutex);
AutoWriteLock lock(mLock);
restrictedPortList.Assign(std::move(mRestrictedPortList));
}
// Get a pref string and chop it up into a list of ports.
@ -1605,7 +1575,7 @@ void nsIOService::ParsePortList(const char* pref, bool remove) {
}
}
MutexAutoLock lock(mMutex);
AutoWriteLock lock(mLock);
mRestrictedPortList.Assign(std::move(restrictedPortList));
}
@ -1713,6 +1683,13 @@ nsIOService::Observe(nsISupports* subject, const char* topic,
gCallbackSecurityPrefs, this);
PrepareForShutdownInSocketProcess();
}
// We're in XPCOM shutdown now. Unregister any dynamic protocol handlers
// after this point to avoid leaks.
{
AutoWriteLock lock(mLock);
mRuntimeProtocolHandlers.Clear();
}
} else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
} else if (!strcmp(topic, NS_NETWORK_ID_CHANGED_TOPIC)) {
@ -1755,13 +1732,16 @@ nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
nsresult rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
// Grab the protocol flags from the URI.
auto handler = LookupProtocolHandler(scheme);
uint32_t protocolFlags;
nsCOMPtr<nsIProtocolHandler> handler;
rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
NS_ENSURE_SUCCESS(rv, rv);
rv = handler->DoGetProtocolFlags(uri, &protocolFlags);
NS_ENSURE_SUCCESS(rv, rv);
if (flags & nsIProtocolHandler::DYNAMIC_URI_FLAGS) {
AssertIsOnMainThread();
rv = handler.DynamicProtocolFlags(uri, &protocolFlags);
NS_ENSURE_SUCCESS(rv, rv);
} else {
protocolFlags = handler.StaticProtocolFlags();
}
*result = (protocolFlags & flags) == flags;
return NS_OK;
@ -2121,5 +2101,60 @@ nsIOService::GetSocketProcessId(uint64_t* aPid) {
return NS_OK;
}
NS_IMETHODIMP
nsIOService::RegisterProtocolHandler(const nsACString& aScheme,
nsIProtocolHandler* aHandler,
uint32_t aProtocolFlags,
int32_t aDefaultPort) {
if (mShutdown) {
return NS_ERROR_NOT_AVAILABLE;
}
if (aScheme.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString scheme(aScheme);
ToLowerCase(scheme);
AutoWriteLock lock(mLock);
return mRuntimeProtocolHandlers.WithEntryHandle(scheme, [&](auto&& entry) {
if (entry) {
NS_WARNING("Cannot override an existing dynamic protocol handler");
return NS_ERROR_FACTORY_EXISTS;
}
if (xpcom::StaticProtocolHandler::Lookup(scheme)) {
NS_WARNING("Cannot override an existing static protocol handler");
return NS_ERROR_FACTORY_EXISTS;
}
nsMainThreadPtrHandle<nsIProtocolHandler> handler(
new nsMainThreadPtrHolder<nsIProtocolHandler>("RuntimeProtocolHandler",
aHandler));
entry.Insert(RuntimeProtocolHandler{
.mHandler = std::move(handler),
.mProtocolFlags = aProtocolFlags,
.mDefaultPort = aDefaultPort,
});
return NS_OK;
});
}
NS_IMETHODIMP
nsIOService::UnregisterProtocolHandler(const nsACString& aScheme) {
if (mShutdown) {
return NS_OK;
}
if (aScheme.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString scheme(aScheme);
ToLowerCase(scheme);
AutoWriteLock lock(mLock);
return mRuntimeProtocolHandlers.Remove(scheme)
? NS_OK
: NS_ERROR_FACTORY_NOT_REGISTERED;
}
} // namespace net
} // namespace mozilla

View File

@ -19,7 +19,8 @@
#include "nsWeakReference.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Mutex.h"
#include "mozilla/RWLock.h"
#include "mozilla/net/ProtocolHandlerInfo.h"
#include "prtime.h"
#include "nsICaptivePortalService.h"
#include "nsIObserverService.h"
@ -35,14 +36,6 @@
#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
static const char gScheme[][sizeof("moz-safe-about")] = {
"chrome", "file", "http", "https",
"jar", "data", "about", "moz-safe-about",
"resource", "moz-extension", "page-icon", "blob"};
static const char gForcedExternalSchemes[][sizeof("moz-nullprincipal")] = {
"place", "fake-favicon-uri", "favicon", "moz-nullprincipal"};
class nsINetworkLinkService;
class nsIPrefBranch;
class nsIProtocolProxyService2;
@ -142,6 +135,10 @@ class nsIOService final : public nsIIOService,
friend SocketProcessMemoryReporter;
RefPtr<MemoryReportingProcess> GetSocketProcessMemoryReporter();
// Lookup the ProtocolHandlerInfo based on a given scheme.
// Safe to call from any thread.
ProtocolHandlerInfo LookupProtocolHandler(const nsACString& aScheme);
static void OnTLSPrefChange(const char* aPref, void* aSelf);
nsresult LaunchSocketProcess();
@ -159,12 +156,6 @@ class nsIOService final : public nsIIOService,
nsresult OnNetworkLinkEvent(const char* data);
nsresult GetCachedProtocolHandler(const char* scheme,
nsIProtocolHandler** hdlrResult,
uint32_t start = 0, uint32_t end = 0);
nsresult CacheProtocolHandler(const char* scheme,
nsIProtocolHandler* handler);
nsresult InitializeCaptivePortalService();
nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
@ -205,6 +196,9 @@ class nsIOService final : public nsIIOService,
nsresult SetOfflineInternal(bool offline, bool notifySocketProcess = true);
bool UsesExternalProtocolHandler(const nsACString& aScheme)
MOZ_REQUIRES_SHARED(mLock);
private:
mozilla::Atomic<bool, mozilla::Relaxed> mOffline{true};
mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange{false};
@ -226,15 +220,15 @@ class nsIOService final : public nsIIOService,
nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
bool mNetworkLinkServiceInitialized{false};
// Cached protocol handlers, only accessed on the main thread
nsWeakPtr mWeakHandler[NS_N(gScheme)];
// cached categories
nsCategoryCache<nsIChannelEventSink> mChannelEventSinks{
NS_CHANNEL_EVENT_SINK_CATEGORY};
Mutex mMutex{"nsIOService::mMutex"};
nsTArray<int32_t> mRestrictedPortList MOZ_GUARDED_BY(mMutex);
RWLock mLock{"nsIOService::mLock"};
nsTArray<int32_t> mRestrictedPortList MOZ_GUARDED_BY(mLock);
nsTArray<nsCString> mForceExternalSchemes MOZ_GUARDED_BY(mLock);
nsTHashMap<nsCString, RuntimeProtocolHandler> mRuntimeProtocolHandlers
MOZ_GUARDED_BY(mLock);
uint32_t mTotalRequests{0};
uint32_t mCacheWon{0};

View File

@ -37,6 +37,9 @@ interface nsIProtocolHandlerWithDynamicFlags : nsISupports
/*
* Returns protocol flags for the given URI, which may be different from the
* flags for another URI of the same scheme.
*
* Only DYNAMIC_URI_FLAGS may be different from the registered flags for the
* protocol handler.
*/
unsigned long getFlagsForURI(in nsIURI aURI);
};
@ -112,6 +115,10 @@ interface nsIProtocolHandler : nsISupports
* Constants for the protocol flags (the first is the default mask, the
* others are deviations):
*
* NOTE: Protocol flags are provided when the protocol handler is
* registered, either through a static component or dynamically with
* `nsIIOService.registerProtocolHandler`.
*
* NOTE: Implementation must ignore any flags they do not understand.
*/
@ -331,7 +338,8 @@ interface nsIProtocolHandler : nsISupports
* Flags which are allowed to be different from the static flags when
* returned from `nsIProtocolHandlerWithDynamicFlags::getFlagsForURI`.
*
* All other flags must match the `protocolFlags` attribute.
* All other flags must match the flags provided when the protocol handler
* was registered.
*/
const unsigned long DYNAMIC_URI_FLAGS =
URI_LOADABLE_BY_ANYONE | URI_DANGEROUS_TO_LOAD |

View File

@ -17,6 +17,7 @@
#include "mozilla/LoadContext.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Monitor.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StoragePrincipalHelper.h"
@ -26,6 +27,7 @@
#include "nsCategoryCache.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsEscape.h"
#include "nsFileStreams.h"
#include "nsHashKeys.h"
#include "nsHttp.h"
@ -666,9 +668,11 @@ int32_t NS_GetDefaultPort(const char* scheme,
nsIIOService* ioService /* = nullptr */) {
nsresult rv;
// Getting the default port through the protocol handler has a lot of XPCOM
// overhead involved. We optimize the protocols that matter for Web pages
// (HTTP and HTTPS) by hardcoding their default ports here.
// Getting the default port through the protocol handler previously had a lot
// of XPCOM overhead involved. We optimize the protocols that matter for Web
// pages (HTTP and HTTPS) by hardcoding their default ports here.
//
// XXX: This might not be necessary for performance anymore.
if (strncmp(scheme, "http", 4) == 0) {
if (scheme[4] == 's' && scheme[5] == '\0') {
return 443;
@ -682,11 +686,8 @@ int32_t NS_GetDefaultPort(const char* scheme,
net_EnsureIOService(&ioService, grip);
if (!ioService) return -1;
nsCOMPtr<nsIProtocolHandler> handler;
rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler));
if (NS_FAILED(rv)) return -1;
int32_t port;
rv = handler->GetDefaultPort(&port);
rv = ioService->GetDefaultPort(scheme, &port);
return NS_SUCCEEDED(rv) ? port : -1;
}

View File

@ -2017,6 +2017,7 @@ void nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters) {
nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri,
nsProtocolInfo* info) {
AssertIsOnMainThread();
MOZ_ASSERT(uri, "URI is null");
MOZ_ASSERT(info, "info is null");
@ -2028,14 +2029,10 @@ nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri,
nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIProtocolHandler> handler;
rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler));
rv = ios->GetDynamicProtocolFlags(uri, &info->flags);
if (NS_FAILED(rv)) return rv;
rv = handler->DoGetProtocolFlags(uri, &info->flags);
if (NS_FAILED(rv)) return rv;
rv = handler->GetDefaultPort(&info->defaultPort);
rv = ios->GetDefaultPort(info->scheme.get(), &info->defaultPort);
return rv;
}

View File

@ -23,15 +23,6 @@ CustomProtocolHandler.prototype = {
get scheme() {
return SCHEME;
},
get defaultPort() {
return -1;
},
get protocolFlags() {
return (
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE
);
},
newChannel(aURI, aLoadInfo) {
return new CustomChannel(aURI, aLoadInfo);
},
@ -39,14 +30,8 @@ CustomProtocolHandler.prototype = {
return port != -1;
},
/** nsIFactory */
createInstance(aIID) {
return this.QueryInterface(aIID);
},
/** nsISupports */
QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),
classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}"),
QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
};
function CustomChannel(aURI, aLoadInfo) {
@ -250,15 +235,15 @@ function loadTestTab(uri) {
add_task(async function() {
var handler = new CustomProtocolHandler();
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(
handler.classID,
"",
"@mozilla.org/network/protocol;1?name=" + handler.scheme,
handler
Services.io.registerProtocolHandler(
SCHEME,
handler,
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
-1
);
registerCleanupFunction(function() {
registrar.unregisterFactory(handler.classID, handler);
Services.io.unregisterProtocolHandler(SCHEME);
});
});

View File

@ -21,19 +21,6 @@ ProtocolHandler.prototype = {
get scheme() {
return "x-bug894586";
},
get defaultPort() {
return -1;
},
get protocolFlags() {
return (
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
Ci.nsIProtocolHandler.URI_NON_PERSISTABLE |
Ci.nsIProtocolHandler.URI_SYNC_LOAD_IS_OK
);
},
newChannel(aURI, aLoadInfo) {
this.loadInfo = aLoadInfo;
return this;
@ -106,19 +93,12 @@ ProtocolHandler.prototype = {
Ci.nsIRequest.INHIBIT_CACHING |
Ci.nsIRequest.LOAD_BYPASS_CACHE,
/** nsIFactory */
createInstance(aIID) {
return this.QueryInterface(aIID);
},
/** nsISupports */
QueryInterface: ChromeUtils.generateQI([
"nsIProtocolHandler",
"nsIRequest",
"nsIChannel",
"nsIFactory",
]),
classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}"),
};
/**
@ -127,12 +107,17 @@ ProtocolHandler.prototype = {
*/
function run_test() {
var handler = new ProtocolHandler();
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(
handler.classID,
"",
"@mozilla.org/network/protocol;1?name=" + handler.scheme,
handler
Services.io.registerProtocolHandler(
handler.scheme,
handler,
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
Ci.nsIProtocolHandler.URI_NON_PERSISTABLE |
Ci.nsIProtocolHandler.URI_SYNC_LOAD_IS_OK,
-1
);
try {
var ss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
@ -143,7 +128,7 @@ function run_test() {
ss.sheetRegistered(handler.uri, Ci.nsIStyleSheetService.AGENT_SHEET)
);
} finally {
registrar.unregisterFactory(handler.classID, handler);
Services.io.unregisterProtocolHandler(handler.scheme);
}
}

View File

@ -249,7 +249,7 @@ export class Proxy {
if (scheme === "socks") {
port = null;
} else {
port = Services.io.getProtocolHandler(scheme).defaultPort;
port = Services.io.getDefaultPort(scheme);
}
}

View File

@ -66,7 +66,6 @@ const backgroundtaskPhases = {
"@mozilla.org/network/idn-service;1",
"@mozilla.org/network/io-service;1",
"@mozilla.org/network/network-link-service;1",
"@mozilla.org/network/protocol;1?name=chrome",
"@mozilla.org/network/protocol;1?name=file",
"@mozilla.org/network/protocol;1?name=jar",
"@mozilla.org/network/protocol;1?name=resource",

View File

@ -290,22 +290,10 @@ bool DownloadPlatform::IsURLPossiblyFromWeb(nsIURI* aURI) {
}
while (uri) {
// We're not using nsIIOService::ProtocolHasFlags because it doesn't
// take per-URI flags into account. We're also not using
// NS_URIChainHasFlags because we're checking for *any* of 3 flags
// to be present on *all* of the nested URIs, which it can't do.
nsAutoCString scheme;
nsresult rv = uri->GetScheme(scheme);
if (NS_FAILED(rv)) {
return true;
}
nsCOMPtr<nsIProtocolHandler> ph;
rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
if (NS_FAILED(rv)) {
return true;
}
// We're not using NS_URIChainHasFlags because we're checking for *any* of 3
// flags to be present on *all* of the nested URIs, which it can't do.
uint32_t flags;
rv = ph->DoGetProtocolFlags(uri, &flags);
nsresult rv = ios->GetDynamicProtocolFlags(uri, &flags);
if (NS_FAILED(rv)) {
return true;
}

View File

@ -83,7 +83,7 @@ class MatchURLFilters {
if (["resource", "chrome"].includes(uri.scheme)) {
port = undefined;
} else {
port = Services.io.getProtocolHandler(uri.scheme).defaultPort;
port = Services.io.getDefaultPort(uri.scheme);
}
}