summaryrefslogtreecommitdiffstats
path: root/mobile/android/components/geckoview/GeckoViewStreamListener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/components/geckoview/GeckoViewStreamListener.cpp')
-rw-r--r--mobile/android/components/geckoview/GeckoViewStreamListener.cpp298
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);
+}