/* -*- 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 { public: typedef java::GeckoInputStream::Support::Natives 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 mRequest; }; NS_IMETHODIMP GeckoViewStreamListener::OnStartRequest(nsIRequest* aRequest) { MOZ_ASSERT(!mStream); nsresult status; aRequest->GetStatus(&status); if (NS_FAILED(status)) { nsCOMPtr 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 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(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(aClosure); MOZ_ASSERT(self); MOZ_ASSERT(self->mStream); *aWriteCount = aCount; jni::ByteArray::LocalRef buffer = jni::ByteArray::New( reinterpret_cast(const_cast(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 channel = do_QueryInterface(aRequest, &rv); NS_ENSURE_SUCCESS(rv, rv); // URI nsCOMPtr 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 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 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 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(mSupport, aRequest)); } std::tuple GeckoViewStreamListener::CertificateFromChannel(nsIChannel* aChannel) { MOZ_ASSERT(aChannel); nsCOMPtr 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 cert; securityInfo->GetServerCert(getter_AddRefs(cert)); if (!cert) { return std::make_tuple((jni::ByteArray::LocalRef) nullptr, (java::sdk::Boolean::LocalRef) nullptr); } nsTArray 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(derBytes.Elements()), derBytes.Length()); return std::make_tuple(certBytes, isSecure); }