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
This commit is contained in:
Mike Conley 2020-04-06 17:46:52 +00:00
parent 3c7916f406
commit a98dabe6ec
17 changed files with 723 additions and 212 deletions

View File

@ -55,6 +55,8 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/NullPrincipal.h"
#include <stdint.h>
#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;
}
}
}

View File

@ -101,6 +101,7 @@
#include "nsAboutProtocolHandler.h"
#include "nsResProtocolHandler.h"
#include "mozilla/net/ExtensionProtocolHandler.h"
#include "mozilla/net/PageThumbProtocolHandler.h"
#include <limits>
#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<mozilla::net::PageThumbProtocolHandler> 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);

View File

@ -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'],

View File

@ -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<ContentParent*>(Manager())->GetRemoteType().EqualsLiteral(
PRIVILEGEDABOUT_REMOTE_TYPE)) {
return IPC_FAIL(this, "Wrong process type");
}
RefPtr<PageThumbProtocolHandler> 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<nsIInputStream> 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<nsIInputStream>& 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

View File

@ -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<LoadInfoArgs>& aLoadInfo);

View File

@ -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.

View File

@ -148,6 +148,9 @@ class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> {
// 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<Ok, nsresult> 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<Promise> {
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<Ok, nsresult> 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<nsIMIMEService> 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<nsIChannel> 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<nsIChannel> channel = NS_NewSimpleChannel(
aURI, aLoadinfo, aChannel,
[](nsIStreamListener* listener, nsIChannel* simpleChannel,

View File

@ -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<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

View File

@ -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> 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<PageThumbStreamGetter> {
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<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
nsIChannel* aChannel);
// Handle an input stream being returned from the parent
void OnStream(already_AddRefed<nsIInputStream> aStream);
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult);
MOZ_DECLARE_REFCOUNTED_TYPENAME(PageThumbStreamGetter)
private:
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
};
// Request an input stream from the parent.
Result<Ok, nsresult> PageThumbStreamGetter::GetAsync(
nsIStreamListener* aListener, nsIChannel* aChannel) {
MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mMainThreadEventTarget);
mListener = aListener;
mChannel = aChannel;
RefPtr<PageThumbStreamGetter> self = this;
// Request an input stream for this moz-page-thumb URI.
gNeckoChild->SendGetPageThumbStream(mURI)->Then(
mMainThreadEventTarget, __func__,
[self](const RefPtr<nsIInputStream>& 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<nsIInputStream> aStream) {
MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mListener);
MOZ_ASSERT(mMainThreadEventTarget);
nsCOMPtr<nsIInputStream> stream = std::move(aStream);
// We must keep an owning reference to the listener until we pass it on
// to AsyncRead.
nsCOMPtr<nsIStreamListener> 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<nsIInputStreamPump> 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>
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<PageThumbStreamPromise> 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<nsIIOService> ioService = do_GetIOService(&rv);
if (NS_FAILED(rv)) {
return PageThumbStreamPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsIURI> 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<nsIChannel> 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<MozPromiseHolder<PageThumbStreamPromise>>();
RefPtr<PageThumbStreamPromise> promise = promiseHolder->Ensure(__func__);
rv = NS_DispatchBackgroundTask(
NS_NewRunnableFunction(
"PageThumbProtocolHandler::NewStream",
[channel, holder = std::move(promiseHolder)]() {
nsresult rv;
nsCOMPtr<nsIFileChannel> fileChannel =
do_QueryInterface(channel, &rv);
if (NS_FAILED(rv)) {
holder->Reject(rv, __func__);
}
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;
}
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<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<PageThumbStreamGetter> 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<nsIPageThumbsStorageService> 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<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);
}
}
}
// static
void PageThumbProtocolHandler::NewSimpleChannel(
nsIURI* aURI, nsILoadInfo* aLoadinfo, PageThumbStreamGetter* aStreamGetter,
nsIChannel** aRetVal) {
nsCOMPtr<nsIChannel> 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

View File

@ -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<nsCOMPtr<nsIInputStream>, 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<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 PageThumbStreamPromise
* The PageThumbStreamPromise will resolve with an nsIInputStream on
* success, and reject with an nsresult on failure.
*/
RefPtr<PageThumbStreamPromise> 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<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 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<PageThumbProtocolHandler> 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___ */

View File

@ -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',
]

View File

@ -100,7 +100,7 @@ var PageThumbs = {
* The static host to use for thumbnail urls.
*/
get staticHost() {
return "thumbnail";
return "thumbnails";
},
/**

View File

@ -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<nsIFile> 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<nsIURI> 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<nsIChannel> channel;
nsCOMPtr<nsIIOService> 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<nsIPageThumbsStorageService> 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<nsIFile> 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;
}

View File

@ -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__ */

View File

@ -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'],

View File

@ -18,10 +18,6 @@ EXTRA_JS_MODULES += [
'PageThumbUtils.jsm',
]
UNIFIED_SOURCES += [
'PageThumbsProtocol.cpp'
]
XPIDL_SOURCES += [
'nsIPageThumbsStorageService.idl',
]

View File

@ -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"
);