summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/res
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp1041
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h236
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.cpp360
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.h120
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.cpp138
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.h68
-rw-r--r--netwerk/protocol/res/SubstitutingJARURI.h161
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp622
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h129
-rw-r--r--netwerk/protocol/res/SubstitutingURL.h66
-rw-r--r--netwerk/protocol/res/moz.build42
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl15
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl62
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp195
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h78
15 files changed, 3333 insertions, 0 deletions
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 0000000000..c4f711807f
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -0,0 +1,1041 @@
+/* -*- 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 "ExtensionProtocolHandler.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+
+#include "FileDescriptorFile.h"
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIJARChannel.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIJARURI.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+
+#if defined(XP_WIN)
+# include "nsILocalFileWin.h"
+# include "WinUtils.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#define EXTENSION_SCHEME "moz-extension"
+using mozilla::dom::Promise;
+using mozilla::ipc::FileDescriptor;
+
+// A list of file extensions containing purely static data, which can be loaded
+// from an extension before the extension is fully ready. The main purpose of
+// this is to allow image resources from theme XPIs to load quickly during
+// browser startup.
+//
+// The layout of this array is chosen in order to prevent the need for runtime
+// relocation, which an array of char* pointers would require. It also has the
+// benefit of being more compact when the difference in length between the
+// longest and average string is less than 8 bytes. The length of the
+// char[] array must match the size of the longest entry in the list.
+//
+// This list must be kept sorted.
+static const char sStaticFileExtensions[][5] = {
+ // clang-format off
+ "bmp",
+ "gif",
+ "ico",
+ "jpeg",
+ "jpg",
+ "png",
+ "svg",
+ // clang-format on
+};
+
+namespace mozilla {
+
+namespace net {
+
+using extensions::URLInfo;
+
+LazyLogModule gExtProtocolLog("ExtProtocol");
+#undef LOG
+#define LOG(...) MOZ_LOG(gExtProtocolLog, LogLevel::Debug, (__VA_ARGS__))
+
+StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream or file descriptor from the parent for a remote moz-extension load
+ * from the child.
+ */
+class ExtensionStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ // To use when getting a remote input stream for a resource
+ // in an unpacked extension.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo), mIsJarChannel(false) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+
+ SetupEventTarget();
+ }
+
+ // To use when getting an FD for a packed extension JAR file
+ // in order to load a resource.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ already_AddRefed<nsIJARChannel>&& aJarChannel,
+ nsIFile* aJarFile)
+ : mURI(aURI),
+ mLoadInfo(aLoadInfo),
+ mJarChannel(std::move(aJarChannel)),
+ mJarFile(aJarFile),
+ mIsJarChannel(true) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+ MOZ_ASSERT(mJarChannel);
+ MOZ_ASSERT(aJarFile);
+
+ SetupEventTarget();
+ }
+
+ void SetupEventTarget() {
+ mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
+ mLoadInfo, TaskCategory::Other);
+ if (!mMainThreadEventTarget) {
+ mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ // Get an input stream or file descriptor from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(already_AddRefed<nsIInputStream> aStream);
+
+ // Handle file descriptor being returned from the parent
+ void OnFD(const FileDescriptor& aFD);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~ExtensionStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIJARChannel> mJarChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ bool mIsJarChannel;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable)
+
+class ExtensionJARFileOpener final : public nsISupports {
+ public:
+ ExtensionJARFileOpener(nsIFile* aFile,
+ NeckoParent::GetExtensionFDResolver& aResolve)
+ : mFile(aFile), mResolve(aResolve) {
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aResolve);
+ }
+
+ NS_IMETHOD OpenFile() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ AutoFDClose prFileDesc;
+
+#if defined(XP_WIN)
+ nsresult rv;
+ nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv);
+ MOZ_ASSERT(winFile);
+ if (NS_SUCCEEDED(rv)) {
+ rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0,
+ &prFileDesc.rwget());
+ }
+#else
+ nsresult rv = mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget());
+#endif /* XP_WIN */
+
+ if (NS_SUCCEEDED(rv)) {
+ mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
+ PR_FileDesc2NativeHandle(prFileDesc)));
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ mozilla::NewRunnableMethod("ExtensionJarFileFDResolver", this,
+ &ExtensionJARFileOpener::SendBackFD);
+
+ rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
+ return NS_OK;
+ }
+
+ NS_IMETHOD SendBackFD() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mResolve(mFD);
+ mResolve = nullptr;
+ return NS_OK;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~ExtensionJARFileOpener() = default;
+
+ nsCOMPtr<nsIFile> mFile;
+ NeckoParent::GetExtensionFDResolver mResolve;
+ FileDescriptor mFD;
+};
+
+NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
+
+// The amount of time, in milliseconds, that the file opener thread will remain
+// allocated after it is used. This value chosen because to match other uses
+// of LazyIdleThread.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+// Request an FD or input stream from the parent.
+RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<ExtensionStreamGetter> self = this;
+ if (mIsJarChannel) {
+ // Request an FD for this moz-extension URI
+ gNeckoChild->SendGetExtensionFD(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const FileDescriptor& fd) { self->OnFD(fd); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnFD(FileDescriptor());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+ }
+
+ // Request an input stream for this moz-extension URI
+ gNeckoChild->SendGetExtensionStream(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const RefPtr<nsIInputStream>& stream) {
+ self->OnStream(do_AddRef(stream));
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(nullptr);
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+ExtensionStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ mPump = nullptr;
+ }
+
+ if (mIsJarChannel && mJarChannel) {
+ mJarChannel->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ }
+
+ return NS_OK;
+}
+
+// static
+void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "ExtensionStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!stream) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
+ 0, false, mMainThreadEventTarget);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+// Handle an FD sent from the parent.
+void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncOpen.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!aFD.IsValid()) {
+ // The parent didn't send us back a valid file descriptor.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
+ mJarChannel->SetJarFile(fdFile);
+ nsresult rv = mJarChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<ExtensionProtocolHandler>
+ExtensionProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new ExtensionProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+ExtensionProtocolHandler::ExtensionProtocolHandler()
+ : SubstitutingProtocolHandler(EXTENSION_SCHEME) {
+ // Note, extensions.webextensions.protocol.remote=false is for
+ // debugging purposes only. With process-level sandboxing, child
+ // processes (specifically content and extension processes), will
+ // not be able to load most moz-extension URI's when the pref is
+ // set to false.
+ mUseRemoteFileChannels =
+ IsNeckoChild() &&
+ Preferences::GetBool("extensions.webextensions.protocol.remote");
+}
+
+static inline ExtensionPolicyService& EPS() {
+ return ExtensionPolicyService::GetSingleton();
+}
+
+nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI,
+ uint32_t* aFlags) {
+ uint32_t flags =
+ URI_STD | URI_IS_LOCAL_RESOURCE | URI_IS_POTENTIALLY_TRUSTWORTHY;
+
+ URLInfo url(aURI);
+ if (auto* policy = EPS().GetByURL(url)) {
+ // In general a moz-extension URI is only loadable by chrome, but an
+ // allowlist subset are web-accessible (and cross-origin fetchable).
+ // The allowlist is checked using EPS.SourceMayLoadExtensionURI in
+ // BasePrincipal and nsScriptSecurityManager.
+ if (policy->IsWebAccessiblePath(url.FilePath())) {
+ if (policy->ManifestVersion() < 3) {
+ flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
+ } else {
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ }
+ } else if (policy->Type() == nsGkAtoms::theme) {
+ // Static themes cannot set web accessible resources, however using this
+ // flag here triggers SourceMayAccessPath calls necessary to allow another
+ // extension to access static theme resources in this extension.
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ } else {
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ // Disallow in private windows if the extension does not have permission.
+ if (!policy->PrivateBrowsingAllowed()) {
+ flags |= URI_DISALLOW_IN_PRIVATE_CONTEXT;
+ }
+ } else {
+ // In case there is no policy, then default to treating moz-extension URIs
+ // as unsafe and generally only allow chrome: to load such.
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ *aFlags = flags;
+ return NS_OK;
+}
+
+bool ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "The ExtensionPolicyService is not thread safe");
+ // Create special moz-extension://foo/_generated_background_page.html page
+ // for all registered extensions. We can't just do this as a substitution
+ // because substitutions can only match on host.
+ if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
+ return false;
+ }
+
+ if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
+ Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult);
+ return !aResult.IsEmpty();
+ }
+
+ return false;
+}
+
+// For file or JAR URI's, substitute in a remote channel.
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ nsAutoCString unResolvedSpec;
+ MOZ_TRY(aURI->GetSpec(unResolvedSpec));
+
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ // Use the target URI scheme to determine if this is a packed or unpacked
+ // extension URI. For unpacked extensions, we'll request an input stream
+ // from the parent. For a packed extension, we'll request a file descriptor
+ // for the JAR file.
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (scheme.EqualsLiteral("file")) {
+ // Unpacked extension
+ SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ return Ok();
+ }
+
+ if (scheme.EqualsLiteral("jar")) {
+ // Packed extension
+ return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ }
+
+ // Only unpacked resource files and JAR files are remoted.
+ // No other moz-extension loads should be reading from the filesystem.
+ return Ok();
+}
+
+void OpenWhenReady(
+ Promise* aPromise, nsIStreamListener* aListener, nsIChannel* aChannel,
+ const std::function<nsresult(nsIStreamListener*, nsIChannel*)>& aCallback) {
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+ nsCOMPtr<nsIChannel> channel(aChannel);
+
+ Unused << aPromise->ThenWithCycleCollectedArgs(
+ [channel, aCallback](
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ nsIStreamListener* aListener) -> already_AddRefed<Promise> {
+ nsresult rv = aCallback(aListener, channel);
+ if (NS_FAILED(rv)) {
+ ExtensionStreamGetter::CancelRequest(aListener, channel, rv);
+ }
+ return nullptr;
+ },
+ listener);
+}
+
+nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ if (mUseRemoteFileChannels) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
+ }
+
+ auto* policy = EPS().GetByURL(aURI);
+ NS_ENSURE_TRUE(policy, NS_ERROR_UNEXPECTED);
+
+ RefPtr<dom::Promise> readyPromise(policy->ReadyPromise());
+
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+ MOZ_TRY(rv);
+
+ nsAutoCString ext;
+ MOZ_TRY(url->GetFileExtension(ext));
+ ToLowerCase(ext);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (ext.EqualsLiteral("css")) {
+ // Filter CSS files to replace locale message tokens with localized strings.
+ static const auto convert = [](nsIStreamListener* listener,
+ nsIChannel* channel,
+ nsIChannel* origChannel) -> nsresult {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> convService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(channel->GetURI(getter_AddRefs(uri)));
+
+ const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+ const char* kToType = "text/css";
+
+ nsCOMPtr<nsIStreamListener> converter;
+ MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri,
+ getter_AddRefs(converter)));
+
+ return origChannel->AsyncOpen(converter);
+ };
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ if (readyPromise) {
+ nsCOMPtr<nsIChannel> chan(channel);
+ OpenWhenReady(
+ readyPromise, listener, origChannel,
+ [chan](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return convert(aListener, chan, aChannel);
+ });
+ } else {
+ MOZ_TRY(convert(listener, channel, origChannel));
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else if (readyPromise) {
+ size_t matchIdx;
+ if (BinarySearchIf(
+ sStaticFileExtensions, 0, ArrayLength(sStaticFileExtensions),
+ [&ext](const char* aOther) {
+ return Compare(ext, nsDependentCString(aOther));
+ },
+ &matchIdx)) {
+ // This is a static resource that shouldn't depend on the extension being
+ // ready. Don't bother waiting for it.
+ return NS_OK;
+ }
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ OpenWhenReady(readyPromise, listener, origChannel,
+ [](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return aChannel->AsyncOpen(aListener);
+ });
+
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
+ if (aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
+ (*result)->SetLoadInfo(loadInfo);
+ }
+
+ channel.swap(*result);
+ return NS_OK;
+}
+
+Result<bool, nsresult> ExtensionProtocolHandler::AllowExternalResource(
+ nsIFile* aExtensionDir, nsIFile* aRequestedFile) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+#if defined(XP_WIN)
+ // On Windows, dev builds don't use symlinks so we never need to
+ // allow a resource from outside of the extension dir.
+ return false;
+#else
+ if (!mozilla::IsDevelopmentBuild()) {
+ return false;
+ }
+
+ // On Mac and Linux unpackaged dev builds, system extensions use
+ // symlinks to point to resources in the repo dir which we have to
+ // allow loading. Before we allow an unpacked extension to load a
+ // resource outside of the extension dir, we make sure the extension
+ // dir is within the app directory.
+ bool result;
+ MOZ_TRY_VAR(result, AppDirContains(aExtensionDir));
+ if (!result) {
+ return false;
+ }
+
+# if defined(XP_MACOSX)
+ // Additionally, on Mac dev builds, we make sure that the requested
+ // resource is within the repo dir. We don't perform this check on Linux
+ // because we don't have a reliable path to the repo dir on Linux.
+ return DevRepoContains(aRequestedFile);
+# else /* XP_MACOSX */
+ return true;
+# endif
+#endif /* defined(XP_WIN) */
+}
+
+#if defined(XP_MACOSX)
+// The |aRequestedFile| argument must already be Normalize()'d
+Result<bool, nsresult> ExtensionProtocolHandler::DevRepoContains(
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(mozilla::IsDevelopmentBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mDevRepo
+ if (!mAlreadyCheckedDevRepo) {
+ mAlreadyCheckedDevRepo = true;
+ MOZ_TRY(nsMacUtilsImpl::GetRepoDir(getter_AddRefs(mDevRepo)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString repoPath;
+ Unused << mDevRepo->GetNativePath(repoPath);
+ LOG("Repo path: %s", repoPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mDevRepo) {
+ MOZ_TRY(mDevRepo->Contains(aRequestedFile, &result));
+ }
+ return result;
+}
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+Result<bool, nsresult> ExtensionProtocolHandler::AppDirContains(
+ nsIFile* aExtensionDir) {
+ MOZ_ASSERT(mozilla::IsDevelopmentBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mAppDir
+ if (!mAlreadyCheckedAppDir) {
+ mAlreadyCheckedAppDir = true;
+ MOZ_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString appDirPath;
+ Unused << mAppDir->GetNativePath(appDirPath);
+ LOG("AppDir path: %s", appDirPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mAppDir) {
+ MOZ_TRY(mAppDir->Contains(aExtensionDir, &result));
+ }
+ return result;
+}
+#endif /* !defined(XP_WIN) */
+
+static void LogExternalResourceError(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(aExtensionDir);
+ MOZ_ASSERT(aRequestedFile);
+
+ LOG("Rejecting external unpacked extension resource [%s] from "
+ "extension directory [%s]",
+ aRequestedFile->HumanReadablePath().get(),
+ aExtensionDir->HumanReadablePath().get());
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> ExtensionProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-extension because
+ // these requests ordinarily come from the child's ExtensionProtocolHandler.
+ // Ensure this request is for a moz-extension URI. A rogue child process
+ // could send us any URI.
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated because
+ // the error is likely to be due to a bug in the extension.
+ *aTerminateSender = false;
+
+ /*
+ * Make sure there is a substitution installed for the host found
+ * in the child's request URI and make sure the host resolves to
+ * a directory.
+ */
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // Lookup the directory this host string resolves to
+ nsCOMPtr<nsIURI> baseURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(baseURI)));
+
+ // The result should be a file URL for the extension base dir
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> extensionDir;
+ MOZ_TRY(fileURL->GetFile(getter_AddRefs(extensionDir)));
+
+ bool isDirectory = false;
+ MOZ_TRY(extensionDir->IsDirectory(&isDirectory));
+ if (!isDirectory) {
+ // The host should map to a directory for unpacked extensions
+ return Err(NS_ERROR_FILE_NOT_DIRECTORY);
+ }
+
+ // Make sure the child URI resolves to a file URI then get a file
+ // channel for the request. The resultant channel should be a
+ // file channel because we only request remote streams for unpacked
+ // extension resource loads where the URI resolves to a file.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aChildURI, resolvedSpec));
+
+ nsAutoCString resolvedScheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, resolvedScheme));
+ if (!resolvedScheme.EqualsLiteral("file")) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ MOZ_TRY(ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI)));
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-extension scheme and that the URI host maps to a directory.
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_TRY(NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER));
+
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> requestedFile;
+ MOZ_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile)));
+
+ /*
+ * Make sure the file we resolved to is within the extension directory.
+ */
+
+ // Normalize paths for sane comparisons. nsIFile::Contains depends on
+ // it for reliable subpath checks.
+ MOZ_TRY(extensionDir->Normalize());
+ MOZ_TRY(requestedFile->Normalize());
+#if defined(XP_WIN)
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(extensionDir) ||
+ !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) {
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+#endif
+
+ bool isResourceFromExtensionDir = false;
+ MOZ_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
+ if (!isResourceFromExtensionDir) {
+ bool isAllowed;
+ MOZ_TRY_VAR(isAllowed, AllowExternalResource(extensionDir, requestedFile));
+ if (!isAllowed) {
+ LogExternalResourceError(extensionDir, requestedFile);
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ MOZ_TRY_VAR(inputStream,
+ NS_NewLocalFileInputStream(requestedFile, PR_RDONLY, -1,
+ nsIFileInputStream::DEFER_OPEN));
+
+ return inputStream;
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::NewFD(
+ nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // Ensure this is a moz-extension URI
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated.
+ *aTerminateSender = false;
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // We expect the host string to map to a JAR file because the URI
+ // should refer to a web accessible resource for an enabled extension.
+ nsCOMPtr<nsIURI> subURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(subURI)));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ if (!mFileOpenerThread) {
+ mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ "ExtensionProtocolHandler"_ns);
+ }
+
+ RefPtr<ExtensionJARFileOpener> fileOpener =
+ new ExtensionJARFileOpener(jarFile, aResolve);
+
+ nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
+ "ExtensionJarFileOpener", fileOpener, &ExtensionJARFileOpener::OpenFile);
+
+ MOZ_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL));
+
+ return Ok();
+}
+
+// Set the channel's content type using the provided URI's type
+
+// static
+void ExtensionProtocolHandler::SetContentType(nsIURI* aURI,
+ nsIChannel* aChannel) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aURI, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ Unused << aChannel->SetContentType(contentType);
+ }
+ }
+}
+
+// Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ ExtensionStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel);
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+// Gets a SimpleChannel that wraps the provided channel
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aChannel,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ nsresult rv = origChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ simpleChannel->Cancel(NS_BINDING_ABORTED);
+ return Err(rv);
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+void ExtensionProtocolHandler::SubstituteRemoteFileChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+}
+
+static Result<Ok, nsresult> LogCacheCheck(const nsIJARChannel* aJarChannel,
+ nsIJARURI* aJarURI, bool aIsCached) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(aJarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ nsAutoCString uriSpec, jarSpec;
+ Unused << aJarURI->GetSpec(uriSpec);
+ Unused << innerFileURI->GetSpec(jarSpec);
+ LOG("[JARChannel %p] Cache %s: %s (%s)", aJarChannel,
+ aIsCached ? "hit" : "miss", uriSpec.get(), jarSpec.get());
+
+ return Ok();
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteJarChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ nsresult rv;
+
+ // Build a JAR URI for this jar:file:// URI and use it to extract the
+ // inner file URI.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv);
+ MOZ_TRY(rv);
+
+ bool isCached = false;
+ MOZ_TRY(jarChannel->EnsureCached(&isCached));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ Unused << LogCacheCheck(jarChannel, jarURI, isCached);
+ }
+
+ if (isCached) {
+ // Using a SimpleChannel with an ExtensionStreamGetter here (like the
+ // non-cached JAR case) isn't needed to load the extension resource
+ // because we don't need to ask the parent for an FD for the JAR, but
+ // wrapping the JARChannel in a SimpleChannel allows HTTP forwarding to
+ // moz-extension URI's to work because HTTP forwarding requires the
+ // target channel implement nsIChildChannel.
+ NewSimpleChannel(aURI, aLoadinfo, jarChannel.get(), aRetVal);
+ return Ok();
+ }
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.h b/netwerk/protocol/res/ExtensionProtocolHandler.h
new file mode 100644
index 0000000000..0824d7dd99
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ExtensionProtocolHandler_h___
+#define ExtensionProtocolHandler_h___
+
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Result.h"
+#include "SubstitutingProtocolHandler.h"
+
+namespace mozilla {
+namespace net {
+
+class ExtensionStreamGetter;
+
+class ExtensionProtocolHandler final
+ : public nsISubstitutingProtocolHandler,
+ public nsIProtocolHandlerWithDynamicFlags,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<ExtensionProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for a
+ * a web accessible resource from an unpacked WebExtension dir.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to a
+ * resource file within the extension could be the result of a bug
+ * in the extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<nsCOMPtr<nsIInputStream>, nsresult> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ /**
+ * To be called in the parent process to obtain a file descriptor for an
+ * enabled WebExtension JAR file.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @param aPromise a promise that will be resolved asynchronously when the
+ * file descriptor is available.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to an
+ * enabled WebExtension JAR could be the result of a bug in the
+ * extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<Ok, nsresult> NewFD(nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve);
+
+ protected:
+ ~ExtensionProtocolHandler() = default;
+
+ private:
+ explicit ExtensionProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ // |result| is an inout param. On entry to this function, *result
+ // is expected to be non-null and already addrefed. This function
+ // may release the object stored in *result on entry and write
+ // a new pointer to an already addrefed channel to *result.
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override;
+
+ /**
+ * For moz-extension URI's that resolve to file or JAR URI's, replaces
+ * the provided channel with a channel that will proxy the load to the
+ * parent process. For moz-extension URI's that resolve to other types
+ * of URI's (not file or JAR), the provide channel is not replaced and
+ * NS_OK is returned.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the channel does not need to be substituted or
+ * or the replacement channel was created successfully.
+ * Otherwise returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a file channel with a remote file channel for loading a
+ * web accessible resource for an unpacked extension from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The reference to the input param file channel is dropped and
+ * replaced with a reference to a new channel that remotes
+ * the file access. The new channel encapsulates a request to
+ * the parent for an IPCStream for the file.
+ */
+ void SubstituteRemoteFileChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a JAR channel with a remote JAR channel for loading a
+ * an extension JAR file from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The input param JAR channel is replaced with a new channel
+ * that remotes the JAR file access. The new channel encapsulates
+ * a request to the parent for the JAR file FD.
+ */
+ Result<Ok, nsresult> SubstituteRemoteJarChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Sets the aResult outparam to true if this unpacked extension load of
+ * a resource that is outside the extension dir should be allowed. This
+ * is only allowed for system extensions on Mac and Linux dev builds.
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AllowExternalResource(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile);
+
+ // Set the channel's content type using the provided URI's type
+ static void SetContentType(nsIURI* aURI, nsIChannel* aChannel);
+
+ // Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+
+ // Gets a SimpleChannel that wraps the provided channel
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel, nsIChannel** aRetVal);
+
+#if defined(XP_MACOSX)
+ /**
+ * Sets the aResult outparam to true if we are a developer build with the
+ * repo dir environment variable set and the requested file resides in the
+ * repo dir. Developer builds may load system extensions with web-accessible
+ * resources that are symlinks to files in the repo dir. This method is for
+ * checking if an unpacked resource requested by the child is from the repo.
+ * The requested file must be already Normalized(). Only compile this for
+ * Mac because the repo dir isn't always available on Linux.
+ *
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> DevRepoContains(nsIFile* aRequestedFile);
+
+ // On development builds, this points to development repo. Lazily set.
+ nsCOMPtr<nsIFile> mDevRepo;
+
+ // Set to true once we've already tried to load the dev repo path,
+ // allowing for lazy initialization of |mDevRepo|.
+ bool mAlreadyCheckedDevRepo{false};
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+ /**
+ * Sets the aResult outparam to true if we are a developer build and the
+ * provided directory is within the NS_GRE_DIR directory. Developer builds
+ * may load system extensions with web-accessible resources that are symlinks
+ * to files outside of the extension dir to the repo dir. This method is for
+ * checking if an extension directory is within NS_GRE_DIR. In that case, we
+ * consider the extension a system extension and allow it to use symlinks to
+ * resources outside of the extension dir. This exception is only applied
+ * to loads for unpacked extensions in unpackaged developer builds.
+ * The requested dir must be already Normalized().
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AppDirContains(nsIFile* aExtensionDir);
+
+ // On development builds, cache the NS_GRE_DIR repo. Lazily set.
+ nsCOMPtr<nsIFile> mAppDir;
+
+ // Set to true once we've already read the AppDir, allowing for lazy
+ // initialization of |mAppDir|.
+ bool mAlreadyCheckedAppDir{false};
+#endif /* !defined(XP_WIN) */
+
+ // Used for opening JAR files off the main thread when we just need to
+ // obtain a file descriptor to send back to the child.
+ RefPtr<mozilla::LazyIdleThread> mFileOpenerThread;
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-extension requests from the child.
+ static StaticRefPtr<ExtensionProtocolHandler> sSingleton;
+
+ // Set to true when this instance of the handler must proxy loads of
+ // extension web-accessible resources to the parent process.
+ bool mUseRemoteFileChannels;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* ExtensionProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.cpp b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
new file mode 100644
index 0000000000..065fd7c36e
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
@@ -0,0 +1,360 @@
+/* -*- 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 "PageThumbProtocolHandler.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIPageThumbsStorageService.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+#include "nsICancelable.h"
+
+#ifdef MOZ_PLACES
+# include "nsIPlacesPreviewsHelperService.h"
+#endif
+
+#define PAGE_THUMB_HOST "thumbnails"
+#define PLACES_PREVIEWS_HOST "places-previews"
+#define PAGE_THUMB_SCHEME "moz-page-thumb"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
+
+#undef LOG
+#define LOG(level, ...) \
+ MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
+
+StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton;
+
+NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<PageThumbProtocolHandler>
+PageThumbProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new PageThumbProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return do_AddRef(sSingleton);
+}
+
+// A moz-page-thumb URI is only loadable by chrome pages in the parent process,
+// or privileged content running in the privileged about content process.
+PageThumbProtocolHandler::PageThumbProtocolHandler()
+ : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {}
+
+RefPtr<RemoteStreamPromise> PageThumbProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aChildURI || !aTerminateSender) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
+ }
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-page-thumb because
+ // these requests ordinarily come from the child's PageThumbProtocolHandler.
+ // Ensure this request is for a moz-page-thumb URI. A compromised child
+ // process could send us any URI.
+ bool isPageThumbScheme = false;
+ if (NS_FAILED(aChildURI->SchemeIs(PAGE_THUMB_SCHEME, &isPageThumbScheme)) ||
+ !isPageThumbScheme) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
+ __func__);
+ }
+
+ // We should never receive a URI that does not have "thumbnails" as the host.
+ nsAutoCString host;
+ if (NS_FAILED(aChildURI->GetAsciiHost(host)) ||
+ !(host.EqualsLiteral(PAGE_THUMB_HOST) ||
+ host.EqualsLiteral(PLACES_PREVIEWS_HOST))) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child process to be terminated.
+ *aTerminateSender = false;
+
+ // Make sure the child URI resolves to a file URI. We will then get a file
+ // channel for the request. The resultant channel should be a file channel
+ // because we only request remote streams for resource loads where the URI
+ // resolves to a file.
+ nsAutoCString resolvedSpec;
+ rv = ResolveURI(aChildURI, resolvedSpec);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString resolvedScheme;
+ rv = net_ExtractURLScheme(resolvedSpec, resolvedScheme);
+ if (NS_FAILED(rv) || !resolvedScheme.EqualsLiteral("file")) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI));
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-page-thumb scheme and that the URI host matches PAGE_THUMB_HOST.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<RemoteStreamPromise>>();
+ RefPtr<RemoteStreamPromise> promise = promiseHolder->Ensure(__func__);
+
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aChildURI, contentType);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "PageThumbProtocolHandler::NewStream",
+ [contentType, channel, holder = std::move(promiseHolder)]() {
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChannel =
+ do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> requestedFile;
+ rv = fileChannel->GetFile(getter_AddRefs(requestedFile));
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ requestedFile, PR_RDONLY, -1);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ RemoteStreamInfo info(inputStream, contentType, -1);
+
+ holder->Resolve(std::move(info), __func__);
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ return promise;
+}
+
+bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ // This should match the scheme in PageThumbs.jsm. We will only resolve
+ // URIs for thumbnails generated by PageThumbs here.
+ if (!aHost.EqualsLiteral(PAGE_THUMB_HOST) &&
+ !aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ // moz-page-thumb should always have a "thumbnails" host. We do not intend
+ // to allow substitution rules to be created for moz-page-thumb.
+ return false;
+ }
+
+ // Regardless of the outcome, the scheme will be resolved to file://.
+ aResult.Assign("file://");
+
+ if (IsNeckoChild()) {
+ // We will resolve the URI in the parent if load is performed in the child
+ // because the child does not have access to the profile directory path.
+ // Technically we could retrieve the path from dom::ContentChild, but I
+ // would prefer to obtain the path from PageThumbsStorageService (which
+ // depends on OS.Path). Here, we resolve to the same URI, with the file://
+ // scheme. This won't ever be accessed directly by the content process,
+ // and is mainly used to keep the substitution protocol handler mechanism
+ // happy.
+ aResult.Append(aHost);
+ aResult.Append(aPath);
+ } else {
+ // Resolve the URI in the parent to the thumbnail file URI since we will
+ // attempt to open the channel to load the file after this.
+ nsAutoString thumbnailUrl;
+ nsresult rv = GetThumbnailPath(aPath, aHost, thumbnailUrl);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
+ }
+
+ return true;
+}
+
+nsresult PageThumbProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal) {
+ // Check if URI resolves to a file URI.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Load the URI remotely if accessed from a child.
+ if (IsNeckoChild()) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aRetVal));
+ }
+
+ return NS_OK;
+}
+
+Result<Ok, nsresult> PageThumbProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+#ifdef DEBUG
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ MOZ_ASSERT(scheme.EqualsLiteral("file"));
+#endif /* DEBUG */
+
+ RefPtr<RemoteStreamGetter> streamGetter =
+ new RemoteStreamGetter(aURI, aLoadInfo);
+
+ NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath,
+ const nsACString& aHost,
+ nsString& aThumbnailPath) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // Ensures that the provided path has a query string. We will start parsing
+ // from there.
+ int32_t queryIndex = aPath.FindChar('?');
+ if (queryIndex <= 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Extract URL from query string.
+ nsAutoString url;
+ bool found =
+ URLParams::Extract(Substring(aPath, queryIndex + 1), u"url"_ns, url);
+ if (!found || url.IsVoid()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ if (aHost.EqualsLiteral(PAGE_THUMB_HOST)) {
+ nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage =
+ do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // Use PageThumbsStorageService to get the local file path of the screenshot
+ // for the given URL.
+ rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath);
+#ifdef MOZ_PLACES
+ } else if (aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ nsCOMPtr<nsIPlacesPreviewsHelperService> helper =
+ do_GetService("@mozilla.org/places/previews-helper;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = helper->GetFilePathForURL(url, aThumbnailPath);
+#endif
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+// static
+void PageThumbProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ RemoteStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel,
+ &NeckoChild::SendGetPageThumbStream);
+ });
+
+ channel.swap(*aRetVal);
+}
+
+#undef LOG
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.h b/netwerk/protocol/res/PageThumbProtocolHandler.h
new file mode 100644
index 0000000000..da670d52d4
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 PageThumbProtocolHandler_h___
+#define PageThumbProtocolHandler_h___
+
+#include "mozilla/Result.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/RemoteStreamGetter.h"
+#include "SubstitutingProtocolHandler.h"
+#include "nsIInputStream.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class RemoteStreamGetter;
+
+class PageThumbProtocolHandler final : public nsISubstitutingProtocolHandler,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<PageThumbProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for the
+ * given thumbnail.
+ *
+ * @param aChildURI a moz-page-thumb URI sent from the child.
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-page-thumb URI, the child is in an invalid state and
+ * should be terminated. This outparam will be set synchronously.
+ *
+ * @return RemoteStreamPromise
+ * The RemoteStreamPromise will resolve with an RemoteStreamInfo on
+ * success, and reject with an nsresult on failure.
+ */
+ RefPtr<RemoteStreamPromise> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ protected:
+ ~PageThumbProtocolHandler() = default;
+
+ private:
+ explicit PageThumbProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ /**
+ * On entry to this function, *aRetVal is expected to be non-null and already
+ * addrefed. This function may release the object stored in *aRetVal on entry
+ * and write a new pointer to an already addrefed channel to *aRetVal.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if channel has been substituted successfully or no
+ * substitution at all. Otherwise, returns an error. This function
+ * will return NS_ERROR_NO_INTERFACE if the URI resolves to a
+ * non file:// URI.
+ */
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) override;
+
+ /**
+ * This replaces the provided channel with a channel that will proxy the load
+ * to the parent process.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the replacement channel was created successfully.
+ * Otherwise, returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /*
+ * Extracts the URL from the query string in the given moz-page-thumb URI
+ * and queries PageThumbsStorageService using the extracted URL to obtain
+ * the local file path of the screenshot. This should only be called from
+ * the parent because PageThumbsStorageService relies on the path of the
+ * profile directory, which is unavailable in the child.
+ *
+ * @param aPath the path of the moz-page-thumb URI.
+ * @param aHost the host of the moz-page-thumb URI.
+ * @param aThumbnailPath in/out string param referring to the thumbnail path.
+ * @return NS_OK if the thumbnail path was obtained successfully. Otherwise
+ * returns an error.
+ */
+ nsresult GetThumbnailPath(const nsACString& aPath, const nsACString& aHost,
+ nsString& aThumbnailPath);
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-page-thumb requests from the child.
+ static StaticRefPtr<PageThumbProtocolHandler> sSingleton;
+
+ // Gets a SimpleChannel that wraps the provided channel.
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* PageThumbProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/RemoteStreamGetter.cpp b/netwerk/protocol/res/RemoteStreamGetter.cpp
new file mode 100644
index 0000000000..e5c218b3c0
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "RemoteStreamGetter.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsContentUtils.h"
+#include "nsIInputStreamPump.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(RemoteStreamGetter, nsICancelable)
+
+RemoteStreamGetter::RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+}
+
+// Request an input stream from the parent.
+RequestOrReason RemoteStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ Method aMethod) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(aMethod);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<RemoteStreamGetter> self = this;
+ Maybe<LoadInfoArgs> loadInfoArgs;
+ nsresult rv = ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ (gNeckoChild->*aMethod)(mURI, loadInfoArgs)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](const Maybe<RemoteStreamInfo>& info) { self->OnStream(info); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(Nothing());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+RemoteStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->Cancel(aStatus);
+ mPump = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// static
+void RemoteStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel, nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "RemoteStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void RemoteStreamGetter::OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener until we pass it on
+ // to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+ if (aStreamInfo.isNothing()) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStreamInfo.ref().inputStream());
+ if (!stream) {
+ // We somehow failed to get a stream, so just cancel the request.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv =
+ NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, 0, false,
+ GetMainThreadSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ channel->SetContentType(aStreamInfo.ref().contentType());
+ channel->SetContentLength(aStreamInfo.ref().contentLength());
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/RemoteStreamGetter.h b/netwerk/protocol/res/RemoteStreamGetter.h
new file mode 100644
index 0000000000..5a4dae43a4
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.h
@@ -0,0 +1,68 @@
+/* -*- 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 RemoteStreamGetter_h___
+#define RemoteStreamGetter_h___
+
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsICancelable.h"
+#include "SimpleChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Maybe.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+using RemoteStreamPromise =
+ mozilla::MozPromise<RemoteStreamInfo, nsresult, false>;
+using Method = RefPtr<
+ MozPromise<Maybe<RemoteStreamInfo>, ipc::ResponseRejectReason, true>> (
+ PNeckoChild::*)(nsIURI*, const Maybe<LoadInfoArgs>&);
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream and metadata from the parent for a remote protocol load from the
+ * child.
+ */
+class RemoteStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+ // Get an input stream from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel,
+ Method aMethod);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~RemoteStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* RemoteStreamGetter_h___ */
diff --git a/netwerk/protocol/res/SubstitutingJARURI.h b/netwerk/protocol/res/SubstitutingJARURI.h
new file mode 100644
index 0000000000..fe9208750c
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -0,0 +1,161 @@
+/* -*- 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 SubstitutingJARURI_h
+#define SubstitutingJARURI_h
+
+#include "nsIURL.h"
+#include "nsJARURI.h"
+#include "nsISerializable.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_SUBSTITUTINGJARURI_IMPL_CID \
+ { /* 8f8c54ed-aba7-4ebf-ba6f-e58aec0aba4c */ \
+ 0x8f8c54ed, 0xaba7, 0x4ebf, { \
+ 0xba, 0x6f, 0xe5, 0x8a, 0xec, 0x0a, 0xba, 0x4c \
+ } \
+ }
+
+// Provides a Substituting URI for resource://-like substitutions which
+// allows consumers to access the underlying jar resource.
+class SubstitutingJARURI : public nsIJARURI,
+ public nsIStandardURL,
+ public nsISerializable {
+ protected:
+ // Contains the resource://-like URI to be mapped. nsIURI and nsIURL will
+ // forward to this.
+ nsCOMPtr<nsIURL> mSource;
+ // Contains the resolved jar resource, nsIJARURI forwards to this to let
+ // consumer acccess the underlying jar resource.
+ nsCOMPtr<nsIJARURI> mResolved;
+ virtual ~SubstitutingJARURI() = default;
+
+ public:
+ SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved);
+ // For deserializing
+ explicit SubstitutingJARURI() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERIALIZABLE
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+ NS_FORWARD_SAFE_NSIURL(mSource)
+ NS_FORWARD_SAFE_NSIJARURI(mResolved)
+
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef };
+
+ // We cannot forward Equals* methods to |mSource| so we override them here
+ NS_IMETHOD EqualsExceptRef(nsIURI* aOther, bool* aResult) override;
+ NS_IMETHOD Equals(nsIURI* aOther, bool* aResult) override;
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult);
+
+ // Forward the rest of nsIURI to mSource
+ NS_IMETHOD GetSpec(nsACString& aSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetSpec(aSpec);
+ }
+ NS_IMETHOD GetPrePath(nsACString& aPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPrePath(aPrePath);
+ }
+ NS_IMETHOD GetScheme(nsACString& aScheme) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetScheme(aScheme);
+ }
+ NS_IMETHOD GetUserPass(nsACString& aUserPass) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUserPass(aUserPass);
+ }
+ NS_IMETHOD GetUsername(nsACString& aUsername) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUsername(aUsername);
+ }
+ NS_IMETHOD GetPassword(nsACString& aPassword) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPassword(aPassword);
+ }
+ NS_IMETHOD GetHostPort(nsACString& aHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHostPort(aHostPort);
+ }
+ NS_IMETHOD GetHost(nsACString& aHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHost(aHost);
+ }
+ NS_IMETHOD GetPort(int32_t* aPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPort(aPort);
+ }
+ NS_IMETHOD GetPathQueryRef(nsACString& aPathQueryRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetPathQueryRef(aPathQueryRef);
+ }
+ NS_IMETHOD SchemeIs(const char* scheme, bool* _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->SchemeIs(scheme, _retval);
+ }
+ NS_IMETHOD Resolve(const nsACString& relativePath,
+ nsACString& _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->Resolve(relativePath, _retval);
+ }
+ NS_IMETHOD GetAsciiSpec(nsACString& aAsciiSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiSpec(aAsciiSpec);
+ }
+ NS_IMETHOD GetAsciiHostPort(nsACString& aAsciiHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetAsciiHostPort(aAsciiHostPort);
+ }
+ NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiHost(aAsciiHost);
+ }
+ NS_IMETHOD GetRef(nsACString& aRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetRef(aRef);
+ }
+ NS_IMETHOD GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetSpecIgnoringRef(aSpecIgnoringRef);
+ }
+ NS_IMETHOD GetHasRef(bool* aHasRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasRef(aHasRef);
+ }
+ NS_IMETHOD GetFilePath(nsACString& aFilePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetFilePath(aFilePath);
+ }
+ NS_IMETHOD GetQuery(nsACString& aQuery) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetQuery(aQuery);
+ }
+ NS_IMETHOD GetDisplayHost(nsACString& aDisplayHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHost(aDisplayHost);
+ }
+ NS_IMETHOD GetDisplayHostPort(nsACString& aDisplayHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHostPort(aDisplayHostPort);
+ }
+ NS_IMETHOD GetDisplaySpec(nsACString& aDisplaySpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplaySpec(aDisplaySpec);
+ }
+ NS_IMETHOD GetDisplayPrePath(nsACString& aDisplayPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayPrePath(aDisplayPrePath);
+ }
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->Mutate(_retval);
+ }
+ NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override {
+ MOZ_ASSERT(mSource);
+ mSource->Serialize(aParams);
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SubstitutingJARURI,
+ NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingJARURI_h */
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
new file mode 100644
index 0000000000..aaab858605
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,622 @@
+/* -*- 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 "mozilla/ModuleUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "SubstitutingURL.h"
+#include "SubstitutingJARURI.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIClassInfoImpl.h"
+
+using mozilla::dom::ContentParent;
+
+namespace mozilla {
+namespace net {
+
+// Log module for Substituting Protocol logging. We keep the pre-existing module
+// name of "nsResProtocol" to avoid disruption.
+static LazyLogModule gResLog("nsResProtocol");
+
+static NS_DEFINE_CID(kSubstitutingJARURIImplCID,
+ NS_SUBSTITUTINGJARURI_IMPL_CID);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+//---------------------------------------------------------------------------------
+
+// The list of interfaces should be in sync with nsStandardURL
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+NS_IMPL_CLASSINFO(SubstitutingURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SUBSTITUTINGURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(SubstitutingURL)
+
+NS_IMPL_ADDREF_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_RELEASE_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(SubstitutingURL, nsStandardURL)
+
+nsresult SubstitutingURL::EnsureFile() {
+ nsAutoCString ourScheme;
+ nsresult rv = GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the handler associated with this scheme. It would be nice to just
+ // pass this in when constructing SubstitutingURLs, but we need a generic
+ // factory constructor.
+ nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISubstitutingProtocolHandler> substHandler =
+ do_QueryInterface(handler);
+ MOZ_ASSERT(substHandler);
+
+ nsAutoCString spec;
+ rv = substHandler->ResolveURI(this, spec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(spec, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // Bug 585869:
+ // In most cases, the scheme is jar if it's not file.
+ // Regardless, net_GetFileFromURLSpec should be avoided
+ // when the scheme isn't file.
+ if (!scheme.EqualsLiteral("file")) return NS_ERROR_NO_INTERFACE;
+
+ return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
+}
+
+/* virtual */
+nsStandardURL* SubstitutingURL::StartClone() {
+ SubstitutingURL* clone = new SubstitutingURL();
+ return clone;
+}
+
+void SubstitutingURL::Serialize(ipc::URIParams& aParams) {
+ nsStandardURL::Serialize(aParams);
+ aParams.get_StandardURLParams().isSubstituting() = true;
+}
+
+// SubstitutingJARURI
+
+SubstitutingJARURI::SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved)
+ : mSource(source), mResolved(resolved) {}
+
+// SubstitutingJARURI::nsIURI
+
+NS_IMETHODIMP
+SubstitutingJARURI::Equals(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eHonorRef, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::EqualsExceptRef(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eIgnoreRef, aResult);
+}
+
+nsresult SubstitutingJARURI::EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult) {
+ *aResult = false;
+ if (!aOther) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ RefPtr<SubstitutingJARURI> other;
+ rv =
+ aOther->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // We only need to check the source as the resolved URI is the same for a
+ // given source
+ return aRefHandlingMode == eHonorRef
+ ? mSource->Equals(other->mSource, aResult)
+ : mSource->EqualsExceptRef(other->mSource, aResult);
+}
+
+// SubstitutingJARURI::nsISerializable
+
+NS_IMETHODIMP
+SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT(!mSource);
+ MOZ_ASSERT(!mResolved);
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> source;
+ rv = aStream->ReadObject(true, getter_AddRefs(source));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSource = do_QueryInterface(source, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> resolved;
+ rv = aStream->ReadObject(true, getter_AddRefs(resolved));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResolved = do_QueryInterface(resolved, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Write(nsIObjectOutputStream* aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ rv = aStream->WriteCompoundObject(mSource, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteCompoundObject(mResolved, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, nsIClassInfo::MAIN_THREAD_ONLY,
+ NS_SUBSTITUTINGJARURI_CID)
+
+NS_IMPL_ADDREF(SubstitutingJARURI)
+NS_IMPL_RELEASE(SubstitutingJARURI)
+
+NS_INTERFACE_MAP_BEGIN(SubstitutingJARURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ if (aIID.Equals(kSubstitutingJARURIImplCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_IMPL_QUERY_CLASSINFO(SubstitutingJARURI)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(SubstitutingJARURI, nsIURI, nsIJARURI, nsIURL,
+ nsIStandardURL, nsISerializable)
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme),
+ mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
+ mSubstitutions(16),
+ mEnforceFileOrJar(aEnforceFileOrJar) {
+ ConstructInternal();
+}
+
+void SubstitutingProtocolHandler::ConstructInternal() {
+ nsresult rv;
+ mIOService = do_GetIOService(&rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
+}
+
+//
+// IPC marshalling.
+//
+
+nsresult SubstitutingProtocolHandler::CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings) {
+ AutoReadLock lock(mSubstitutionsLock);
+ for (const auto& substitutionEntry : mSubstitutions) {
+ const SubstitutionEntry& entry = substitutionEntry.GetData();
+ nsCOMPtr<nsIURI> uri = entry.baseURI;
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ SubstitutionMapping substitution = {mScheme,
+ nsCString(substitutionEntry.GetKey()),
+ serialized, entry.flags};
+ aMappings.AppendElement(substitution);
+ }
+
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ if (GeckoProcessType_Content == XRE_GetProcessType()) {
+ return NS_OK;
+ }
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length()) {
+ return NS_OK;
+ }
+
+ SubstitutionMapping mapping;
+ mapping.scheme = mScheme;
+ mapping.path = aRoot;
+ if (aBaseURI) {
+ nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mapping.flags = aFlags;
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendRegisterChromeItem(mapping);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsIProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::NewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** aResult) {
+ // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
+ // Later net_GetFileFromURLSpec() will do a full unescape and we want to
+ // treat them the same way the file system will. (bugs 380994, 394075)
+ nsresult rv;
+ nsAutoCString spec;
+ const char* src = aSpec.BeginReading();
+ const char* end = aSpec.EndReading();
+ const char* last = src;
+
+ spec.SetCapacity(aSpec.Length() + 1);
+ for (; src < end; ++src) {
+ if (*src == '%' && (src < end - 2) && *(src + 1) == '2') {
+ char ch = '\0';
+ if (*(src + 2) == 'f' || *(src + 2) == 'F') {
+ ch = '/';
+ } else if (*(src + 2) == 'e' || *(src + 2) == 'E') {
+ ch = '.';
+ }
+
+ if (ch) {
+ if (last < src) {
+ spec.Append(last, src - last);
+ }
+ spec.Append(ch);
+ src += 2;
+ last = src + 1; // src will be incremented by the loop
+ }
+ }
+ if (*src == '?' || *src == '#') {
+ break; // Don't escape %2f and %2e in the query or ref parts of the URI
+ }
+ }
+
+ if (last < end) {
+ spec.Append(last, end - last);
+ }
+
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ nsCOMPtr<nsIURL> uri;
+ rv =
+ NS_MutateURI(new SubstitutingURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, spec, aCharset, base, nullptr)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // "android" is the only root that would return the RESOLVE_JAR_URI flag
+ // see nsResProtocolHandler::GetSubstitutionInternal
+ if (MustResolveJAR(host)) {
+ return ResolveJARURI(uri, aResult);
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveJARURI(nsIURL* aURL,
+ nsIURI** aResult) {
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(aURL, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = NS_NewURI(getter_AddRefs(resolvedURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(resolvedURI);
+ nsAutoCString scheme;
+ innermostURI->GetScheme(scheme);
+
+ // We only ever want to resolve to a local jar.
+ NS_ENSURE_TRUE(scheme.EqualsLiteral("file"), NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(resolvedURI));
+ if (!jarURI) {
+ // This substitution does not resolve to a jar: URL, so we just
+ // return the plain SubstitutionURL
+ nsCOMPtr<nsIURI> url = aURL;
+ url.forget(aResult);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIJARURI> result = new SubstitutingJARURI(aURL, jarURI);
+ result.forget(aResult);
+
+ return rv;
+}
+
+nsresult SubstitutingProtocolHandler::NewChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(uri, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_NewURI(getter_AddRefs(newURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't want to allow the inner protocol handler to modify the result
+ // principal URI since we want either |uri| or anything pre-set by upper
+ // layers to prevail.
+ nsCOMPtr<nsIURI> savedResultPrincipalURI;
+ rv =
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(savedResultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetResultPrincipalURI(savedResultPrincipalURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = (*result)->SetOriginalURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SubstituteChannel(uri, aLoadInfo, result);
+}
+
+nsresult SubstitutingProtocolHandler::AllowPort(int32_t port,
+ const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsISubstitutingProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::SetSubstitution(const nsACString& root,
+ nsIURI* baseURI) {
+ // Add-ons use this API but they should not be able to make anything
+ // content-accessible.
+ return SetSubstitutionWithFlags(root, baseURI, 0);
+}
+
+nsresult SubstitutingProtocolHandler::SetSubstitutionWithFlags(
+ const nsACString& origRoot, nsIURI* baseURI, uint32_t flags) {
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ if (!baseURI) {
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.Remove(root);
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // If baseURI isn't a same-scheme URI, we can set the substitution
+ // immediately.
+ nsAutoCString scheme;
+ nsresult rv = baseURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!scheme.Equals(mScheme)) {
+ if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") &&
+ !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app") &&
+ !scheme.EqualsLiteral("resource")) {
+ NS_WARNING("Refusing to create substituting URI to non-file:// target");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{baseURI, flags});
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // baseURI is a same-type substituting URI, let's resolve it first.
+ nsAutoCString newBase;
+ rv = ResolveURI(baseURI, newBase);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ rv =
+ mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{newBaseURI, flags});
+ }
+
+ return SendSubstitution(root, newBaseURI, flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitution(
+ const nsACString& origRoot, nsIURI** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ nsCOMPtr<nsIURI> baseURI = entry.baseURI;
+ baseURI.forget(result);
+ return NS_OK;
+ }
+ }
+
+ uint32_t flags;
+ return GetSubstitutionInternal(root, result, &flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitutionFlags(
+ const nsACString& root, uint32_t* flags) {
+#ifdef DEBUG
+ nsAutoCString lcRoot;
+ ToLowerCase(root, lcRoot);
+ MOZ_ASSERT(root.Equals(lcRoot),
+ "GetSubstitutionFlags should never receive mixed-case root name");
+#endif
+
+ *flags = 0;
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ *flags = entry.flags;
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
+}
+
+nsresult SubstitutingProtocolHandler::HasSubstitution(
+ const nsACString& origRoot, bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ *result = HasSubstitution(root);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveURI(nsIURI* uri,
+ nsACString& result) {
+ nsresult rv;
+
+ nsAutoCString host;
+ nsAutoCString path;
+ nsAutoCString pathname;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ if (!url) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->GetFilePath(pathname);
+ if (NS_FAILED(rv)) return rv;
+
+ if (ResolveSpecialCases(host, path, pathname, result)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ rv = GetSubstitution(host, getter_AddRefs(baseURI));
+ if (NS_FAILED(rv)) return rv;
+
+ // Unescape the path so we can perform some checks on it.
+ NS_UnescapeURL(pathname);
+ if (pathname.FindChar('\\') != -1) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Some code relies on an empty path resolving to a file rather than a
+ // directory.
+ NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'");
+ if (path.Length() == 1) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ // Make sure we always resolve the path as file-relative to our target URI.
+ // When the baseURI is a nsIFileURL, and the directory it points to doesn't
+ // exist, it doesn't end with a /. In that case, a file-relative resolution
+ // is going to pick something in the parent directory, so we resolve using
+ // an absolute path derived from the full path in that case.
+ nsCOMPtr<nsIFileURL> baseDir = do_QueryInterface(baseURI);
+ if (baseDir) {
+ nsAutoCString basePath;
+ rv = baseURI->GetFilePath(basePath);
+ if (NS_SUCCEEDED(rv) && !StringEndsWith(basePath, "/"_ns)) {
+ // Cf. the assertion above, path already starts with a /, so prefixing
+ // with a string that doesn't end with one will leave us wit the right
+ // amount of /.
+ path.Insert(basePath, 0);
+ } else {
+ // Allow to fall through below.
+ baseDir = nullptr;
+ }
+ }
+ if (!baseDir) {
+ path.Insert('.', 0);
+ }
+ rv = baseURI->Resolve(path, result);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ MOZ_LOG(gResLog, LogLevel::Debug,
+ ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.h b/netwerk/protocol/res/SubstitutingProtocolHandler.h
new file mode 100644
index 0000000000..6bdb27f38a
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,129 @@
+/* -*- 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 SubstitutingProtocolHandler_h___
+#define SubstitutingProtocolHandler_h___
+
+#include "nsISubstitutingProtocolHandler.h"
+
+#include "nsTHashMap.h"
+#include "nsStandardURL.h"
+#include "nsJARURI.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RWLock.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+//
+// Base class for resource://-like substitution protocols.
+//
+// If you add a new protocol, make sure to change nsChromeRegistryChrome
+// to properly invoke CollectSubstitutions at the right time.
+class SubstitutingProtocolHandler {
+ public:
+ explicit SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar = true);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SubstitutingProtocolHandler);
+ NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+ NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+ bool HasSubstitution(const nsACString& aRoot) const {
+ AutoReadLock lock(const_cast<RWLock&>(mSubstitutionsLock));
+ return mSubstitutions.Get(aRoot, nullptr);
+ }
+
+ nsresult NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult);
+
+ [[nodiscard]] nsresult CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings);
+
+ protected:
+ virtual ~SubstitutingProtocolHandler() = default;
+ void ConstructInternal();
+
+ [[nodiscard]] nsresult SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI, uint32_t aFlags);
+
+ nsresult GetSubstitutionFlags(const nsACString& root, uint32_t* flags);
+
+ // Override this in the subclass to try additional lookups after checking
+ // mSubstitutions.
+ [[nodiscard]] virtual nsresult GetSubstitutionInternal(
+ const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags) {
+ *aResult = nullptr;
+ *aFlags = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Override this in the subclass to check for special case when resolving URIs
+ // _before_ checking substitutions.
+ [[nodiscard]] virtual bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ return false;
+ }
+
+ // This method should only return true if GetSubstitutionInternal would
+ // return the RESOLVE_JAR_URI flag.
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) {
+ return false;
+ }
+
+ // Override this in the subclass to check for special case when opening
+ // channels.
+ [[nodiscard]] virtual nsresult SubstituteChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NS_OK;
+ }
+
+ nsIIOService* IOService() { return mIOService; }
+
+ private:
+ struct SubstitutionEntry {
+ nsCOMPtr<nsIURI> baseURI;
+ uint32_t flags = 0;
+ };
+
+ // Notifies all observers that a new substitution from |aRoot| to
+ // |aBaseURI| has been set/installed for this protocol handler.
+ void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
+
+ nsCString mScheme;
+
+ RWLock mSubstitutionsLock;
+ nsTHashMap<nsCStringHashKey, SubstitutionEntry> mSubstitutions
+ MOZ_GUARDED_BY(mSubstitutionsLock);
+ nsCOMPtr<nsIIOService> mIOService;
+
+ // Returns a SubstitutingJARURI if |aUrl| maps to a |jar:| URI,
+ // otherwise will return |aURL|
+ nsresult ResolveJARURI(nsIURL* aURL, nsIURI** aResult);
+
+ // In general, we expect the principal of a document loaded from a
+ // substituting URI to be a content principal for that URI (rather than
+ // a principal for whatever is underneath). However, this only works if
+ // the protocol handler for the underlying URI doesn't set an explicit
+ // owner (which chrome:// does, for example). So we want to require that
+ // substituting URIs only map to other URIs of the same type, or to
+ // file:// and jar:// URIs.
+ //
+ // Enforcing this for ye olde resource:// URIs could carry compat risks, so
+ // we just try to enforce it on new protocols going forward.
+ bool mEnforceFileOrJar;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingURL.h b/netwerk/protocol/res/SubstitutingURL.h
new file mode 100644
index 0000000000..b9012f1665
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingURL.h
@@ -0,0 +1,66 @@
+/* -*- 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 SubstitutingURL_h
+#define SubstitutingURL_h
+
+#include "nsStandardURL.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+class SubstitutingURL : public nsStandardURL {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ virtual nsStandardURL* StartClone() override;
+ [[nodiscard]] virtual nsresult EnsureFile() override;
+
+ private:
+ explicit SubstitutingURL() : nsStandardURL(true) {}
+ explicit SubstitutingURL(bool aSupportsFileURL) : nsStandardURL(true) {
+ MOZ_ASSERT(aSupportsFileURL);
+ }
+ virtual nsresult Clone(nsIURI** aURI) override {
+ return nsStandardURL::Clone(aURI);
+ }
+ virtual ~SubstitutingURL() = default;
+
+ public:
+ class Mutator : public TemplatedMutator<SubstitutingURL> {
+ NS_DECL_ISUPPORTS
+ public:
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ SubstitutingURL* Create() override { return new SubstitutingURL(); }
+ };
+
+ NS_IMETHOD Mutate(nsIURIMutator** aMutator) override {
+ RefPtr<SubstitutingURL::Mutator> mutator = new SubstitutingURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+ }
+
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ friend BaseURIMutator<SubstitutingURL>;
+ friend TemplatedMutator<SubstitutingURL>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingURL_h */
diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build
new file mode 100644
index 0000000000..63407eebd8
--- /dev/null
+++ b/netwerk/protocol/res/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIResProtocolHandler.idl",
+ "nsISubstitutingProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_res"
+
+EXPORTS.mozilla.net += [
+ "ExtensionProtocolHandler.h",
+ "PageThumbProtocolHandler.h",
+ "RemoteStreamGetter.h",
+ "SubstitutingJARURI.h",
+ "SubstitutingProtocolHandler.h",
+ "SubstitutingURL.h",
+]
+
+EXPORTS += [
+ "nsResProtocolHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "ExtensionProtocolHandler.cpp",
+ "nsResProtocolHandler.cpp",
+ "PageThumbProtocolHandler.cpp",
+ "RemoteStreamGetter.cpp",
+ "SubstitutingProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/xpcom/base",
+]
diff --git a/netwerk/protocol/res/nsIResProtocolHandler.idl b/netwerk/protocol/res/nsIResProtocolHandler.idl
new file mode 100644
index 0000000000..7046f2f1d4
--- /dev/null
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISubstitutingProtocolHandler.idl"
+
+/**
+ * Protocol handler interface for the resource:// protocol
+ */
+[scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
+interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
+{
+ boolean allowContentToAccess(in nsIURI url);
+};
diff --git a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
new file mode 100644
index 0000000000..cf2e0d42ab
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+
+/**
+ * Protocol handler superinterface for a protocol which performs substitutions
+ * from URIs of its scheme to URIs of another scheme.
+ */
+[scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
+interface nsISubstitutingProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * Content script may access files in this package.
+ */
+ const short ALLOW_CONTENT_ACCESS = 1 << 0;
+ /**
+ * This substitution exposes nsIJARURI instead of a nsIFileURL. By default
+ * NewURI will always return a nsIFileURL even when the URL is jar:
+ */
+ const short RESOLVE_JAR_URI = 1 << 1;
+
+ /**
+ * Sets the substitution for the root key:
+ * resource://root/path ==> baseURI.resolve(path)
+ *
+ * A null baseURI removes the specified substitution.
+ *
+ * The root key will be converted to lower-case to conform to
+ * case-insensitive URI hostname matching behavior.
+ */
+ [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
+
+ /**
+ * Same as setSubstitution, but with specific flags.
+ */
+ [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
+
+ /**
+ * Gets the substitution for the root key.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+ */
+ [must_use] nsIURI getSubstitution(in ACString root);
+
+ /**
+ * Returns TRUE if the substitution exists and FALSE otherwise.
+ */
+ [must_use] boolean hasSubstitution(in ACString root);
+
+ /**
+ * Utility function to resolve a substituted URI. A resolved URI is not
+ * guaranteed to reference a resource that exists (ie. opening a channel to
+ * the resolved URI may fail).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
+ */
+ [must_use] AUTF8String resolveURI(in nsIURI resURI);
+};
diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp
new file mode 100644
index 0000000000..208baedf2b
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Unused.h"
+
+#include "nsResProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+#include "mozilla/Omnijar.h"
+
+using mozilla::LogLevel;
+using mozilla::Unused;
+using mozilla::dom::ContentParent;
+
+#define kAPP "app"
+#define kGRE "gre"
+#define kAndroid "android"
+
+mozilla::StaticRefPtr<nsResProtocolHandler> nsResProtocolHandler::sSingleton;
+
+already_AddRefed<nsResProtocolHandler> nsResProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ RefPtr<nsResProtocolHandler> handler = new nsResProtocolHandler();
+ if (NS_WARN_IF(NS_FAILED(handler->Init()))) {
+ return nullptr;
+ }
+ sSingleton = handler;
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+nsresult nsResProtocolHandler::Init() {
+ nsresult rv;
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, mAppURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, mGREURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mozilla::Omnijar::GetURIString always returns a string ending with /,
+ // and we want to remove it.
+ mGREURI.Truncate(mGREURI.Length() - 1);
+ if (mAppURI.Length()) {
+ mAppURI.Truncate(mAppURI.Length() - 1);
+ } else {
+ mAppURI = mGREURI;
+ }
+
+#ifdef ANDROID
+ rv = GetApkURI(mApkURI);
+#endif
+
+ // XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir...
+ // but once I finish multiple chrome registration I'm not sure that it is
+ // needed
+
+ // XXX dveditz: resource://pchrome/ defeats profile directory salting
+ // if web content can load it. Tread carefully.
+
+ return rv;
+}
+
+#ifdef ANDROID
+nsresult nsResProtocolHandler::GetApkURI(nsACString& aResult) {
+ nsCString::const_iterator start, iter;
+ mGREURI.BeginReading(start);
+ mGREURI.EndReading(iter);
+ nsCString::const_iterator start_iter = start;
+
+ // This is like jar:jar:file://path/to/apk/base.apk!/path/to/omni.ja!/
+ bool found = FindInReadable("!/"_ns, start_iter, iter);
+ NS_ENSURE_TRUE(found, NS_ERROR_UNEXPECTED);
+
+ // like jar:jar:file://path/to/apk/base.apk!/
+ const nsDependentCSubstring& withoutPath = Substring(start, iter);
+ NS_ENSURE_TRUE(withoutPath.Length() >= 4, NS_ERROR_UNEXPECTED);
+
+ // Let's make sure we're removing what we expect to remove
+ NS_ENSURE_TRUE(Substring(withoutPath, 0, 4).EqualsLiteral("jar:"),
+ NS_ERROR_UNEXPECTED);
+
+ // like jar:file://path/to/apk/base.apk!/
+ aResult = ToNewCString(Substring(withoutPath, 4));
+
+ // Remove the trailing /
+ NS_ENSURE_TRUE(aResult.Length() >= 1, NS_ERROR_UNEXPECTED);
+ aResult.Truncate(aResult.Length() - 1);
+ return NS_OK;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsResProtocolHandler::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+
+NS_IMETHODIMP
+nsResProtocolHandler::AllowContentToAccess(nsIURI* aURI, bool* aResult) {
+ *aResult = false;
+
+ nsAutoCString host;
+ nsresult rv = aURI->GetAsciiHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetSubstitutionFlags(host, &flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = flags & nsISubstitutingProtocolHandler::ALLOW_CONTENT_ACCESS;
+ return NS_OK;
+}
+
+nsresult nsResProtocolHandler::GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) {
+ nsAutoCString uri;
+
+ if (!ResolveSpecialCases(aRoot, "/"_ns, "/"_ns, uri)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aRoot.Equals(kAndroid)) {
+ *aFlags = nsISubstitutingProtocolHandler::RESOLVE_JAR_URI;
+ } else {
+ *aFlags = 0; // No content access.
+ }
+ return NS_NewURI(aResult, uri);
+}
+
+bool nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ if (aHost.EqualsLiteral("") || aHost.EqualsLiteral(kAPP)) {
+ aResult.Assign(mAppURI);
+ } else if (aHost.Equals(kGRE)) {
+ aResult.Assign(mGREURI);
+#ifdef ANDROID
+ } else if (aHost.Equals(kAndroid)) {
+ aResult.Assign(mApkURI);
+#endif
+ } else {
+ return false;
+ }
+ aResult.Append(aPath);
+ return true;
+}
+
+nsresult nsResProtocolHandler::SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
+}
+
+nsresult nsResProtocolHandler::SetSubstitutionWithFlags(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitutionWithFlags(aRoot, aBaseURI,
+ aFlags);
+}
+
+nsresult nsResProtocolHandler::HasSubstitution(const nsACString& aRoot,
+ bool* aResult) {
+ if (aRoot.EqualsLiteral(kAPP) || aRoot.EqualsLiteral(kGRE)
+#ifdef ANDROID
+ || aRoot.EqualsLiteral(kAndroid)
+#endif
+ ) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ return mozilla::net::SubstitutingProtocolHandler::HasSubstitution(aRoot,
+ aResult);
+}
diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h
new file mode 100644
index 0000000000..50e790a53a
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsResProtocolHandler_h___
+#define nsResProtocolHandler_h___
+
+#include "mozilla/net/SubstitutingProtocolHandler.h"
+
+#include "nsIResProtocolHandler.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+
+struct SubstitutionMapping;
+class nsResProtocolHandler final
+ : public nsIResProtocolHandler,
+ public mozilla::net::SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRESPROTOCOLHANDLER
+
+ static already_AddRefed<nsResProtocolHandler> GetSingleton();
+
+ NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::net::SubstitutingProtocolHandler::)
+
+ nsResProtocolHandler()
+ : mozilla::net::SubstitutingProtocolHandler(
+ "resource",
+ /* aEnforceFileOrJar = */ false) {}
+
+ NS_IMETHOD SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) override;
+ NS_IMETHOD SetSubstitutionWithFlags(const nsACString& aRoot, nsIURI* aBaseURI,
+ uint32_t aFlags) override;
+ NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override;
+
+ NS_IMETHOD GetSubstitution(const nsACString& aRoot,
+ nsIURI** aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::GetSubstitution(aRoot,
+ aResult);
+ }
+
+ NS_IMETHOD ResolveURI(nsIURI* aResURI, nsACString& aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::ResolveURI(aResURI,
+ aResult);
+ }
+
+ protected:
+ [[nodiscard]] nsresult GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) override;
+ virtual ~nsResProtocolHandler() = default;
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) override {
+ return aRoot.EqualsLiteral("android");
+ }
+
+ private:
+ [[nodiscard]] nsresult Init();
+ static mozilla::StaticRefPtr<nsResProtocolHandler> sSingleton;
+
+ nsCString mAppURI;
+ nsCString mGREURI;
+#ifdef ANDROID
+ // Used for resource://android URIs
+ nsCString mApkURI;
+ nsresult GetApkURI(nsACString& aResult);
+#endif
+};
+
+#endif /* nsResProtocolHandler_h___ */