fune/netwerk/base/nsIOService.cpp
Nika Layzell 98304d1200 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
2022-12-01 15:43:19 +00:00

2160 lines
67 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 cindent et: */
/* 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 "mozilla/DebugOnly.h"
#include "nsIOService.h"
#include "nsIProtocolHandler.h"
#include "nsIFileProtocolHandler.h"
#include "nscore.h"
#include "nsIURI.h"
#include "prprf.h"
#include "netCore.h"
#include "nsIObserverService.h"
#include "nsXPCOM.h"
#include "nsIProxiedProtocolHandler.h"
#include "nsIProxyInfo.h"
#include "nsDNSService2.h"
#include "nsEscape.h"
#include "nsNetUtil.h"
#include "nsNetCID.h"
#include "nsCRT.h"
#include "nsSimpleNestedURI.h"
#include "nsSocketTransport2.h"
#include "nsTArray.h"
#include "nsIConsoleService.h"
#include "nsIUploadChannel2.h"
#include "nsXULAppAPI.h"
#include "nsIProtocolProxyCallback.h"
#include "nsICancelable.h"
#include "nsINetworkLinkService.h"
#include "nsAsyncRedirectVerifyHelper.h"
#include "nsURLHelper.h"
#include "nsIProtocolProxyService2.h"
#include "MainThreadUtils.h"
#include "nsINode.h"
#include "nsIWebTransport.h"
#include "nsIWidget.h"
#include "nsThreadUtils.h"
#include "WebTransportSessionProxy.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/net/NeckoCommon.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/net/DNS.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/ServiceWorkerDescriptor.h"
#include "mozilla/net/CaptivePortalService.h"
#include "mozilla/net/NetworkConnectivityService.h"
#include "mozilla/net/SocketProcessHost.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/Unused.h"
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_security.h"
#include "nsNSSComponent.h"
#include "ssl.h"
#include "StaticComponents.h"
namespace mozilla {
namespace net {
using mozilla::Maybe;
using mozilla::dom::ClientInfo;
using mozilla::dom::ServiceWorkerDescriptor;
#define PORT_PREF_PREFIX "network.security.ports."
#define PORT_PREF(x) PORT_PREF_PREFIX x
#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
// Nb: these have been misnomers since bug 715770 removed the buffer cache.
// "network.segment.count" and "network.segment.size" would be better names,
// but the old names are still used to preserve backward compatibility.
#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
#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
nsIOService* gIOService;
static bool gHasWarnedUploadChannel2;
static bool gCaptivePortalEnabled = false;
static LazyLogModule gIOServiceLog("nsIOService");
#undef LOG
#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args)
// A general port blacklist. Connections to these ports will not be allowed
// unless the protocol overrides.
//
// This list is to be kept in sync with "bad ports" as defined in the
// WHATWG Fetch standard at <https://fetch.spec.whatwg.org/#port-blocking>
int16_t gBadPortList[] = {
1, // tcpmux
7, // echo
9, // discard
11, // systat
13, // daytime
15, // netstat
17, // qotd
19, // chargen
20, // ftp-data
21, // ftp
22, // ssh
23, // telnet
25, // smtp
37, // time
42, // name
43, // nicname
53, // domain
69, // tftp
77, // priv-rjs
79, // finger
87, // ttylink
95, // supdup
101, // hostriame
102, // iso-tsap
103, // gppitnp
104, // acr-nema
109, // pop2
110, // pop3
111, // sunrpc
113, // auth
115, // sftp
117, // uucp-path
119, // nntp
123, // ntp
135, // loc-srv / epmap
137, // netbios
139, // netbios
143, // imap2
161, // snmp
179, // bgp
389, // ldap
427, // afp (alternate)
465, // smtp (alternate)
512, // print / exec
513, // login
514, // shell
515, // printer
526, // tempo
530, // courier
531, // chat
532, // netnews
540, // uucp
548, // afp
554, // rtsp
556, // remotefs
563, // nntp+ssl
587, // smtp (outgoing)
601, // syslog-conn
636, // ldap+ssl
989, // ftps-data
990, // ftps
993, // imap+ssl
995, // pop3+ssl
1719, // h323gatestat
1720, // h323hostcall
1723, // pptp
2049, // nfs
3659, // apple-sasl
4045, // lockd
5060, // sip
5061, // sips
6000, // x11
6566, // sane-port
6665, // irc (alternate)
6666, // irc (alternate)
6667, // irc (default)
6668, // irc (alternate)
6669, // irc (alternate)
6697, // irc+tls
10080, // amanda
0, // Sentinel value: This MUST be zero
};
static const char kProfileChangeNetTeardownTopic[] =
"profile-change-net-teardown";
static const char kProfileChangeNetRestoreTopic[] =
"profile-change-net-restore";
static const char kProfileDoChange[] = "profile-do-change";
// Necko buffer defaults
uint32_t nsIOService::gDefaultSegmentSize = 4096;
uint32_t nsIOService::gDefaultSegmentCount = 24;
uint32_t nsIOService::sSocketProcessCrashedCount = 0;
////////////////////////////////////////////////////////////////////////////////
nsIOService::nsIOService()
: mLastOfflineStateChange(PR_IntervalNow()),
mLastConnectivityChange(PR_IntervalNow()),
mLastNetworkLinkChange(PR_IntervalNow()) {}
static const char* gCallbackPrefs[] = {
PORT_PREF_PREFIX,
MANAGE_OFFLINE_STATUS_PREF,
NECKO_BUFFER_CACHE_COUNT_PREF,
NECKO_BUFFER_CACHE_SIZE_PREF,
NETWORK_CAPTIVE_PORTAL_PREF,
FORCE_EXTERNAL_PREF_PREFIX,
nullptr,
};
static const char* gCallbackPrefsForSocketProcess[] = {
WEBRTC_PREF_PREFIX,
NETWORK_DNS_PREF,
"network.send_ODA_to_content_directly",
"network.trr.",
"doh-rollout.",
"network.dns.disableIPv6",
"network.dns.skipTRR-when-parental-control-enabled",
"network.offline-mirrors-connectivity",
"network.disable-localhost-when-offline",
"network.proxy.parse_pac_on_socket_process",
"network.proxy.allow_hijacking_localhost",
"network.connectivity-service.",
"network.captive-portal-service.testMode",
nullptr,
};
static const char* gCallbackSecurityPrefs[] = {
// Note the prefs listed below should be in sync with the code in
// HandleTLSPrefChange().
"security.tls.version.min",
"security.tls.version.max",
"security.tls.version.enable-deprecated",
"security.tls.hello_downgrade_check",
"security.ssl.require_safe_negotiation",
"security.ssl.enable_false_start",
"security.ssl.enable_alpn",
"security.tls.enable_0rtt_data",
"security.ssl.disable_session_identifiers",
"security.tls.enable_post_handshake_auth",
"security.tls.enable_delegated_credentials",
// Note the prefs listed below should be in sync with the code in
// SetValidationOptionsCommon().
"security.ssl.enable_ocsp_stapling",
"security.ssl.enable_ocsp_must_staple",
"security.pki.certificate_transparency.mode",
nullptr,
};
nsresult nsIOService::Init() {
SSLTokensCache::Init();
InitializeCaptivePortalService();
// setup our bad port list stuff
for (int i = 0; gBadPortList[i]; i++) {
// We can't be accessed by another thread yet
MOZ_PUSH_IGNORE_THREAD_SAFETY
mRestrictedPortList.AppendElement(gBadPortList[i]);
MOZ_POP_THREAD_SAFETY
}
// Further modifications to the port list come from prefs
Preferences::RegisterPrefixCallbacks(nsIOService::PrefsChanged,
gCallbackPrefs, this);
PrefsChanged();
mSocketProcessTopicBlockedList.Insert(
nsLiteralCString(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID));
mSocketProcessTopicBlockedList.Insert(
nsLiteralCString(NS_XPCOM_SHUTDOWN_OBSERVER_ID));
mSocketProcessTopicBlockedList.Insert("xpcom-shutdown-threads"_ns);
mSocketProcessTopicBlockedList.Insert("profile-do-change"_ns);
mSocketProcessTopicBlockedList.Insert("network:socket-process-crashed"_ns);
// Register for profile change notifications
mObserverService = services::GetObserverService();
AddObserver(this, kProfileChangeNetTeardownTopic, true);
AddObserver(this, kProfileChangeNetRestoreTopic, true);
AddObserver(this, kProfileDoChange, true);
AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
AddObserver(this, NS_NETWORK_ID_CHANGED_TOPIC, true);
AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
// Register observers for sending notifications to nsSocketTransportService
if (XRE_IsParentProcess()) {
AddObserver(this, "profile-initial-state", true);
AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
}
if (IsSocketProcessChild()) {
Preferences::RegisterCallbacks(nsIOService::OnTLSPrefChange,
gCallbackSecurityPrefs, this);
}
gIOService = this;
InitializeNetworkLinkService();
InitializeProtocolProxyService();
SetOffline(false);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::AddObserver(nsIObserver* aObserver, const char* aTopic,
bool aOwnsWeak) {
if (!mObserverService) {
return NS_ERROR_FAILURE;
}
// Register for the origional observer.
nsresult rv = mObserverService->AddObserver(aObserver, aTopic, aOwnsWeak);
if (NS_FAILED(rv)) {
return rv;
}
if (!XRE_IsParentProcess()) {
return NS_OK;
}
nsAutoCString topic(aTopic);
// This happens when AddObserver() is called by nsIOService::Init(). We don't
// want to add nsIOService again.
if (SameCOMIdentity(aObserver, static_cast<nsIObserver*>(this))) {
mIOServiceTopicList.Insert(topic);
return NS_OK;
}
if (!UseSocketProcess()) {
return NS_OK;
}
if (mSocketProcessTopicBlockedList.Contains(topic)) {
return NS_ERROR_FAILURE;
}
// Avoid registering duplicate topics.
if (mObserverTopicForSocketProcess.Contains(topic)) {
return NS_ERROR_FAILURE;
}
mObserverTopicForSocketProcess.Insert(topic);
// Avoid registering duplicate topics.
if (mIOServiceTopicList.Contains(topic)) {
return NS_ERROR_FAILURE;
}
return mObserverService->AddObserver(this, aTopic, true);
}
NS_IMETHODIMP
nsIOService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsIOService::EnumerateObservers(const char* aTopic,
nsISimpleEnumerator** anEnumerator) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsIOService::NotifyObservers(nsISupports* aSubject,
const char* aTopic,
const char16_t* aSomeData) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsIOService::~nsIOService() {
if (gIOService) {
MOZ_ASSERT(gIOService == this);
gIOService = nullptr;
}
}
// static
void nsIOService::OnTLSPrefChange(const char* aPref, void* aSelf) {
MOZ_ASSERT(IsSocketProcessChild());
if (!EnsureNSSInitializedChromeOrContent()) {
LOG(("NSS not initialized."));
return;
}
nsAutoCString pref(aPref);
// The preferences listed in gCallbackSecurityPrefs need to be in sync with
// the code in HandleTLSPrefChange() and SetValidationOptionsCommon().
if (HandleTLSPrefChange(pref)) {
LOG(("HandleTLSPrefChange done"));
} else if (pref.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
pref.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
pref.EqualsLiteral("security.pki.certificate_transparency.mode")) {
SetValidationOptionsCommon();
}
}
nsresult nsIOService::InitializeCaptivePortalService() {
if (XRE_GetProcessType() != GeckoProcessType_Default) {
// We only initalize a captive portal service in the main process
return NS_OK;
}
mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID);
if (mCaptivePortalService) {
return static_cast<CaptivePortalService*>(mCaptivePortalService.get())
->Initialize();
}
// Instantiate and initialize the service
RefPtr<NetworkConnectivityService> ncs =
NetworkConnectivityService::GetSingleton();
return NS_OK;
}
nsresult nsIOService::InitializeSocketTransportService() {
nsresult rv = NS_OK;
if (!mSocketTransportService) {
mSocketTransportService =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
NS_WARNING("failed to get socket transport service");
}
}
if (mSocketTransportService) {
rv = mSocketTransportService->Init();
NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
mSocketTransportService->SetOffline(false);
}
return rv;
}
nsresult nsIOService::InitializeNetworkLinkService() {
nsresult rv = NS_OK;
if (mNetworkLinkServiceInitialized) return rv;
if (!NS_IsMainThread()) {
NS_WARNING("Network link service should be created on main thread");
return NS_ERROR_FAILURE;
}
// go into managed mode if we can, and chrome process
if (!XRE_IsParentProcess()) {
return NS_ERROR_NOT_AVAILABLE;
}
mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
if (mNetworkLinkService) {
mNetworkLinkServiceInitialized = true;
}
// After initializing the networkLinkService, query the connectivity state
OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
return rv;
}
nsresult nsIOService::InitializeProtocolProxyService() {
nsresult rv = NS_OK;
if (XRE_IsParentProcess()) {
// for early-initialization
Unused << do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
}
return rv;
}
already_AddRefed<nsIOService> nsIOService::GetInstance() {
if (!gIOService) {
RefPtr<nsIOService> ios = new nsIOService();
if (NS_SUCCEEDED(ios->Init())) {
MOZ_ASSERT(gIOService == ios.get());
return ios.forget();
}
}
return do_AddRef(gIOService);
}
class SocketProcessListenerProxy : public SocketProcessHost::Listener {
public:
SocketProcessListenerProxy() = default;
void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded) {
if (!gIOService) {
return;
}
gIOService->OnProcessLaunchComplete(aHost, aSucceeded);
}
void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
if (!gIOService) {
return;
}
gIOService->OnProcessUnexpectedShutdown(aHost);
}
};
// static
bool nsIOService::TooManySocketProcessCrash() {
return sSocketProcessCrashedCount >=
StaticPrefs::network_max_socket_process_failed_count();
}
// static
void nsIOService::IncreaseSocketProcessCrashCount() {
MOZ_ASSERT(IsNeckoChild());
sSocketProcessCrashedCount++;
}
nsresult nsIOService::LaunchSocketProcess() {
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() != GeckoProcessType_Default) {
return NS_OK;
}
// We shouldn't launch socket prcess when shutdown begins.
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
return NS_OK;
}
if (mSocketProcess) {
return NS_OK;
}
if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
LOG(("nsIOService skipping LaunchSocketProcess because of the env"));
return NS_OK;
}
if (!StaticPrefs::network_process_enabled()) {
LOG(("nsIOService skipping LaunchSocketProcess because of the pref"));
return NS_OK;
}
Preferences::RegisterPrefixCallbacks(
nsIOService::NotifySocketProcessPrefsChanged,
gCallbackPrefsForSocketProcess, this);
// The subprocess is launched asynchronously, so we wait for a callback to
// acquire the IPDL actor.
mSocketProcess = new SocketProcessHost(new SocketProcessListenerProxy());
LOG(("nsIOService::LaunchSocketProcess"));
if (!mSocketProcess->Launch()) {
NS_WARNING("Failed to launch socket process!!");
DestroySocketProcess();
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void nsIOService::DestroySocketProcess() {
LOG(("nsIOService::DestroySocketProcess"));
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() != GeckoProcessType_Default || !mSocketProcess) {
return;
}
Preferences::UnregisterPrefixCallbacks(
nsIOService::NotifySocketProcessPrefsChanged,
gCallbackPrefsForSocketProcess, this);
mSocketProcess->Shutdown();
mSocketProcess = nullptr;
}
bool nsIOService::SocketProcessReady() {
return mSocketProcess && mSocketProcess->IsConnected();
}
static bool sUseSocketProcess = false;
static bool sUseSocketProcessChecked = false;
// static
bool nsIOService::UseSocketProcess(bool aCheckAgain) {
if (sUseSocketProcessChecked && !aCheckAgain) {
return sUseSocketProcess;
}
sUseSocketProcessChecked = true;
sUseSocketProcess = false;
if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
return sUseSocketProcess;
}
if (TooManySocketProcessCrash()) {
LOG(("TooManySocketProcessCrash"));
return sUseSocketProcess;
}
if (PR_GetEnv("MOZ_FORCE_USE_SOCKET_PROCESS")) {
sUseSocketProcess = true;
return sUseSocketProcess;
}
if (StaticPrefs::network_process_enabled()) {
sUseSocketProcess =
StaticPrefs::network_http_network_access_on_socket_process_enabled();
}
return sUseSocketProcess;
}
// static
void nsIOService::NotifySocketProcessPrefsChanged(const char* aName,
void* aSelf) {
static_cast<nsIOService*>(aSelf)->NotifySocketProcessPrefsChanged(aName);
}
void nsIOService::NotifySocketProcessPrefsChanged(const char* aName) {
MOZ_ASSERT(NS_IsMainThread());
if (!XRE_IsParentProcess()) {
return;
}
if (!StaticPrefs::network_process_enabled()) {
return;
}
dom::Pref pref(nsCString(aName), /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_Socket,
/* remoteType */ ""_ns);
auto sendPrefUpdate = [pref]() {
Unused << gIOService->mSocketProcess->GetActor()->SendPreferenceUpdate(
pref);
};
CallOrWaitForSocketProcess(sendPrefUpdate);
}
void nsIOService::OnProcessLaunchComplete(SocketProcessHost* aHost,
bool aSucceeded) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("nsIOService::OnProcessLaunchComplete aSucceeded=%d\n", aSucceeded));
mSocketProcessLaunchComplete = aSucceeded;
if (mShutdown || !SocketProcessReady() || !aSucceeded) {
mPendingEvents.Clear();
return;
}
if (!mPendingEvents.IsEmpty()) {
nsTArray<std::function<void()>> pendingEvents = std::move(mPendingEvents);
for (auto& func : pendingEvents) {
func();
}
}
}
void nsIOService::CallOrWaitForSocketProcess(
const std::function<void()>& aFunc) {
MOZ_ASSERT(NS_IsMainThread());
if (IsSocketProcessLaunchComplete() && SocketProcessReady()) {
aFunc();
} else {
mPendingEvents.AppendElement(aFunc); // infallible
LaunchSocketProcess();
}
}
int32_t nsIOService::SocketProcessPid() {
if (!mSocketProcess) {
return 0;
}
if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
return (int32_t)actor->OtherPid();
}
return 0;
}
bool nsIOService::IsSocketProcessLaunchComplete() {
MOZ_ASSERT(NS_IsMainThread());
return mSocketProcessLaunchComplete;
}
void nsIOService::OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("nsIOService::OnProcessUnexpectedShutdown\n"));
DestroySocketProcess();
mPendingEvents.Clear();
// Nothing to do if socket process was not used before.
if (!UseSocketProcess()) {
return;
}
sSocketProcessCrashedCount++;
if (TooManySocketProcessCrash()) {
sUseSocketProcessChecked = false;
DNSServiceWrapper::SwitchToBackupDNSService();
}
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (observerService) {
(void)observerService->NotifyObservers(
nullptr, "network:socket-process-crashed", nullptr);
}
// UseSocketProcess() could return false if we have too many crashes, so we
// should call it again.
if (UseSocketProcess()) {
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
NewRunnableMethod("nsIOService::LaunchSocketProcess", this,
&nsIOService::LaunchSocketProcess)));
}
}
RefPtr<MemoryReportingProcess> nsIOService::GetSocketProcessMemoryReporter() {
// Check the prefs here again, since we don't want to create
// SocketProcessMemoryReporter for some tests.
if (!StaticPrefs::network_process_enabled() || !SocketProcessReady()) {
return nullptr;
}
return new SocketProcessMemoryReporter();
}
NS_IMETHODIMP
nsIOService::SocketProcessTelemetryPing() {
CallOrWaitForSocketProcess([]() {
Unused << gIOService->mSocketProcess->GetActor()
->SendSocketProcessTelemetryPing();
});
return NS_OK;
}
NS_IMPL_ISUPPORTS(nsIOService, nsIIOService, nsINetUtil, nsISpeculativeConnect,
nsIObserver, nsIIOServiceInternal, nsISupportsWeakReference,
nsIObserverService)
////////////////////////////////////////////////////////////////////////////////
nsresult nsIOService::RecheckCaptivePortal() {
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
if (!mCaptivePortalService) {
return NS_OK;
}
nsCOMPtr<nsIRunnable> task = NewRunnableMethod(
"nsIOService::RecheckCaptivePortal", mCaptivePortalService,
&nsICaptivePortalService::RecheckCaptivePortal);
return NS_DispatchToMainThread(task);
}
nsresult nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan) {
nsresult rv;
if (!mCaptivePortalService) {
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
rv = newChan->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return rv;
}
nsCString host;
rv = uri->GetHost(host);
if (NS_FAILED(rv)) {
return rv;
}
NetAddr addr;
// If the redirect wasn't to an IP literal, so there's probably no need
// to trigger the captive portal detection right now. It can wait.
if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
RecheckCaptivePortal();
}
return NS_OK;
}
nsresult nsIOService::AsyncOnChannelRedirect(
nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
nsAsyncRedirectVerifyHelper* helper) {
// If a redirect to a local network address occurs, then chances are we
// are in a captive portal, so we trigger a recheck.
RecheckCaptivePortalIfLocalRedirect(newChan);
// This is silly. I wish there was a simpler way to get at the global
// reference of the contentSecurityManager. But it lives in the XPCOM
// service registry.
nsCOMPtr<nsIChannelEventSink> sink =
do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
if (sink) {
nsresult rv =
helper->DelegateOnChannelRedirect(sink, oldChan, newChan, flags);
if (NS_FAILED(rv)) return rv;
}
// Finally, our category
nsCOMArray<nsIChannelEventSink> entries;
mChannelEventSinks.GetEntries(entries);
int32_t len = entries.Count();
for (int32_t i = 0; i < len; ++i) {
nsresult rv =
helper->DelegateOnChannelRedirect(entries[i], oldChan, newChan, flags);
if (NS_FAILED(rv)) return rv;
}
nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(oldChan));
// Collect the redirection from HTTP(S) only.
if (httpChan) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIURI> newURI;
newChan->GetURI(getter_AddRefs(newURI));
MOZ_ASSERT(newURI);
nsAutoCString scheme;
newURI->GetScheme(scheme);
MOZ_ASSERT(!scheme.IsEmpty());
Telemetry::AccumulateCategoricalKeyed(
scheme,
oldChan->IsDocument()
? Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::topLevel
: Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::subresource);
}
return NS_OK;
}
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;
}
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;
}
ProtocolHandlerInfo nsIOService::LookupProtocolHandler(
const nsACString& aScheme) {
// Look-ups are ASCII-case-insensitive, so lower-case the string before
// continuing.
nsAutoCString scheme(aScheme);
ToLowerCase(scheme);
// 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) {
AssertIsOnMainThread();
NS_ENSURE_ARG_POINTER(scheme);
*result = LookupProtocolHandler(nsDependentCString(scheme)).Handler().take();
return *result ? NS_OK : NS_ERROR_UNKNOWN_PROTOCOL;
}
NS_IMETHODIMP
nsIOService::ExtractScheme(const nsACString& inURI, nsACString& scheme) {
return net_ExtractURLScheme(inURI, scheme);
}
NS_IMETHODIMP
nsIOService::HostnameIsLocalIPAddress(nsIURI* aURI, bool* aResult) {
NS_ENSURE_ARG_POINTER(aURI);
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
NS_ENSURE_ARG_POINTER(innerURI);
nsAutoCString host;
nsresult rv = innerURI->GetAsciiHost(host);
if (NS_FAILED(rv)) {
return rv;
}
*aResult = false;
NetAddr addr;
if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
*aResult = true;
}
return NS_OK;
}
NS_IMETHODIMP
nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) {
NS_ENSURE_ARG_POINTER(aURI);
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
NS_ENSURE_ARG_POINTER(innerURI);
nsAutoCString host;
nsresult rv = innerURI->GetAsciiHost(host);
if (NS_FAILED(rv)) {
return rv;
}
*aResult = false;
NetAddr addr;
if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrShared()) {
*aResult = true;
}
return NS_OK;
}
NS_IMETHODIMP
nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) {
NS_ENSURE_ARG_POINTER(scheme);
*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 {
public:
explicit AutoIncrement(uint32_t* var) : mVar(var) { ++*var; }
~AutoIncrement() { --*mVar; }
private:
uint32_t* mVar;
};
nsresult nsIOService::NewURI(const nsACString& aSpec, const char* aCharset,
nsIURI* aBaseURI, nsIURI** result) {
return NS_NewURI(result, aSpec, aCharset, aBaseURI);
}
NS_IMETHODIMP
nsIOService::NewFileURI(nsIFile* file, nsIURI** result) {
nsresult rv;
NS_ENSURE_ARG_POINTER(file);
nsCOMPtr<nsIProtocolHandler> handler;
rv = GetProtocolHandler("file", getter_AddRefs(handler));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIFileProtocolHandler> fileHandler(do_QueryInterface(handler, &rv));
if (NS_FAILED(rv)) return rv;
return fileHandler->NewFileURI(file, result);
}
// static
already_AddRefed<nsIURI> nsIOService::CreateExposableURI(nsIURI* aURI) {
MOZ_ASSERT(aURI, "Must have a URI");
nsCOMPtr<nsIURI> uri = aURI;
nsAutoCString userPass;
uri->GetUserPass(userPass);
if (!userPass.IsEmpty()) {
DebugOnly<nsresult> rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri);
MOZ_ASSERT(NS_SUCCEEDED(rv) && uri, "Mutating URI should never fail");
}
return uri.forget();
}
NS_IMETHODIMP
nsIOService::CreateExposableURI(nsIURI* aURI, nsIURI** _result) {
NS_ENSURE_ARG_POINTER(aURI);
NS_ENSURE_ARG_POINTER(_result);
nsCOMPtr<nsIURI> exposableURI = CreateExposableURI(aURI);
exposableURI.forget(_result);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::NewChannelFromURI(nsIURI* aURI, nsINode* aLoadingNode,
nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
uint32_t aSecurityFlags,
nsContentPolicyType aContentPolicyType,
nsIChannel** result) {
return NewChannelFromURIWithProxyFlags(aURI,
nullptr, // aProxyURI
0, // aProxyFlags
aLoadingNode, aLoadingPrincipal,
aTriggeringPrincipal, aSecurityFlags,
aContentPolicyType, result);
}
nsresult nsIOService::NewChannelFromURIWithClientAndController(
nsIURI* aURI, nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
const Maybe<ClientInfo>& aLoadingClientInfo,
const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** aResult) {
return NewChannelFromURIWithProxyFlagsInternal(
aURI,
nullptr, // aProxyURI
0, // aProxyFlags
aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aLoadingClientInfo,
aController, aSecurityFlags, aContentPolicyType, aSandboxFlags,
aSkipCheckForBrokenURLOrZeroSized, aResult);
}
NS_IMETHODIMP
nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI, nsILoadInfo* aLoadInfo,
nsIChannel** result) {
return NewChannelFromURIWithProxyFlagsInternal(aURI,
nullptr, // aProxyURI
0, // aProxyFlags
aLoadInfo, result);
}
nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
const Maybe<ClientInfo>& aLoadingClientInfo,
const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** result) {
nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(
aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags,
aSkipCheckForBrokenURLOrZeroSized);
return NewChannelFromURIWithProxyFlagsInternal(aURI, aProxyURI, aProxyFlags,
loadInfo, result);
}
nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
nsILoadInfo* aLoadInfo, nsIChannel** result) {
nsresult rv;
NS_ENSURE_ARG_POINTER(aURI);
// all channel creations must provide a valid loadinfo
MOZ_ASSERT(aLoadInfo, "can not create channel without aLoadInfo");
NS_ENSURE_ARG_POINTER(aLoadInfo);
nsAutoCString scheme;
rv = aURI->GetScheme(scheme);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIProtocolHandler> handler;
rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
if (pph) {
rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
aLoadInfo, getter_AddRefs(channel));
} else {
rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel));
}
if (NS_FAILED(rv)) return rv;
// Make sure that all the individual protocolhandlers attach a loadInfo.
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (aLoadInfo != loadInfo) {
MOZ_ASSERT(false, "newly created channel must have a loadinfo attached");
return NS_ERROR_UNEXPECTED;
}
// If we're sandboxed, make sure to clear any owner the channel
// might already have.
if (loadInfo->GetLoadingSandboxed()) {
channel->SetOwner(nullptr);
}
// Some extensions override the http protocol handler and provide their own
// implementation. The channels returned from that implementation doesn't
// seem to always implement the nsIUploadChannel2 interface, presumably
// because it's a new interface.
// Eventually we should remove this and simply require that http channels
// implement the new interface.
// See bug 529041
if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) {
nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel);
if (!uploadChannel2) {
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
if (consoleService) {
consoleService->LogStringMessage(
u"Http channel implementation "
"doesn't support nsIUploadChannel2. An extension has "
"supplied a non-functional http protocol handler. This will "
"break behavior and in future releases not work at all.");
}
gHasWarnedUploadChannel2 = true;
}
}
channel.forget(result);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::NewChannelFromURIWithProxyFlags(
nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags,
nsContentPolicyType aContentPolicyType, nsIChannel** result) {
return NewChannelFromURIWithProxyFlagsInternal(
aURI, aProxyURI, aProxyFlags, aLoadingNode, aLoadingPrincipal,
aTriggeringPrincipal, Maybe<ClientInfo>(),
Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, 0,
/* aSkipCheckForBrokenURLOrZeroSized = */ false, result);
}
NS_IMETHODIMP
nsIOService::NewChannel(const nsACString& aSpec, const char* aCharset,
nsIURI* aBaseURI, nsINode* aLoadingNode,
nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
uint32_t aSecurityFlags,
nsContentPolicyType aContentPolicyType,
nsIChannel** result) {
nsresult rv;
nsCOMPtr<nsIURI> uri;
rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
if (NS_FAILED(rv)) return rv;
return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal,
aTriggeringPrincipal, aSecurityFlags,
aContentPolicyType, result);
}
NS_IMETHODIMP
nsIOService::NewWebTransport(nsIWebTransport** result) {
if (!XRE_IsParentProcess()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIWebTransport> webTransport = new WebTransportSessionProxy();
webTransport.forget(result);
return NS_OK;
}
bool nsIOService::IsLinkUp() {
InitializeNetworkLinkService();
if (!mNetworkLinkService) {
// We cannot decide, assume the link is up
return true;
}
bool isLinkUp;
nsresult rv;
rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp);
if (NS_FAILED(rv)) {
return true;
}
return isLinkUp;
}
NS_IMETHODIMP
nsIOService::GetOffline(bool* offline) {
if (StaticPrefs::network_offline_mirrors_connectivity()) {
*offline = mOffline || !mConnectivity;
} else {
*offline = mOffline;
}
return NS_OK;
}
NS_IMETHODIMP
nsIOService::SetOffline(bool offline) { return SetOfflineInternal(offline); }
nsresult nsIOService::SetOfflineInternal(bool offline,
bool notifySocketProcess) {
LOG(("nsIOService::SetOffline offline=%d\n", offline));
// When someone wants to go online (!offline) after we got XPCOM shutdown
// throw ERROR_NOT_AVAILABLE to prevent return to online state.
if ((mShutdown || mOfflineForProfileChange) && !offline) {
return NS_ERROR_NOT_AVAILABLE;
}
// SetOffline() may re-enter while it's shutting down services.
// If that happens, save the most recent value and it will be
// processed when the first SetOffline() call is done bringing
// down the service.
mSetOfflineValue = offline;
if (mSettingOffline) {
return NS_OK;
}
mSettingOffline = true;
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
NS_ASSERTION(observerService, "The observer service should not be null");
if (XRE_IsParentProcess()) {
if (observerService) {
(void)observerService->NotifyObservers(nullptr,
NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
offline ? u"true" : u"false");
}
if (SocketProcessReady() && notifySocketProcess) {
Unused << mSocketProcess->GetActor()->SendSetOffline(offline);
}
}
nsIIOService* subject = static_cast<nsIIOService*>(this);
while (mSetOfflineValue != mOffline) {
offline = mSetOfflineValue;
if (offline && !mOffline) {
mOffline = true; // indicate we're trying to shutdown
// don't care if notifications fail
if (observerService) {
observerService->NotifyObservers(subject,
NS_IOSERVICE_GOING_OFFLINE_TOPIC,
u"" NS_IOSERVICE_OFFLINE);
}
if (mSocketTransportService) mSocketTransportService->SetOffline(true);
mLastOfflineStateChange = PR_IntervalNow();
if (observerService) {
observerService->NotifyObservers(subject,
NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
u"" NS_IOSERVICE_OFFLINE);
}
} else if (!offline && mOffline) {
// go online
InitializeSocketTransportService();
mOffline = false; // indicate success only AFTER we've
// brought up the services
mLastOfflineStateChange = PR_IntervalNow();
// don't care if notification fails
// Only send the ONLINE notification if there is connectivity
if (observerService && mConnectivity) {
observerService->NotifyObservers(subject,
NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
(u"" NS_IOSERVICE_ONLINE));
}
}
}
// Don't notify here, as the above notifications (if used) suffice.
if ((mShutdown || mOfflineForProfileChange) && mOffline) {
if (mSocketTransportService) {
DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown);
NS_ASSERTION(NS_SUCCEEDED(rv),
"socket transport service shutdown failed");
}
}
mSettingOffline = false;
return NS_OK;
}
NS_IMETHODIMP
nsIOService::GetConnectivity(bool* aConnectivity) {
*aConnectivity = mConnectivity;
return NS_OK;
}
NS_IMETHODIMP
nsIOService::SetConnectivity(bool aConnectivity) {
LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity));
// This should only be called from ContentChild to pass the connectivity
// value from the chrome process to the content process.
if (XRE_IsParentProcess()) {
return NS_ERROR_NOT_AVAILABLE;
}
return SetConnectivityInternal(aConnectivity);
}
nsresult nsIOService::SetConnectivityInternal(bool aConnectivity) {
LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n",
aConnectivity));
if (mConnectivity == aConnectivity) {
// Nothing to do here.
return NS_OK;
}
mConnectivity = aConnectivity;
// This is used for PR_Connect PR_Close telemetry so it is important that
// we have statistic about network change event even if we are offline.
mLastConnectivityChange = PR_IntervalNow();
if (mCaptivePortalService) {
if (aConnectivity && gCaptivePortalEnabled) {
// This will also trigger a captive portal check for the new network
static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
} else {
static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
}
}
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (!observerService) {
return NS_OK;
}
// This notification sends the connectivity to the child processes
if (XRE_IsParentProcess()) {
observerService->NotifyObservers(nullptr,
NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
aConnectivity ? u"true" : u"false");
if (SocketProcessReady()) {
Unused << mSocketProcess->GetActor()->SendSetConnectivity(aConnectivity);
}
}
if (mOffline) {
// We don't need to send any notifications if we're offline
return NS_OK;
}
if (aConnectivity) {
// If we were previously offline due to connectivity=false,
// send the ONLINE notification
observerService->NotifyObservers(static_cast<nsIIOService*>(this),
NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
(u"" NS_IOSERVICE_ONLINE));
} else {
// If we were previously online and lost connectivity
// send the OFFLINE notification
observerService->NotifyObservers(static_cast<nsIIOService*>(this),
NS_IOSERVICE_GOING_OFFLINE_TOPIC,
u"" NS_IOSERVICE_OFFLINE);
observerService->NotifyObservers(static_cast<nsIIOService*>(this),
NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
u"" NS_IOSERVICE_OFFLINE);
}
return NS_OK;
}
NS_IMETHODIMP
nsIOService::AllowPort(int32_t inPort, const char* scheme, bool* _retval) {
int32_t port = inPort;
if (port == -1) {
*_retval = true;
return NS_OK;
}
if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
*_retval = false;
return NS_OK;
}
nsTArray<int32_t> restrictedPortList;
{
AutoReadLock lock(mLock);
restrictedPortList.Assign(mRestrictedPortList);
}
// first check to see if the port is in our blacklist:
int32_t badPortListCnt = restrictedPortList.Length();
for (int i = 0; i < badPortListCnt; i++) {
if (port == restrictedPortList[i]) {
*_retval = false;
// check to see if the protocol wants to override
if (!scheme) return NS_OK;
// We don't support get protocol handler off main thread.
if (!NS_IsMainThread()) {
return NS_OK;
}
nsCOMPtr<nsIProtocolHandler> handler;
nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
if (NS_FAILED(rv)) return rv;
// let the protocol handler decide
return handler->AllowPort(port, scheme, _retval);
}
}
*_retval = true;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// static
void nsIOService::PrefsChanged(const char* pref, void* self) {
static_cast<nsIOService*>(self)->PrefsChanged(pref);
}
void nsIOService::PrefsChanged(const char* pref) {
// Look for extra ports to block
if (!pref || strcmp(pref, PORT_PREF("banned")) == 0) {
ParsePortList(PORT_PREF("banned"), false);
}
// ...as well as previous blocks to remove.
if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0) {
ParsePortList(PORT_PREF("banned.override"), true);
}
if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) {
bool manage;
if (mNetworkLinkServiceInitialized &&
NS_SUCCEEDED(
Preferences::GetBool(MANAGE_OFFLINE_STATUS_PREF, &manage))) {
LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n",
manage));
SetManageOfflineStatus(manage);
}
}
if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) {
int32_t count;
if (NS_SUCCEEDED(
Preferences::GetInt(NECKO_BUFFER_CACHE_COUNT_PREF, &count))) {
/* check for bogus values and default if we find such a value */
if (count > 0) gDefaultSegmentCount = count;
}
}
if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) {
int32_t size;
if (NS_SUCCEEDED(
Preferences::GetInt(NECKO_BUFFER_CACHE_SIZE_PREF, &size))) {
/* check for bogus values and default if we find such a value
* the upper limit here is arbitrary. having a 1mb segment size
* is pretty crazy. if you remove this, consider adding some
* integer rollover test.
*/
if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size;
}
NS_WARNING_ASSERTION(!(size & (size - 1)),
"network segment size is not a power of 2!");
}
if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF,
&gCaptivePortalEnabled);
if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
if (gCaptivePortalEnabled) {
static_cast<CaptivePortalService*>(mCaptivePortalService.get())
->Start();
} else {
static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
}
}
}
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;
{
AutoWriteLock lock(mLock);
restrictedPortList.Assign(std::move(mRestrictedPortList));
}
// Get a pref string and chop it up into a list of ports.
Preferences::GetCString(pref, portList);
if (!portList.IsVoid()) {
nsTArray<nsCString> portListArray;
ParseString(portList, ',', portListArray);
uint32_t index;
for (index = 0; index < portListArray.Length(); index++) {
portListArray[index].StripWhitespace();
int32_t portBegin, portEnd;
if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin,
&portEnd) == 2) {
if ((portBegin < 65536) && (portEnd < 65536)) {
int32_t curPort;
if (remove) {
for (curPort = portBegin; curPort <= portEnd; curPort++) {
restrictedPortList.RemoveElement(curPort);
}
} else {
for (curPort = portBegin; curPort <= portEnd; curPort++) {
restrictedPortList.AppendElement(curPort);
}
}
}
} else {
nsresult aErrorCode;
int32_t port = portListArray[index].ToInteger(&aErrorCode);
if (NS_SUCCEEDED(aErrorCode) && port < 65536) {
if (remove) {
restrictedPortList.RemoveElement(port);
} else {
restrictedPortList.AppendElement(port);
}
}
}
}
}
AutoWriteLock lock(mLock);
mRestrictedPortList.Assign(std::move(restrictedPortList));
}
class nsWakeupNotifier : public Runnable {
public:
explicit nsWakeupNotifier(nsIIOServiceInternal* ioService)
: Runnable("net::nsWakeupNotifier"), mIOService(ioService) {}
NS_IMETHOD Run() override { return mIOService->NotifyWakeup(); }
private:
virtual ~nsWakeupNotifier() = default;
nsCOMPtr<nsIIOServiceInternal> mIOService;
};
NS_IMETHODIMP
nsIOService::NotifyWakeup() {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
NS_ASSERTION(observerService, "The observer service should not be null");
if (observerService && StaticPrefs::network_notify_changed()) {
(void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
(u"" NS_NETWORK_LINK_DATA_CHANGED));
}
RecheckCaptivePortal();
return NS_OK;
}
void nsIOService::SetHttpHandlerAlreadyShutingDown() {
if (!mShutdown && !mOfflineForProfileChange) {
mNetTearingDownStarted = PR_IntervalNow();
mHttpHandlerAlreadyShutingDown = true;
}
}
// nsIObserver interface
NS_IMETHODIMP
nsIOService::Observe(nsISupports* subject, const char* topic,
const char16_t* data) {
if (UseSocketProcess() && SocketProcessReady() &&
mObserverTopicForSocketProcess.Contains(nsDependentCString(topic))) {
nsCString topicStr(topic);
nsString dataStr(data);
Unused << mSocketProcess->GetActor()->SendNotifyObserver(topicStr, dataStr);
}
if (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
if (!mHttpHandlerAlreadyShutingDown) {
mNetTearingDownStarted = PR_IntervalNow();
}
mHttpHandlerAlreadyShutingDown = false;
if (!mOffline) {
mOfflineForProfileChange = true;
SetOfflineInternal(true, false);
}
} else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
if (mOfflineForProfileChange) {
mOfflineForProfileChange = false;
SetOfflineInternal(false, false);
}
} else if (!strcmp(topic, kProfileDoChange)) {
if (data && u"startup"_ns.Equals(data)) {
// Lazy initialization of network link service (see bug 620472)
InitializeNetworkLinkService();
// Set up the initilization flag regardless the actuall result.
// If we fail here, we will fail always on.
mNetworkLinkServiceInitialized = true;
// And now reflect the preference setting
PrefsChanged(MANAGE_OFFLINE_STATUS_PREF);
// Bug 870460 - Read cookie database at an early-as-possible time
// off main thread. Hence, we have more chance to finish db query
// before something calls into the cookie service.
nsCOMPtr<nsISupports> cookieServ =
do_GetService(NS_COOKIESERVICE_CONTRACTID);
}
} else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
// Remember we passed XPCOM shutdown notification to prevent any
// changes of the offline status from now. We must not allow going
// online after this point.
mShutdown = true;
if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
mNetTearingDownStarted = PR_IntervalNow();
}
mHttpHandlerAlreadyShutingDown = false;
SetOfflineInternal(true, false);
if (mCaptivePortalService) {
static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
mCaptivePortalService = nullptr;
}
SSLTokensCache::Shutdown();
DestroySocketProcess();
if (IsSocketProcessChild()) {
Preferences::UnregisterCallbacks(nsIOService::OnTLSPrefChange,
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)) {
LOG(("nsIOService::OnNetworkLinkEvent Network id changed"));
} else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
// coming back alive from sleep
// this indirection brought to you by:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
NS_DispatchToMainThread(wakeupNotifier);
}
return NS_OK;
}
// nsINetUtil interface
NS_IMETHODIMP
nsIOService::ParseRequestContentType(const nsACString& aTypeHeader,
nsACString& aCharset, bool* aHadCharset,
nsACString& aContentType) {
net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
return NS_OK;
}
// nsINetUtil interface
NS_IMETHODIMP
nsIOService::ParseResponseContentType(const nsACString& aTypeHeader,
nsACString& aCharset, bool* aHadCharset,
nsACString& aContentType) {
net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
NS_ENSURE_ARG(uri);
*result = false;
nsAutoCString scheme;
nsresult rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
auto handler = LookupProtocolHandler(scheme);
uint32_t protocolFlags;
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;
}
NS_IMETHODIMP
nsIOService::URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
nsresult rv = ProtocolHasFlags(uri, flags, result);
NS_ENSURE_SUCCESS(rv, rv);
if (*result) {
return rv;
}
// Dig deeper into the chain. Note that this is not a do/while loop to
// avoid the extra addref/release on |uri| in the common (non-nested) case.
nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
while (nestedURI) {
nsCOMPtr<nsIURI> innerURI;
rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = ProtocolHasFlags(innerURI, flags, result);
if (*result) {
return rv;
}
nestedURI = do_QueryInterface(innerURI);
}
return rv;
}
NS_IMETHODIMP
nsIOService::SetManageOfflineStatus(bool aManage) {
LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage));
mManageLinkStatus = aManage;
// When detection is not activated, the default connectivity state is true.
if (!mManageLinkStatus) {
SetConnectivityInternal(true);
return NS_OK;
}
InitializeNetworkLinkService();
// If the NetworkLinkService is already initialized, it does not call
// OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from
// false to true.
OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::GetManageOfflineStatus(bool* aManage) {
*aManage = mManageLinkStatus;
return NS_OK;
}
// input argument 'data' is already UTF8'ed
nsresult nsIOService::OnNetworkLinkEvent(const char* data) {
if (IsNeckoChild() || IsSocketProcessChild()) {
// There is nothing IO service could do on the child process
// with this at the moment. Feel free to add functionality
// here at will, though.
return NS_OK;
}
if (mShutdown) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCString dataAsString(data);
for (auto* cp : mozilla::dom::ContentParent::AllProcesses(
mozilla::dom::ContentParent::eLive)) {
PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
if (!neckoParent) {
continue;
}
Unused << neckoParent->SendNetworkChangeNotification(dataAsString);
}
LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data));
if (!mNetworkLinkService) {
return NS_ERROR_FAILURE;
}
if (!mManageLinkStatus) {
LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n"));
return NS_OK;
}
bool isUp = true;
if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
mLastNetworkLinkChange = PR_IntervalNow();
// CHANGED means UP/DOWN didn't change
// but the status of the captive portal may have changed.
RecheckCaptivePortal();
return NS_OK;
}
if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
isUp = false;
} else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
isUp = true;
} else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
NS_ENSURE_SUCCESS(rv, rv);
} else {
NS_WARNING("Unhandled network event!");
return NS_OK;
}
return SetConnectivityInternal(isUp);
}
NS_IMETHODIMP
nsIOService::EscapeString(const nsACString& aString, uint32_t aEscapeType,
nsACString& aResult) {
NS_ENSURE_ARG_MAX(aEscapeType, 4);
nsAutoCString stringCopy(aString);
nsCString result;
if (!NS_Escape(stringCopy, result, (nsEscapeMask)aEscapeType)) {
return NS_ERROR_OUT_OF_MEMORY;
}
aResult.Assign(result);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::EscapeURL(const nsACString& aStr, uint32_t aFlags,
nsACString& aResult) {
aResult.Truncate();
NS_EscapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
aResult);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::UnescapeString(const nsACString& aStr, uint32_t aFlags,
nsACString& aResult) {
aResult.Truncate();
NS_UnescapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
aResult);
return NS_OK;
}
NS_IMETHODIMP
nsIOService::ExtractCharsetFromContentType(const nsACString& aTypeHeader,
nsACString& aCharset,
int32_t* aCharsetStart,
int32_t* aCharsetEnd,
bool* aHadCharset) {
nsAutoCString ignored;
net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
aCharsetStart, aCharsetEnd);
if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
*aHadCharset = false;
}
return NS_OK;
}
// nsISpeculativeConnect
class IOServiceProxyCallback final : public nsIProtocolProxyCallback {
~IOServiceProxyCallback() = default;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPROTOCOLPROXYCALLBACK
IOServiceProxyCallback(nsIInterfaceRequestor* aCallbacks,
nsIOService* aIOService)
: mCallbacks(aCallbacks), mIOService(aIOService) {}
private:
RefPtr<nsIInterfaceRequestor> mCallbacks;
RefPtr<nsIOService> mIOService;
};
NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
NS_IMETHODIMP
IOServiceProxyCallback::OnProxyAvailable(nsICancelable* request,
nsIChannel* channel, nsIProxyInfo* pi,
nsresult status) {
// Checking proxy status for speculative connect
nsAutoCString type;
if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
!type.EqualsLiteral("direct")) {
// proxies dont do speculative connect
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = channel->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return NS_OK;
}
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
if (NS_FAILED(rv)) return NS_OK;
nsCOMPtr<nsIProtocolHandler> handler;
rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
if (NS_FAILED(rv)) return NS_OK;
nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
do_QueryInterface(handler);
if (!speculativeHandler) return NS_OK;
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
nsLoadFlags loadFlags = 0;
channel->GetLoadFlags(&loadFlags);
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
speculativeHandler->SpeculativeAnonymousConnect(uri, principal, mCallbacks);
} else {
speculativeHandler->SpeculativeConnect(uri, principal, mCallbacks);
}
return NS_OK;
}
nsresult nsIOService::SpeculativeConnectInternal(
nsIURI* aURI, nsIPrincipal* aPrincipal, nsIInterfaceRequestor* aCallbacks,
bool aAnonymous) {
NS_ENSURE_ARG(aURI);
if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) {
// We don't speculatively connect to non-HTTP[S] URIs.
return NS_OK;
}
if (IsNeckoChild()) {
gNeckoChild->SendSpeculativeConnect(aURI, aPrincipal, aAnonymous);
return NS_OK;
}
// Check for proxy information. If there is a proxy configured then a
// speculative connect should not be performed because the potential
// reward is slim with tcp peers closely located to the browser.
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
MOZ_ASSERT(aPrincipal, "We expect passing a principal here.");
if (!aPrincipal) {
return NS_ERROR_INVALID_ARG;
}
// XXX Bug 1724080: Avoid TCP connections on port 80 when https-only
// or https-first is enabled. Let's create a dummy loadinfo which we
// only use to determine whether we need ot upgrade the speculative
// connection from http to https.
nsCOMPtr<nsIURI> httpsURI;
if (aURI->SchemeIs("http")) {
nsCOMPtr<nsILoadInfo> httpsOnlyCheckLoadInfo =
new LoadInfo(loadingPrincipal, loadingPrincipal, nullptr,
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
nsIContentPolicy::TYPE_SPECULATIVE);
// Check if https-only, or https-first would upgrade the request
if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, httpsOnlyCheckLoadInfo) ||
nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(
aURI, httpsOnlyCheckLoadInfo)) {
rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(httpsURI));
NS_ENSURE_SUCCESS(rv, rv);
aURI = httpsURI.get();
}
}
// dummy channel used to create a TCP connection.
// we perform security checks on the *real* channel, responsible
// for any network loads. this real channel just checks the TCP
// pool if there is an available connection created by the
// channel we create underneath - hence it's safe to use
// the systemPrincipal as the loadingPrincipal for this channel.
nsCOMPtr<nsIChannel> channel;
rv = NewChannelFromURI(
aURI,
nullptr, // aLoadingNode,
loadingPrincipal,
nullptr, // aTriggeringPrincipal,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_SPECULATIVE, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv);
if (aAnonymous) {
nsLoadFlags loadFlags = 0;
channel->GetLoadFlags(&loadFlags);
loadFlags |= nsIRequest::LOAD_ANONYMOUS;
channel->SetLoadFlags(loadFlags);
}
nsCOMPtr<nsICancelable> cancelable;
RefPtr<IOServiceProxyCallback> callback =
new IOServiceProxyCallback(aCallbacks, this);
nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
if (pps2) {
return pps2->AsyncResolve2(channel, 0, callback, nullptr,
getter_AddRefs(cancelable));
}
return pps->AsyncResolve(channel, 0, callback, nullptr,
getter_AddRefs(cancelable));
}
NS_IMETHODIMP
nsIOService::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
nsIInterfaceRequestor* aCallbacks) {
return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
}
NS_IMETHODIMP
nsIOService::SpeculativeAnonymousConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
nsIInterfaceRequestor* aCallbacks) {
return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
}
NS_IMETHODIMP
nsIOService::NotImplemented() { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHODIMP
nsIOService::GetSocketProcessLaunched(bool* aResult) {
NS_ENSURE_ARG_POINTER(aResult);
*aResult = SocketProcessReady();
return NS_OK;
}
bool nsIOService::HasObservers(const char* aTopic) {
MOZ_ASSERT(false, "Calling this method is unexpected");
return false;
}
NS_IMETHODIMP
nsIOService::GetSocketProcessId(uint64_t* aPid) {
NS_ENSURE_ARG_POINTER(aPid);
*aPid = 0;
if (!mSocketProcess) {
return NS_OK;
}
if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
*aPid = (uint64_t)actor->OtherPid();
}
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