From a98dabe6ecbfa9b4d659c5b86248077ab01f8878 Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Mon, 6 Apr 2020 17:46:52 +0000 Subject: [PATCH] Bug 1184701 - Make the moz-page-thumb protocol work in the privileged about content process. r=haik,valentin Differential Revision: https://phabricator.services.mozilla.com/D68281 --HG-- extra : moz-landing-system : lando --- caps/nsScriptSecurityManager.cpp | 11 + netwerk/base/nsNetUtil.cpp | 18 +- netwerk/build/components.conf | 8 + netwerk/ipc/NeckoParent.cpp | 47 ++ netwerk/ipc/NeckoParent.h | 4 + netwerk/ipc/PNecko.ipdl | 5 + .../protocol/res/ExtensionProtocolHandler.cpp | 32 +- .../protocol/res/ExtensionProtocolHandler.h | 14 + .../protocol/res/PageThumbProtocolHandler.cpp | 467 ++++++++++++++++++ .../protocol/res/PageThumbProtocolHandler.h | 123 +++++ netwerk/protocol/res/moz.build | 2 + toolkit/components/thumbnails/PageThumbs.jsm | 2 +- .../thumbnails/PageThumbsProtocol.cpp | 158 ------ .../thumbnails/PageThumbsProtocol.h | 31 -- toolkit/components/thumbnails/components.conf | 7 - toolkit/components/thumbnails/moz.build | 4 - .../test/test_thumbnails_interfaces.js | 2 +- 17 files changed, 723 insertions(+), 212 deletions(-) create mode 100644 netwerk/protocol/res/PageThumbProtocolHandler.cpp create mode 100644 netwerk/protocol/res/PageThumbProtocolHandler.h delete mode 100644 toolkit/components/thumbnails/PageThumbsProtocol.cpp delete mode 100644 toolkit/components/thumbnails/PageThumbsProtocol.h diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 840e9c64c25d..944ed6709dc7 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -55,6 +55,8 @@ #include "mozilla/dom/BindingUtils.h" #include "mozilla/NullPrincipal.h" #include +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" @@ -925,6 +927,15 @@ nsresult nsScriptSecurityManager::CheckLoadURIFlags( return NS_OK; } } + } else if (targetScheme.EqualsLiteral("moz-page-thumb")) { + if (XRE_IsParentProcess()) { + return NS_OK; + } + + auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType(); + if (remoteType.EqualsLiteral(PRIVILEGEDABOUT_REMOTE_TYPE)) { + return NS_OK; + } } } diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 6e6730a081cf..dab529344b15 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -101,6 +101,7 @@ #include "nsAboutProtocolHandler.h" #include "nsResProtocolHandler.h" #include "mozilla/net/ExtensionProtocolHandler.h" +#include "mozilla/net/PageThumbProtocolHandler.h" #include #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) @@ -1753,7 +1754,6 @@ nsresult NS_NewURI(nsIURI** aURI, const nsACString& aSpec, if (scheme.EqualsLiteral("moz-safe-about") || scheme.EqualsLiteral("page-icon") || scheme.EqualsLiteral("moz") || scheme.EqualsLiteral("moz-anno") || - scheme.EqualsLiteral("moz-page-thumb") || scheme.EqualsLiteral("moz-fonttable")) { return NS_MutateURI(new nsSimpleURI::Mutator()) .SetSpec(aSpec) @@ -1804,6 +1804,22 @@ nsresult NS_NewURI(nsIURI** aURI, const nsACString& aSpec, return handler->NewURI(aSpec, aCharset, aBaseURI, aURI); } + if (scheme.EqualsLiteral("moz-page-thumb")) { + // The moz-page-thumb service runs JS to resolve a URI to a + // storage location, so this should only ever run on the main + // thread. + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr handler = + mozilla::net::PageThumbProtocolHandler::GetSingleton(); + if (!handler) { + return NS_ERROR_NOT_AVAILABLE; + } + return handler->NewURI(aSpec, aCharset, aBaseURI, aURI); + } + if (scheme.EqualsLiteral("about")) { return nsAboutProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI); diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index 65a33cf500d5..34a96848b291 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -314,6 +314,14 @@ Classes = [ 'headers': ['mozilla/net/ExtensionProtocolHandler.h'], 'constructor': 'mozilla::net::ExtensionProtocolHandler::GetSingleton', }, + { + 'cid': '{450a2b55-620a-44b3-9f67-839b3b0c329c}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-page-thumb'], + 'singleton': True, + 'type': 'mozilla::net::PageThumbProtocolHandler', + 'headers': ['mozilla/net/PageThumbProtocolHandler.h'], + 'constructor': 'mozilla::net::PageThumbProtocolHandler::GetSingleton', + }, { 'cid': '{1423e739-782c-4081-b5d8-fe6fba68c0ef}', 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-safe-about'], diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index 1eecdae5b1fa..0bbfce141c35 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -10,6 +10,7 @@ #include "mozilla/ContentPrincipal.h" #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/net/ExtensionProtocolHandler.h" +#include "mozilla/net/PageThumbProtocolHandler.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/net/HttpChannelParent.h" #include "mozilla/net/CookieServiceParent.h" @@ -1001,5 +1002,51 @@ mozilla::ipc::IPCResult NeckoParent::RecvEnsureHSTSData( return IPC_OK(); } +mozilla::ipc::IPCResult NeckoParent::RecvGetPageThumbStream( + nsIURI* aURI, GetPageThumbStreamResolver&& aResolver) { + // Only the privileged about content process is allowed to access + // things over the moz-page-thumb protocol. Any other content process + // that tries to send this should have been blocked via the + // ScriptSecurityManager, but if somehow the process has been tricked into + // sending this message, we send IPC_FAIL in order to crash that + // likely-compromised content process. + if (!static_cast(Manager())->GetRemoteType().EqualsLiteral( + PRIVILEGEDABOUT_REMOTE_TYPE)) { + return IPC_FAIL(this, "Wrong process type"); + } + + RefPtr ph(PageThumbProtocolHandler::GetSingleton()); + MOZ_ASSERT(ph); + + // Ask the PageThumbProtocolHandler to give us a new input stream for + // this URI. The request comes from a PageThumbProtocolHandler in the + // child process, but is not guaranteed to be a valid moz-page-thumb URI, + // and not guaranteed to represent a resource that the child should be + // allowed to access. The PageThumbProtocolHandler is responsible for + // validating the request. + nsCOMPtr inputStream; + bool terminateSender = true; + auto inputStreamPromise = ph->NewStream(aURI, &terminateSender); + + if (terminateSender) { + return IPC_FAIL(this, "Malformed moz-page-thumb request"); + } + + inputStreamPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](const nsCOMPtr& aStream) { + aResolver(aStream); + }, + [aResolver](nsresult aRv) { + // If NewStream failed, we send back an invalid stream to the child so + // it can handle the error. MozPromise rejection is reserved for channel + // errors/disconnects. + Unused << NS_WARN_IF(NS_FAILED(aRv)); + aResolver(nullptr); + }); + + return IPC_OK(); +} + } // namespace net } // namespace mozilla diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h index 1d89ef1c5937..d9c0c2964cea 100644 --- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -234,6 +234,10 @@ class NeckoParent : public PNeckoParent { mozilla::ipc::IPCResult RecvGetExtensionFD(nsIURI* aURI, GetExtensionFDResolver&& aResolve); + /* Page thumbnails remote resource loading */ + mozilla::ipc::IPCResult RecvGetPageThumbStream( + nsIURI* aURI, GetPageThumbStreamResolver&& aResolve); + PClassifierDummyChannelParent* AllocPClassifierDummyChannelParent( nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, const Maybe& aLoadInfo); diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl index fb0eb42157b7..ed0c176f1b89 100644 --- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -156,6 +156,11 @@ parent: async EnsureHSTSData() returns (bool result); + /** + * Page thumbnails remote resource loading + */ + async GetPageThumbStream(nsIURI uri) returns (nsIInputStream stream); + child: /* * Bring up the http auth prompt for a nested remote mozbrowser. diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp index f6a05b9c7af9..44c02d6594fb 100644 --- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp +++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp @@ -148,6 +148,9 @@ class ExtensionStreamGetter : public RefCounted { // Handle file descriptor being returned from the parent void OnFD(const FileDescriptor& aFD); + static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, + nsresult aResult); + MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter) private: @@ -257,8 +260,10 @@ Result ExtensionStreamGetter::GetAsync( return Ok(); } -static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, - nsresult aResult) { +// static +void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener, + nsIChannel* aChannel, + nsresult aResult) { MOZ_ASSERT(aListener); MOZ_ASSERT(aChannel); @@ -464,7 +469,7 @@ void OpenWhenReady( nsIStreamListener* aListener) -> already_AddRefed { nsresult rv = aCallback(aListener, channel); if (NS_FAILED(rv)) { - CancelRequest(aListener, channel, rv); + ExtensionStreamGetter::CancelRequest(aListener, channel, rv); } return nullptr; }, @@ -840,7 +845,10 @@ Result ExtensionProtocolHandler::NewFD( } // Set the channel's content type using the provided URI's type -void SetContentType(nsIURI* aURI, nsIChannel* aChannel) { + +// static +void ExtensionProtocolHandler::SetContentType(nsIURI* aURI, + nsIChannel* aChannel) { nsresult rv; nsCOMPtr mime = do_GetService("@mozilla.org/mime;1", &rv); if (NS_SUCCEEDED(rv)) { @@ -853,9 +861,11 @@ 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) { + +// static +void ExtensionProtocolHandler::NewSimpleChannel( + nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter, + nsIChannel** aRetVal) { nsCOMPtr channel = NS_NewSimpleChannel( aURI, aLoadinfo, aStreamGetter, [](nsIStreamListener* listener, nsIChannel* simpleChannel, @@ -869,8 +879,12 @@ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, } // Gets a SimpleChannel that wraps the provided channel -static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, - nsIChannel* aChannel, nsIChannel** aRetVal) { + +// static +void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI, + nsILoadInfo* aLoadinfo, + nsIChannel* aChannel, + nsIChannel** aRetVal) { nsCOMPtr channel = NS_NewSimpleChannel( aURI, aLoadinfo, aChannel, [](nsIStreamListener* listener, nsIChannel* simpleChannel, diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.h b/netwerk/protocol/res/ExtensionProtocolHandler.h index 371fec3dd6ee..0138c67f4a41 100644 --- a/netwerk/protocol/res/ExtensionProtocolHandler.h +++ b/netwerk/protocol/res/ExtensionProtocolHandler.h @@ -14,6 +14,8 @@ namespace mozilla { namespace net { +class ExtensionStreamGetter; + class ExtensionProtocolHandler final : public nsISubstitutingProtocolHandler, public nsIProtocolHandlerWithDynamicFlags, @@ -156,6 +158,18 @@ class ExtensionProtocolHandler final Result 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 diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.cpp b/netwerk/protocol/res/PageThumbProtocolHandler.cpp new file mode 100644 index 000000000000..b3e6c3004888 --- /dev/null +++ b/netwerk/protocol/res/PageThumbProtocolHandler.cpp @@ -0,0 +1,467 @@ +/* -*- 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 "prio.h" +#include "SimpleChannel.h" + +#define PAGE_THUMB_HOST "thumbnails" +#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::sSingleton; + +/** + * Helper class used with SimpleChannel to asynchronously obtain an input + * stream from the parent for a remote moz-page-thumb load from the child. + */ +class PageThumbStreamGetter : public RefCounted { + public: + PageThumbStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo) + : mURI(aURI), mLoadInfo(aLoadInfo) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(aLoadInfo); + + SetupEventTarget(); + } + + ~PageThumbStreamGetter() = default; + + void SetupEventTarget() { + mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo( + mLoadInfo, TaskCategory::Other); + if (!mMainThreadEventTarget) { + mMainThreadEventTarget = GetMainThreadSerialEventTarget(); + } + } + + // Get an input stream from the parent asynchronously. + Result GetAsync(nsIStreamListener* aListener, + nsIChannel* aChannel); + + // Handle an input stream being returned from the parent + void OnStream(already_AddRefed aStream); + + static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, + nsresult aResult); + + MOZ_DECLARE_REFCOUNTED_TYPENAME(PageThumbStreamGetter) + + private: + nsCOMPtr mURI; + nsCOMPtr mLoadInfo; + nsCOMPtr mListener; + nsCOMPtr mChannel; + nsCOMPtr mMainThreadEventTarget; +}; + +// Request an input stream from the parent. +Result PageThumbStreamGetter::GetAsync( + nsIStreamListener* aListener, nsIChannel* aChannel) { + MOZ_ASSERT(IsNeckoChild()); + MOZ_ASSERT(mMainThreadEventTarget); + + mListener = aListener; + mChannel = aChannel; + + RefPtr self = this; + + // Request an input stream for this moz-page-thumb URI. + gNeckoChild->SendGetPageThumbStream(mURI)->Then( + mMainThreadEventTarget, __func__, + [self](const RefPtr& stream) { + self->OnStream(do_AddRef(stream)); + }, + [self](const mozilla::ipc::ResponseRejectReason) { + self->OnStream(nullptr); + }); + return Ok(); +} + +// static +void PageThumbStreamGetter::CancelRequest(nsIStreamListener* aListener, + nsIChannel* aChannel, + nsresult aResult) { + MOZ_ASSERT(aListener); + MOZ_ASSERT(aChannel); + + aListener->OnStartRequest(aChannel); + aListener->OnStopRequest(aChannel, aResult); + aChannel->Cancel(NS_BINDING_ABORTED); +} + +// Handle an input stream sent from the parent. +void PageThumbStreamGetter::OnStream(already_AddRefed aStream) { + MOZ_ASSERT(IsNeckoChild()); + MOZ_ASSERT(mListener); + MOZ_ASSERT(mMainThreadEventTarget); + + nsCOMPtr stream = std::move(aStream); + + // We must keep an owning reference to the listener until we pass it on + // to AsyncRead. + nsCOMPtr listener = mListener.forget(); + + MOZ_ASSERT(mChannel); + + if (!stream) { + // The parent didn't send us back a stream. + CancelRequest(listener, mChannel, NS_ERROR_FILE_ACCESS_DENIED); + return; + } + + nsCOMPtr pump; + nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, + 0, false, mMainThreadEventTarget); + if (NS_FAILED(rv)) { + CancelRequest(listener, mChannel, rv); + return; + } + + rv = pump->AsyncRead(listener, nullptr); + if (NS_FAILED(rv)) { + CancelRequest(listener, mChannel, rv); + } +} + +NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler, + nsISubstitutingProtocolHandler, nsIProtocolHandler, + nsIProtocolHandlerWithDynamicFlags, + nsISupportsWeakReference) +NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler) +NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler) + +already_AddRefed +PageThumbProtocolHandler::GetSingleton() { + if (!sSingleton) { + sSingleton = new PageThumbProtocolHandler(); + ClearOnShutdown(&sSingleton); + } + + return do_AddRef(sSingleton); +} + +PageThumbProtocolHandler::PageThumbProtocolHandler() + : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {} + +nsresult PageThumbProtocolHandler::GetFlagsForURI(nsIURI* aURI, + uint32_t* aFlags) { + // 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. + *aFlags = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE | + URI_NORELATIVE | URI_NOAUTH; + + return NS_OK; +} + +RefPtr PageThumbProtocolHandler::NewStream( + nsIURI* aChildURI, bool* aTerminateSender) { + MOZ_ASSERT(!IsNeckoChild()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!aChildURI || !aTerminateSender) { + return PageThumbStreamPromise::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 PageThumbStreamPromise::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)) { + return PageThumbStreamPromise::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 PageThumbStreamPromise::CreateAndReject(rv, __func__); + } + + nsAutoCString resolvedScheme; + rv = net_ExtractURLScheme(resolvedSpec, resolvedScheme); + if (NS_FAILED(rv) || !resolvedScheme.EqualsLiteral("file")) { + return PageThumbStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + } + + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_FAILED(rv)) { + return PageThumbStreamPromise::CreateAndReject(rv, __func__); + } + + nsCOMPtr resolvedURI; + rv = ioService->NewURI(resolvedSpec, nullptr, nullptr, + getter_AddRefs(resolvedURI)); + if (NS_FAILED(rv)) { + return PageThumbStreamPromise::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 channel; + rv = NS_NewChannel(getter_AddRefs(channel), resolvedURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + if (NS_FAILED(rv)) { + return PageThumbStreamPromise::CreateAndReject(rv, __func__); + } + + auto promiseHolder = MakeUnique>(); + RefPtr promise = promiseHolder->Ensure(__func__); + + rv = NS_DispatchBackgroundTask( + NS_NewRunnableFunction( + "PageThumbProtocolHandler::NewStream", + [channel, holder = std::move(promiseHolder)]() { + nsresult rv; + + nsCOMPtr fileChannel = + do_QueryInterface(channel, &rv); + if (NS_FAILED(rv)) { + holder->Reject(rv, __func__); + } + + nsCOMPtr requestedFile; + rv = fileChannel->GetFile(getter_AddRefs(requestedFile)); + if (NS_FAILED(rv)) { + holder->Reject(rv, __func__); + return; + } + + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), + requestedFile, PR_RDONLY, -1); + if (NS_FAILED(rv)) { + holder->Reject(rv, __func__); + return; + } + + holder->Resolve(inputStream, __func__); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + if (NS_FAILED(rv)) { + return PageThumbStreamPromise::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)) { + // 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, 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 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 streamGetter = + new PageThumbStreamGetter(aURI, aLoadInfo); + + NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal); + return Ok(); +} + +nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath, + 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; + } + + nsresult rv; + + nsCOMPtr pageThumbsStorage = + do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Extract URL from query string. + nsAutoString url; + bool found = dom::URLParams::Extract(Substring(aPath, queryIndex + 1), + NS_LITERAL_STRING("url"), url); + if (!found || url.IsVoid()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Use PageThumbsStorageService to get the local file path of the screenshot + // for the given URL. + rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// static +void PageThumbProtocolHandler::SetContentType(nsIURI* aURI, + nsIChannel* aChannel) { + nsresult rv; + nsCOMPtr 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); + } + } +} + +// static +void PageThumbProtocolHandler::NewSimpleChannel( + nsIURI* aURI, nsILoadInfo* aLoadinfo, PageThumbStreamGetter* aStreamGetter, + nsIChannel** aRetVal) { + nsCOMPtr channel = NS_NewSimpleChannel( + aURI, aLoadinfo, aStreamGetter, + [](nsIStreamListener* listener, nsIChannel* simpleChannel, + PageThumbStreamGetter* getter) -> RequestOrReason { + MOZ_TRY(getter->GetAsync(listener, simpleChannel)); + return RequestOrReason(nullptr); + }); + + SetContentType(aURI, channel); + 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 000000000000..a9ffa0a0ccff --- /dev/null +++ b/netwerk/protocol/res/PageThumbProtocolHandler.h @@ -0,0 +1,123 @@ +/* -*- 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 "SubstitutingProtocolHandler.h" + +namespace mozilla { +namespace net { + +using PageThumbStreamPromise = + mozilla::MozPromise, nsresult, false>; + +class PageThumbStreamGetter; + +class PageThumbProtocolHandler 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 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 PageThumbStreamPromise + * The PageThumbStreamPromise will resolve with an nsIInputStream on + * success, and reject with an nsresult on failure. + */ + RefPtr NewStream(nsIURI* aChildURI, + bool* aTerminateSender); + + protected: + ~PageThumbProtocolHandler() = default; + + private: + explicit PageThumbProtocolHandler(); + + MOZ_MUST_USE 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. + */ + virtual MOZ_MUST_USE 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 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 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, nsString& aThumbnailPath); + + // To allow parent IPDL actors to invoke methods on this handler when + // handling moz-page-thumb requests from the child. + static StaticRefPtr sSingleton; + + // 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 channel. + static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, + PageThumbStreamGetter* aStreamGetter, + nsIChannel** aRetVal); +}; + +} // namespace net +} // namespace mozilla + +#endif /* PageThumbProtocolHandler_h___ */ diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build index 913100a352b3..db449ec90905 100644 --- a/netwerk/protocol/res/moz.build +++ b/netwerk/protocol/res/moz.build @@ -13,6 +13,7 @@ XPIDL_MODULE = 'necko_res' EXPORTS.mozilla.net += [ 'ExtensionProtocolHandler.h', + 'PageThumbProtocolHandler.h', 'SubstitutingJARURI.h', 'SubstitutingProtocolHandler.h', 'SubstitutingURL.h', @@ -25,6 +26,7 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'ExtensionProtocolHandler.cpp', 'nsResProtocolHandler.cpp', + 'PageThumbProtocolHandler.cpp', 'SubstitutingProtocolHandler.cpp', ] diff --git a/toolkit/components/thumbnails/PageThumbs.jsm b/toolkit/components/thumbnails/PageThumbs.jsm index 8c4175b55f12..5a6ec41691d9 100644 --- a/toolkit/components/thumbnails/PageThumbs.jsm +++ b/toolkit/components/thumbnails/PageThumbs.jsm @@ -100,7 +100,7 @@ var PageThumbs = { * The static host to use for thumbnail urls. */ get staticHost() { - return "thumbnail"; + return "thumbnails"; }, /** diff --git a/toolkit/components/thumbnails/PageThumbsProtocol.cpp b/toolkit/components/thumbnails/PageThumbsProtocol.cpp deleted file mode 100644 index 3fc307390418..000000000000 --- a/toolkit/components/thumbnails/PageThumbsProtocol.cpp +++ /dev/null @@ -1,158 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; 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/. */ - -/** - * Implementation of moz-page-thumb protocol. This accesses and displays - * screenshots for URLs that are stored in the profile directory. - */ - -#include "nsIPageThumbsStorageService.h" -#include "PageThumbsProtocol.h" -#include "nsIURI.h" -#include "nsIFile.h" -#include "nsIChannel.h" -#include "nsComponentManagerUtils.h" -#include "nsNetUtil.h" -#include "mozilla/dom/URLSearchParams.h" -#include "nsStandardURL.h" - -using mozilla::dom::URLParams; -using mozilla::net::nsStandardURL; - -NS_IMPL_ISUPPORTS(PageThumbsProtocol, nsIProtocolHandler); - -// PageThumbsProtocol::GetScheme - -NS_IMETHODIMP -PageThumbsProtocol::GetScheme(nsACString& aScheme) { - aScheme.AssignLiteral("moz-page-thumb"); - return NS_OK; -} - -// PageThumbsProtocol::GetDefaultPort - -NS_IMETHODIMP -PageThumbsProtocol::GetDefaultPort(int32_t* aDefaultPort) { - *aDefaultPort = -1; - return NS_OK; -} - -// PageThumbsProtocol::GetProtocolFlags - -NS_IMETHODIMP -PageThumbsProtocol::GetProtocolFlags(uint32_t* aProtocolFlags) { - *aProtocolFlags = (URI_DANGEROUS_TO_LOAD | URI_IS_LOCAL_RESOURCE | - URI_NORELATIVE | URI_NOAUTH); - return NS_OK; -} - -// PageThumbsProtocol::NewChannel - -NS_IMETHODIMP -PageThumbsProtocol::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, - nsIChannel** _retval) { - // Get the file path for the URL - nsCOMPtr filePath; - nsresult rv = GetFilePathForURL(aURI, getter_AddRefs(filePath)); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - // Get a file URI from the local file path - nsCOMPtr fileURI; - rv = NS_NewFileURI(getter_AddRefs(fileURI), filePath); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - // Create a new channel with the file URI created - nsCOMPtr channel; - nsCOMPtr ios = do_GetIOService(); - rv = ios->NewChannelFromURIWithLoadInfo(fileURI, aLoadInfo, - getter_AddRefs(channel)); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - channel->SetOriginalURI(aURI); - channel.forget(_retval); - return NS_OK; -} - -// PageThumbsProtocol::AllowPort - -NS_IMETHODIMP -PageThumbsProtocol::AllowPort(int32_t aPort, const char* aScheme, - bool* _retval) { - *_retval = false; - return NS_OK; -} - -// PageThumbsProtocol::ParseProtocolURL -// -// Extracts the URL from the query parameter. The URI is passed in in the -// form: 'moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F'. - -nsresult PageThumbsProtocol::ParseProtocolURL(nsIURI* aURI, - nsString& aParsedURL) { - nsAutoCString spec; - aURI->GetSpec(spec); - - // Check that we have the correct host - nsAutoCString host; - host = Substring(spec, spec.FindChar(':') + 3, 9); - if (!host.EqualsLiteral("thumbnail")) { - return NS_ERROR_NOT_AVAILABLE; - } - - // Get the path out of the URI - nsAutoCString path; - nsresult rv = aURI->GetPathQueryRef(path); - - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - // Since this is a protocol URI and it doesn't parse nicely, we split on where - // the start of the query is and parse it from there - int32_t queryBegins = path.FindChar('?'); - if (queryBegins <= 0) { - return NS_ERROR_MALFORMED_URI; - } - - URLParams::Extract(Substring(path, queryBegins + 1), NS_LITERAL_STRING("url"), - aParsedURL); - - // If there's no URL as part of the query params, there will be no thumbnail - if (aParsedURL.IsVoid()) { - return NS_ERROR_NOT_AVAILABLE; - } - - return NS_OK; -} - -// PageThumbsProtocol::GetFilePathForURL -// -// Returns the thumbnail's file path for a given URL - -nsresult PageThumbsProtocol::GetFilePathForURL(nsIURI* aURI, - nsIFile** _retval) { - nsresult rv; - - // Use PageThumbsStorageService to get the local file path of the screenshot - // for the given URL - nsAutoString filePathForURL; - nsCOMPtr pageThumbsStorage = - do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - // Parse the protocol URL to extract the thumbnail's URL - nsAutoString parsedURL; - rv = ParseProtocolURL(aURI, parsedURL); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - rv = pageThumbsStorage->GetFilePathForURL(parsedURL, filePathForURL); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - // Find the local file containing the screenshot - nsCOMPtr filePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); - rv = filePath->InitWithPath(filePathForURL); - if (NS_WARN_IF(NS_FAILED(rv))) return rv; - - filePath.forget(_retval); - return NS_OK; -} diff --git a/toolkit/components/thumbnails/PageThumbsProtocol.h b/toolkit/components/thumbnails/PageThumbsProtocol.h deleted file mode 100644 index 3a87ec1a38d8..000000000000 --- a/toolkit/components/thumbnails/PageThumbsProtocol.h +++ /dev/null @@ -1,31 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; 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 PageThumbsProtocol_h__ -#define PageThumbsProtocol_h__ - -#include "nsIProtocolHandler.h" -#include "nsString.h" - -// {5a4ae9b5-f475-48ae-9dce-0b4c1d347884} -#define PAGETHUMBSPROTOCOL_CID \ - { \ - 0x5a4ae9b5, 0xf475, 0x48ae, { \ - 0x9d, 0xce, 0x0b, 0x4c, 0x1d, 0x34, 0x78, 0x84 \ - } \ - } - -class PageThumbsProtocol final : public nsIProtocolHandler { - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIPROTOCOLHANDLER - - private: - ~PageThumbsProtocol() = default; - nsresult GetFilePathForURL(nsIURI* aURI, nsIFile** _retval); - nsresult ParseProtocolURL(nsIURI* aURI, nsString& aParsedURL); -}; - -#endif /* PageThumbsProtocol_h__ */ diff --git a/toolkit/components/thumbnails/components.conf b/toolkit/components/thumbnails/components.conf index 615216adbf47..08c4b34170b4 100644 --- a/toolkit/components/thumbnails/components.conf +++ b/toolkit/components/thumbnails/components.conf @@ -5,13 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. Classes = [ - { - 'cid': '{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}', - 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-page-thumb'], - 'type': 'PageThumbsProtocol', - 'headers': ['/toolkit/components/thumbnails/PageThumbsProtocol.h'], - }, - { 'cid': '{97943eec-0e48-49ef-b7b7-cf4aa0109bb6}', 'contract_ids': ['@mozilla.org/thumbnails/pagethumbs-service;1'], diff --git a/toolkit/components/thumbnails/moz.build b/toolkit/components/thumbnails/moz.build index 8275dbd7e87f..43956d090d79 100644 --- a/toolkit/components/thumbnails/moz.build +++ b/toolkit/components/thumbnails/moz.build @@ -18,10 +18,6 @@ EXTRA_JS_MODULES += [ 'PageThumbUtils.jsm', ] -UNIFIED_SOURCES += [ - 'PageThumbsProtocol.cpp' -] - XPIDL_SOURCES += [ 'nsIPageThumbsStorageService.idl', ] diff --git a/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js index 0b47f8f05fc0..f7e585070237 100644 --- a/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js +++ b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js @@ -37,7 +37,7 @@ function run_test() { ); Assert.throws( () => handler.newChannel(badQuery, dummyLoadInfo), - /NS_ERROR_MALFORMED_URI/i, + /NS_ERROR_NOT_AVAILABLE/i, "moz-page-thumb object with malformed query parameters must not resolve to a file path" );