diff options
Diffstat (limited to 'mobile/android/components/geckoview/GeckoViewStreamListener.cpp')
-rw-r--r-- | mobile/android/components/geckoview/GeckoViewStreamListener.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/mobile/android/components/geckoview/GeckoViewStreamListener.cpp b/mobile/android/components/geckoview/GeckoViewStreamListener.cpp new file mode 100644 index 0000000000..71b9fadeb5 --- /dev/null +++ b/mobile/android/components/geckoview/GeckoViewStreamListener.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 2; indent-tabs-mode: nil; -*- + * 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 "GeckoViewStreamListener.h" + +#include "mozilla/fallible.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIChannelEventSink.h" +#include "nsIHttpChannel.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIInputStream.h" +#include "nsINSSErrorsService.h" +#include "nsITransportSecurityInfo.h" +#include "nsIWebProgressListener.h" +#include "nsIX509Cert.h" +#include "nsPrintfCString.h" + +#include "nsNetUtil.h" + +#include "JavaBuiltins.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(GeckoViewStreamListener, nsIStreamListener, + nsIInterfaceRequestor, nsIChannelEventSink) + +class HeaderVisitor final : public nsIHttpHeaderVisitor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit HeaderVisitor(java::WebResponse::Builder::Param aBuilder) + : mBuilder(aBuilder) {} + + NS_IMETHOD + VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { + mBuilder->Header(aHeader, aValue); + return NS_OK; + } + + private: + virtual ~HeaderVisitor() {} + + const java::WebResponse::Builder::GlobalRef mBuilder; +}; + +NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor) + +class StreamSupport final + : public java::GeckoInputStream::Support::Natives<StreamSupport> { + public: + typedef java::GeckoInputStream::Support::Natives<StreamSupport> Base; + using Base::AttachNative; + using Base::GetNative; + + explicit StreamSupport(java::GeckoInputStream::Support::Param aInstance, + nsIRequest* aRequest) + : mInstance(aInstance), mRequest(aRequest) {} + + void Close() { + mRequest->Cancel(NS_ERROR_ABORT); + mRequest->Resume(); + + // This is basically `delete this`, so don't run anything else! + Base::DisposeNative(mInstance); + } + + void Resume() { mRequest->Resume(); } + + private: + java::GeckoInputStream::Support::GlobalRef mInstance; + nsCOMPtr<nsIRequest> mRequest; +}; + +NS_IMETHODIMP +GeckoViewStreamListener::OnStartRequest(nsIRequest* aRequest) { + MOZ_ASSERT(!mStream); + + nsresult status; + aRequest->GetStatus(&status); + if (NS_FAILED(status)) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + CompleteWithError(status, channel); + return NS_OK; + } + + // We're expecting data later via OnDataAvailable, so create the stream now. + InitializeStreamSupport(aRequest); + + mStream = java::GeckoInputStream::New(mSupport); + + // Suspend the request immediately. It will be resumed when (if) someone + // tries to read the Java stream. + aRequest->Suspend(); + + nsresult rv = HandleWebResponse(aRequest); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + CompleteWithError(rv, channel); + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +GeckoViewStreamListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + if (mStream) { + if (NS_FAILED(aStatusCode)) { + mStream->SendError(); + } else { + mStream->SendEof(); + } + } + return NS_OK; +} + +NS_IMETHODIMP GeckoViewStreamListener::OnDataAvailable( + nsIRequest* aRequest, nsIInputStream* aInputStream, uint64_t aOffset, + uint32_t aCount) { + MOZ_ASSERT(mStream); + + // We only need this for the ReadSegments call, the value is unused. + uint32_t countRead; + nsresult rv = + aInputStream->ReadSegments(WriteSegment, this, aCount, &countRead); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP +GeckoViewStreamListener::GetInterface(const nsIID& aIID, void** aResultOut) { + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + *aResultOut = static_cast<nsIChannelEventSink*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +GeckoViewStreamListener::AsyncOnChannelRedirect( + nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) { + callback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +/* static */ +nsresult GeckoViewStreamListener::WriteSegment( + nsIInputStream* aInputStream, void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) { + GeckoViewStreamListener* self = + static_cast<GeckoViewStreamListener*>(aClosure); + MOZ_ASSERT(self); + MOZ_ASSERT(self->mStream); + + *aWriteCount = aCount; + + jni::ByteArray::LocalRef buffer = jni::ByteArray::New( + reinterpret_cast<signed char*>(const_cast<char*>(aFromSegment)), + *aWriteCount, fallible); + if (!buffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (NS_FAILED(self->mStream->AppendBuffer(buffer))) { + // The stream was closed or something, abort reading this channel. + return NS_ERROR_ABORT; + } + + return NS_OK; +} + +nsresult GeckoViewStreamListener::HandleWebResponse(nsIRequest* aRequest) { + nsresult rv; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // URI + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString uriSpec; + rv = uri->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + + java::WebResponse::Builder::LocalRef builder = + java::WebResponse::Builder::New(uriSpec); + + // Body stream + if (mStream) { + builder->Body(mStream); + } + + // Redirected + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + builder->Redirected(!loadInfo->RedirectChain().IsEmpty()); + + // Secure status + auto [certBytes, isSecure] = CertificateFromChannel(channel); + builder->IsSecure(isSecure); + if (certBytes) { + rv = builder->CertificateBytes(certBytes); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We might need some additional info for response to http/https request + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv)); + if (httpChannel) { + // Status code + uint32_t statusCode; + rv = httpChannel->GetResponseStatus(&statusCode); + NS_ENSURE_SUCCESS(rv, rv); + builder->StatusCode(statusCode); + + // Headers + RefPtr<HeaderVisitor> visitor = new HeaderVisitor(builder); + rv = httpChannel->VisitResponseHeaders(visitor); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // Headers for other responses + // try to provide some basic metadata about the response + nsString filename; + if (NS_SUCCEEDED(channel->GetContentDispositionFilename(filename))) { + builder->Header(jni::StringParam(u"content-disposition"_ns), + nsPrintfCString("attachment; filename=\"%s\"", + NS_ConvertUTF16toUTF8(filename).get())); + } + + nsCString contentType; + if (NS_SUCCEEDED(channel->GetContentType(contentType))) { + builder->Header(jni::StringParam(u"content-type"_ns), contentType); + } + + int64_t contentLength = 0; + if (NS_SUCCEEDED(channel->GetContentLength(&contentLength))) { + nsString contentLengthString; + contentLengthString.AppendInt(contentLength); + builder->Header(jni::StringParam(u"content-length"_ns), + contentLengthString); + } + } + + java::WebResponse::GlobalRef response = builder->Build(); + + SendWebResponse(response); + return NS_OK; +} + +void GeckoViewStreamListener::InitializeStreamSupport(nsIRequest* aRequest) { + StreamSupport::Init(); + + mSupport = java::GeckoInputStream::Support::New(); + StreamSupport::AttachNative( + mSupport, mozilla::MakeUnique<StreamSupport>(mSupport, aRequest)); +} + +std::tuple<jni::ByteArray::LocalRef, java::sdk::Boolean::LocalRef> +GeckoViewStreamListener::CertificateFromChannel(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + aChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (!securityInfo) { + return std::make_tuple((jni::ByteArray::LocalRef) nullptr, + (java::sdk::Boolean::LocalRef) nullptr); + } + + uint32_t securityState = 0; + securityInfo->GetSecurityState(&securityState); + auto isSecure = securityState == nsIWebProgressListener::STATE_IS_SECURE + ? java::sdk::Boolean::TRUE() + : java::sdk::Boolean::FALSE(); + + nsCOMPtr<nsIX509Cert> cert; + securityInfo->GetServerCert(getter_AddRefs(cert)); + if (!cert) { + return std::make_tuple((jni::ByteArray::LocalRef) nullptr, + (java::sdk::Boolean::LocalRef) nullptr); + } + + nsTArray<uint8_t> derBytes; + nsresult rv = cert->GetRawDER(derBytes); + NS_ENSURE_SUCCESS(rv, + std::make_tuple((jni::ByteArray::LocalRef) nullptr, + (java::sdk::Boolean::LocalRef) nullptr)); + + auto certBytes = jni::ByteArray::New( + reinterpret_cast<const int8_t*>(derBytes.Elements()), derBytes.Length()); + + return std::make_tuple(certBytes, isSecure); +} |