forked from mirrors/gecko-dev
CLOSED TREE Backed out changeset 210012222277 (bug 1826761) Backed out changeset e364bb149efa (bug 1826760) Backed out changeset e456e2f9966c (bug 1826759) Backed out changeset 2b6ff545f4a3 (bug 1826758) Backed out changeset 95fe1de8ba00 (bug 1826757) Backed out changeset f8af52d7f2a1 (bug 1826756) Backed out changeset 2646e773f098 (bug 1826754) Backed out changeset 58d5d74b1835 (bug 1826753) Backed out changeset 8567e6595acc (bug 1826752)
394 lines
13 KiB
C++
394 lines
13 KiB
C++
/* 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 "PageIconProtocolHandler.h"
|
|
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/Components.h"
|
|
#include "nsFaviconService.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsIFaviconService.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsIPipe.h"
|
|
#include "nsIRequestObserver.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "nsNetUtil.h"
|
|
#include "SimpleChannel.h"
|
|
|
|
#define PAGE_ICON_SCHEME "page-icon"
|
|
|
|
using mozilla::net::IsNeckoChild;
|
|
using mozilla::net::NeckoChild;
|
|
using mozilla::net::RemoteStreamGetter;
|
|
using mozilla::net::RemoteStreamInfo;
|
|
|
|
namespace mozilla::places {
|
|
|
|
struct FaviconMetadata {
|
|
nsCOMPtr<nsIInputStream> mStream;
|
|
nsCString mContentType;
|
|
int64_t mContentLength = 0;
|
|
};
|
|
|
|
StaticRefPtr<PageIconProtocolHandler> PageIconProtocolHandler::sSingleton;
|
|
|
|
namespace {
|
|
|
|
class DefaultFaviconObserver final : public nsIRequestObserver {
|
|
public:
|
|
explicit DefaultFaviconObserver(nsIOutputStream* aOutputStream)
|
|
: mOutputStream(aOutputStream) {
|
|
MOZ_ASSERT(aOutputStream);
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
private:
|
|
~DefaultFaviconObserver() = default;
|
|
|
|
nsCOMPtr<nsIOutputStream> mOutputStream;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(DefaultFaviconObserver, nsIRequestObserver);
|
|
|
|
NS_IMETHODIMP DefaultFaviconObserver::OnStartRequest(nsIRequest*) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP DefaultFaviconObserver::OnStopRequest(nsIRequest*, nsresult) {
|
|
// We must close the outputStream regardless.
|
|
mOutputStream->Close();
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static nsresult MakeDefaultFaviconChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
|
|
nsIChannel** aOutChannel) {
|
|
nsCOMPtr<nsIIOService> ios = mozilla::components::IO::Service();
|
|
nsCOMPtr<nsIChannel> chan;
|
|
nsCOMPtr<nsIURI> defaultFaviconURI;
|
|
|
|
auto* faviconService = nsFaviconService::GetFaviconService();
|
|
if (MOZ_UNLIKELY(!faviconService)) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult rv =
|
|
faviconService->GetDefaultFavicon(getter_AddRefs(defaultFaviconURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = ios->NewChannelFromURIWithLoadInfo(defaultFaviconURI, aLoadInfo,
|
|
getter_AddRefs(chan));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
chan->SetOriginalURI(aURI);
|
|
chan->SetContentType(nsLiteralCString(FAVICON_DEFAULT_MIMETYPE));
|
|
chan.forget(aOutChannel);
|
|
return NS_OK;
|
|
}
|
|
|
|
static nsresult StreamDefaultFavicon(nsIURI* aURI, nsILoadInfo* aLoadInfo,
|
|
nsIOutputStream* aOutputStream) {
|
|
auto closeStreamOnError =
|
|
mozilla::MakeScopeExit([&] { aOutputStream->Close(); });
|
|
|
|
auto observer = MakeRefPtr<DefaultFaviconObserver>(aOutputStream);
|
|
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(listener),
|
|
aOutputStream, observer);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIChannel> defaultIconChannel;
|
|
rv = MakeDefaultFaviconChannel(aURI, aLoadInfo,
|
|
getter_AddRefs(defaultIconChannel));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = defaultIconChannel->AsyncOpen(listener);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
closeStreamOnError.release();
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
class FaviconDataCallback final : public nsIFaviconDataCallback {
|
|
public:
|
|
FaviconDataCallback(nsIURI* aURI, nsILoadInfo* aLoadInfo)
|
|
: mURI(aURI), mLoadInfo(aLoadInfo) {
|
|
MOZ_ASSERT(aURI);
|
|
MOZ_ASSERT(aLoadInfo);
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIFAVICONDATACALLBACK
|
|
|
|
RefPtr<FaviconMetadataPromise> Promise() {
|
|
return mPromiseHolder.Ensure(__func__);
|
|
}
|
|
|
|
private:
|
|
~FaviconDataCallback();
|
|
nsCOMPtr<nsIURI> mURI;
|
|
MozPromiseHolder<FaviconMetadataPromise> mPromiseHolder;
|
|
nsCOMPtr<nsILoadInfo> mLoadInfo;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(FaviconDataCallback, nsIFaviconDataCallback);
|
|
|
|
FaviconDataCallback::~FaviconDataCallback() {
|
|
mPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
NS_IMETHODIMP FaviconDataCallback::OnComplete(nsIURI* aURI, uint32_t aDataLen,
|
|
const uint8_t* aData,
|
|
const nsACString& aMimeType,
|
|
uint16_t aWidth) {
|
|
if (!aDataLen) {
|
|
mPromiseHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
nsresult rv =
|
|
NS_NewByteInputStream(getter_AddRefs(inputStream),
|
|
AsChars(Span{aData, aDataLen}), NS_ASSIGNMENT_COPY);
|
|
if (NS_FAILED(rv)) {
|
|
mPromiseHolder.Reject(rv, __func__);
|
|
return rv;
|
|
}
|
|
|
|
FaviconMetadata metadata;
|
|
metadata.mStream = inputStream;
|
|
metadata.mContentType = aMimeType;
|
|
metadata.mContentLength = aDataLen;
|
|
mPromiseHolder.Resolve(std::move(metadata), __func__);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
NS_IMPL_ISUPPORTS(PageIconProtocolHandler, nsIProtocolHandler,
|
|
nsISupportsWeakReference);
|
|
|
|
NS_IMETHODIMP PageIconProtocolHandler::GetScheme(nsACString& aScheme) {
|
|
aScheme.AssignLiteral(PAGE_ICON_SCHEME);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP PageIconProtocolHandler::AllowPort(int32_t, const char*,
|
|
bool* aAllow) {
|
|
*aAllow = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP PageIconProtocolHandler::NewChannel(nsIURI* aURI,
|
|
nsILoadInfo* aLoadInfo,
|
|
nsIChannel** aOutChannel) {
|
|
// Load the URI remotely if accessed from a child.
|
|
if (IsNeckoChild()) {
|
|
MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aOutChannel));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = NewChannelInternal(aURI, aLoadInfo, aOutChannel);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return rv;
|
|
}
|
|
return MakeDefaultFaviconChannel(aURI, aLoadInfo, aOutChannel);
|
|
}
|
|
|
|
Result<Ok, nsresult> PageIconProtocolHandler::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);
|
|
|
|
RefPtr<RemoteStreamGetter> streamGetter =
|
|
new RemoteStreamGetter(aURI, aLoadInfo);
|
|
|
|
NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
|
|
return Ok();
|
|
}
|
|
|
|
nsresult PageIconProtocolHandler::NewChannelInternal(nsIURI* aURI,
|
|
nsILoadInfo* aLoadInfo,
|
|
nsIChannel** aOutChannel) {
|
|
// Create a pipe that will give us an output stream that we can use once
|
|
// we got all the favicon data.
|
|
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
|
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
|
GetStreams(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut));
|
|
|
|
// Create our channel.
|
|
nsCOMPtr<nsIChannel> channel;
|
|
{
|
|
// We override the channel's loadinfo below anyway, so using a null
|
|
// principal here is alright.
|
|
nsCOMPtr<nsIPrincipal> loadingPrincipal =
|
|
NullPrincipal::CreateWithoutOriginAttributes();
|
|
nsresult rv = NS_NewInputStreamChannel(
|
|
getter_AddRefs(channel), aURI, pipeIn.forget(), loadingPrincipal,
|
|
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
|
|
nsIContentPolicy::TYPE_INTERNAL_IMAGE);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsresult rv = channel->SetLoadInfo(aLoadInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
GetFaviconData(aURI, aLoadInfo)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[pipeOut, channel](const FaviconMetadata& aMetadata) {
|
|
channel->SetContentType(aMetadata.mContentType);
|
|
channel->SetContentLength(aMetadata.mContentLength);
|
|
|
|
nsresult rv;
|
|
const nsCOMPtr<nsIEventTarget> target =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
channel->CancelWithReason(NS_BINDING_ABORTED,
|
|
"GetFaviconData failed"_ns);
|
|
return;
|
|
}
|
|
|
|
NS_AsyncCopy(aMetadata.mStream, pipeOut, target);
|
|
},
|
|
[uri = nsCOMPtr{aURI}, loadInfo = nsCOMPtr{aLoadInfo}, pipeOut,
|
|
channel](nsresult aRv) {
|
|
// There are a few reasons why this might fail. For example, one
|
|
// reason is that the URI might not actually be properly parsable.
|
|
// In that case, we'll try one last time to stream the default
|
|
// favicon before giving up.
|
|
channel->SetContentType(nsLiteralCString(FAVICON_DEFAULT_MIMETYPE));
|
|
channel->SetContentLength(-1);
|
|
Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut);
|
|
});
|
|
channel.forget(aOutChannel);
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<FaviconMetadataPromise> PageIconProtocolHandler::GetFaviconData(
|
|
nsIURI* aPageIconURI, nsILoadInfo* aLoadInfo) {
|
|
auto* faviconService = nsFaviconService::GetFaviconService();
|
|
if (MOZ_UNLIKELY(!faviconService)) {
|
|
return FaviconMetadataPromise::CreateAndReject(NS_ERROR_UNEXPECTED,
|
|
__func__);
|
|
}
|
|
|
|
uint16_t preferredSize = 0;
|
|
faviconService->PreferredSizeFromURI(aPageIconURI, &preferredSize);
|
|
|
|
nsCOMPtr<nsIURI> pageURI;
|
|
nsresult rv;
|
|
{
|
|
// NOTE: We don't need to strip #size= fragments because
|
|
// GetFaviconDataForPage strips them when doing the database lookup.
|
|
nsAutoCString pageQuery;
|
|
aPageIconURI->GetPathQueryRef(pageQuery);
|
|
rv = NS_NewURI(getter_AddRefs(pageURI), pageQuery);
|
|
if (NS_FAILED(rv)) {
|
|
return FaviconMetadataPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
}
|
|
|
|
auto faviconCallback =
|
|
MakeRefPtr<FaviconDataCallback>(aPageIconURI, aLoadInfo);
|
|
rv = faviconService->GetFaviconDataForPage(pageURI, faviconCallback,
|
|
preferredSize);
|
|
if (NS_FAILED(rv)) {
|
|
return FaviconMetadataPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
return faviconCallback->Promise();
|
|
}
|
|
|
|
RefPtr<RemoteStreamPromise> PageIconProtocolHandler::NewStream(
|
|
nsIURI* aChildURI, nsILoadInfo* aLoadInfo, bool* aTerminateSender) {
|
|
MOZ_ASSERT(!IsNeckoChild());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aChildURI || !aLoadInfo || !aTerminateSender) {
|
|
return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
|
|
}
|
|
|
|
*aTerminateSender = true;
|
|
|
|
// We should never receive a URI that isn't for a page-icon because
|
|
// these requests ordinarily come from the child's PageIconProtocolHandler.
|
|
// Ensure this request is for a page-icon URI. A compromised child process
|
|
// could send us any URI.
|
|
bool isPageIconScheme = false;
|
|
if (NS_FAILED(aChildURI->SchemeIs(PAGE_ICON_SCHEME, &isPageIconScheme)) ||
|
|
!isPageIconScheme) {
|
|
return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
|
|
__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;
|
|
|
|
RefPtr<RemoteStreamPromise::Private> outerPromise =
|
|
new RemoteStreamPromise::Private(__func__);
|
|
nsCOMPtr<nsIURI> uri(aChildURI);
|
|
nsCOMPtr<nsILoadInfo> loadInfo(aLoadInfo);
|
|
RefPtr<PageIconProtocolHandler> self = this;
|
|
|
|
GetFaviconData(uri, loadInfo)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[outerPromise](const FaviconMetadata& aMetadata) {
|
|
RemoteStreamInfo info(aMetadata.mStream, aMetadata.mContentType,
|
|
aMetadata.mContentLength);
|
|
outerPromise->Resolve(std::move(info), __func__);
|
|
},
|
|
[self, uri, loadInfo, outerPromise](nsresult aRv) {
|
|
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
|
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
|
self->GetStreams(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut));
|
|
|
|
RemoteStreamInfo info(
|
|
pipeIn, nsLiteralCString(FAVICON_DEFAULT_MIMETYPE), -1);
|
|
Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut);
|
|
outerPromise->Resolve(std::move(info), __func__);
|
|
});
|
|
return outerPromise;
|
|
}
|
|
|
|
void PageIconProtocolHandler::GetStreams(nsIAsyncInputStream** inStream,
|
|
nsIAsyncOutputStream** outStream) {
|
|
static constexpr size_t kSegmentSize = 4096;
|
|
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
|
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
|
NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true,
|
|
kSegmentSize,
|
|
nsIFaviconService::MAX_FAVICON_BUFFER_SIZE / kSegmentSize);
|
|
|
|
pipeIn.forget(inStream);
|
|
pipeOut.forget(outStream);
|
|
}
|
|
|
|
// static
|
|
void PageIconProtocolHandler::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::SendGetPageIconStream);
|
|
});
|
|
|
|
channel.swap(*aRetVal);
|
|
}
|
|
|
|
} // namespace mozilla::places
|