/* 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 mStream; nsCString mContentType; int64_t mContentLength = 0; }; StaticRefPtr 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 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 ios = mozilla::components::IO::Service(); nsCOMPtr chan; nsCOMPtr 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(aOutputStream); nsCOMPtr listener; nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), aOutputStream, observer); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 Promise() { return mPromiseHolder.Ensure(__func__); } private: ~FaviconDataCallback(); nsCOMPtr mURI; MozPromiseHolder mPromiseHolder; nsCOMPtr 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 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 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 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 pipeIn; nsCOMPtr pipeOut; GetStreams(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut)); // Create our channel. nsCOMPtr channel; { // We override the channel's loadinfo below anyway, so using a null // principal here is alright. nsCOMPtr 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 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 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 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(aPageIconURI, aLoadInfo); rv = faviconService->GetFaviconDataForPage(pageURI, faviconCallback, preferredSize); if (NS_FAILED(rv)) { return FaviconMetadataPromise::CreateAndReject(rv, __func__); } return faviconCallback->Promise(); } RefPtr 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 outerPromise = new RemoteStreamPromise::Private(__func__); nsCOMPtr uri(aChildURI); nsCOMPtr loadInfo(aLoadInfo); RefPtr 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 pipeIn; nsCOMPtr 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 pipeIn; nsCOMPtr 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 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