diff options
Diffstat (limited to '')
-rw-r--r-- | widget/android/WebExecutorSupport.cpp | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp new file mode 100644 index 0000000000..eca9790aad --- /dev/null +++ b/widget/android/WebExecutorSupport.cpp @@ -0,0 +1,469 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 <algorithm> + +#include "GeckoViewStreamListener.h" +#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException +#include "ReferrerInfo.h" +#include "WebExecutorSupport.h" + +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIDNSService.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsINSSErrorsService.h" +#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader +#include "nsIPrivateBrowsingChannel.h" +#include "nsIUploadChannel2.h" +#include "nsIX509Cert.h" + +#include "mozilla/Preferences.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/net/DNS.h" // for NetAddr +#include "mozilla/java/GeckoWebExecutorWrappers.h" +#include "mozilla/java/WebMessageWrappers.h" +#include "mozilla/java/WebRequestErrorWrappers.h" +#include "mozilla/java/WebResponseWrappers.h" + +namespace mozilla { +using namespace net; + +namespace widget { + +static void CompleteWithError(java::GeckoResult::Param aResult, + nsresult aStatus, nsIChannel* aChannel) { + nsCOMPtr<nsINSSErrorsService> errSvc = + do_GetService("@mozilla.org/nss_errors_service;1"); + MOZ_ASSERT(errSvc); + + uint32_t errorClass; + nsresult rv = errSvc->GetErrorClass(aStatus, &errorClass); + if (NS_FAILED(rv)) { + errorClass = 0; + } + + jni::ByteArray::LocalRef certBytes; + if (aChannel) { + std::tie(certBytes, std::ignore) = + GeckoViewStreamListener::CertificateFromChannel(aChannel); + } + + java::WebRequestError::LocalRef error = java::WebRequestError::FromGeckoError( + int64_t(aStatus), NS_ERROR_GET_MODULE(aStatus), errorClass, certBytes); + + aResult->CompleteExceptionally(error.Cast<jni::Throwable>()); +} + +static void CompleteWithError(java::GeckoResult::Param aResult, + nsresult aStatus) { + CompleteWithError(aResult, aStatus, nullptr); +} + +class ByteBufferStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit ByteBufferStream(jni::ByteBuffer::Param buffer) + : mBuffer(buffer), mPosition(0), mClosed(false) { + MOZ_ASSERT(mBuffer); + MOZ_ASSERT(mBuffer->Address()); + } + + NS_IMETHOD + Close() override { + mClosed = true; + return NS_OK; + } + + NS_IMETHOD + Available(uint64_t* aResult) override { + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + *aResult = (mBuffer->Capacity() - mPosition); + return NS_OK; + } + + NS_IMETHOD + StreamStatus() override { return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; } + + NS_IMETHOD + Read(char* aBuf, uint32_t aCount, uint32_t* aCountRead) override { + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + *aCountRead = uint32_t( + std::min(uint64_t(mBuffer->Capacity() - mPosition), uint64_t(aCount))); + + if (*aCountRead > 0) { + memcpy(aBuf, (char*)mBuffer->Address() + mPosition, *aCountRead); + mPosition += *aCountRead; + } + + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + IsNonBlocking(bool* aResult) override { + *aResult = false; + return NS_OK; + } + + protected: + virtual ~ByteBufferStream() {} + + const jni::ByteBuffer::GlobalRef mBuffer; + uint64_t mPosition; + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(ByteBufferStream, nsIInputStream) + +class LoaderListener final : public GeckoViewStreamListener { + public: + explicit LoaderListener(java::GeckoResult::Param aResult, + bool aAllowRedirects, bool testStreamFailure) + : GeckoViewStreamListener(), + mResult(aResult), + mTestStreamFailure(testStreamFailure), + mAllowRedirects(aAllowRedirects) { + MOZ_ASSERT(mResult); + } + + NS_IMETHOD + OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) override { + MOZ_ASSERT(mStream); + + if (mTestStreamFailure) { + return NS_ERROR_UNEXPECTED; + } + + // 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_IMETHOD + AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, + uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) override { + if (!mAllowRedirects) { + return NS_ERROR_ABORT; + } + + callback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; + } + + void SendWebResponse(java::WebResponse::Param aResponse) override { + mResult->Complete(aResponse); + } + + void CompleteWithError(nsresult aStatus, nsIChannel* aChannel) override { + ::CompleteWithError(mResult, aStatus, aChannel); + } + + virtual ~LoaderListener() {} + + const java::GeckoResult::GlobalRef mResult; + const bool mTestStreamFailure; + bool mAllowRedirects; +}; + +class DNSListener final : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + DNSListener(const nsCString& aHost, java::GeckoResult::Param aResult) + : mHost(aHost), mResult(aResult) {} + + NS_IMETHOD + OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, + nsresult aStatus) override { + if (NS_FAILED(aStatus)) { + CompleteUnknownHostError(); + return NS_OK; + } + + nsresult rv = CompleteWithRecord(aRecord); + if (NS_FAILED(rv)) { + CompleteUnknownHostError(); + return NS_OK; + } + + return NS_OK; + } + + void CompleteUnknownHostError() { + java::sdk::UnknownHostException::LocalRef error = + java::sdk::UnknownHostException::New(); + mResult->CompleteExceptionally(error.Cast<jni::Throwable>()); + } + + private: + nsresult CompleteWithRecord(nsIDNSRecord* aRecord) { + nsTArray<NetAddr> addrs; + nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord); + if (!rec) { + return NS_ERROR_UNEXPECTED; + } + nsresult rv = rec->GetAddresses(addrs); + NS_ENSURE_SUCCESS(rv, rv); + + jni::ByteArray::LocalRef bytes; + auto objects = + jni::ObjectArray::New<java::sdk::InetAddress>(addrs.Length()); + for (size_t i = 0; i < addrs.Length(); i++) { + const auto& addr = addrs[i]; + if (addr.raw.family == AF_INET) { + bytes = jni::ByteArray::New( + reinterpret_cast<const int8_t*>(&addr.inet.ip), 4); + } else if (addr.raw.family == AF_INET6) { + bytes = jni::ByteArray::New( + reinterpret_cast<const int8_t*>(&addr.inet6.ip), 16); + } else { + // We don't handle this, skip it. + continue; + } + + objects->SetElement(i, + java::sdk::InetAddress::GetByAddress(mHost, bytes)); + } + + mResult->Complete(objects); + return NS_OK; + } + + virtual ~DNSListener() {} + + const nsCString mHost; + const java::GeckoResult::GlobalRef mResult; +}; + +NS_IMPL_ISUPPORTS(DNSListener, nsIDNSListener) + +static nsresult ConvertCacheMode(int32_t mode, int32_t& result) { + switch (mode) { + case java::WebRequest::CACHE_MODE_DEFAULT: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT; + break; + case java::WebRequest::CACHE_MODE_NO_STORE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE; + break; + case java::WebRequest::CACHE_MODE_RELOAD: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD; + break; + case java::WebRequest::CACHE_MODE_NO_CACHE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE; + break; + case java::WebRequest::CACHE_MODE_FORCE_CACHE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE; + break; + case java::WebRequest::CACHE_MODE_ONLY_IF_CACHED: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED; + break; + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +static nsresult SetupHttpChannel(nsIHttpChannel* aHttpChannel, + nsIChannel* aChannel, + java::WebRequest::Param aRequest) { + const auto req = java::WebRequest::LocalRef(aRequest); + const auto reqBase = java::WebMessage::LocalRef(req.Cast<java::WebMessage>()); + + // Method + nsresult rv = aHttpChannel->SetRequestMethod(aRequest->Method()->ToCString()); + NS_ENSURE_SUCCESS(rv, rv); + + // Headers + const auto keys = reqBase->GetHeaderKeys(); + const auto values = reqBase->GetHeaderValues(); + nsCString contentType; + for (size_t i = 0; i < keys->Length(); i++) { + const auto key = jni::String::LocalRef(keys->GetElement(i))->ToCString(); + const auto value = + jni::String::LocalRef(values->GetElement(i))->ToCString(); + + if (key.LowerCaseEqualsASCII("content-type")) { + contentType = value; + } + + // We clobber any duplicate keys here because we've already merged them + // in the upstream WebRequest. + rv = aHttpChannel->SetRequestHeader(key, value, false /* merge */); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Body + const auto body = req->Body(); + if (body) { + nsCOMPtr<nsIInputStream> stream = new ByteBufferStream(body); + + nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(aChannel, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = uploadChannel->ExplicitSetUploadStream( + stream, contentType, -1, aRequest->Method()->ToCString(), false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Referrer + RefPtr<nsIURI> referrerUri; + const auto referrer = req->Referrer(); + if (referrer) { + rv = NS_NewURI(getter_AddRefs(referrerUri), referrer->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + } + + nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrerUri); + rv = aHttpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + + // Cache mode + nsCOMPtr<nsIHttpChannelInternal> internalChannel( + do_QueryInterface(aChannel, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cacheMode; + rv = ConvertCacheMode(req->CacheMode(), cacheMode); + NS_ENSURE_SUCCESS(rv, rv); + + rv = internalChannel->SetFetchCacheMode(cacheMode); + NS_ENSURE_SUCCESS(rv, rv); + + if (req->BeConservative()) { + rv = internalChannel->SetBeConservative(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We don't have any UI + rv = internalChannel->SetBlockAuthPrompt(true); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult WebExecutorSupport::CreateStreamLoader( + java::WebRequest::Param aRequest, int32_t aFlags, + java::GeckoResult::Param aResult) { + const auto req = java::WebRequest::LocalRef(aRequest); + const auto reqBase = java::WebMessage::LocalRef(req.Cast<java::WebMessage>()); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), reqBase->Uri()->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_ANONYMOUS) { + channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS); + } + + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting(channel); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_PRIVATE) { + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel); + NS_ENSURE_TRUE(pbChannel, NS_ERROR_FAILURE); + pbChannel->SetPrivate(true); + cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting); + } else { + cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + MOZ_ASSERT(cookieJarSettings); + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + loadInfo->SetCookieJarSettings(cookieJarSettings); + + // setup http/https specific things + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv)); + if (httpChannel) { + rv = SetupHttpChannel(httpChannel, channel, aRequest); + NS_ENSURE_SUCCESS(rv, rv); + } + + // set up the listener + const bool allowRedirects = + !(aFlags & java::GeckoWebExecutor::FETCH_FLAGS_NO_REDIRECTS); + const bool testStreamFailure = + (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_STREAM_FAILURE_TEST); + + RefPtr<LoaderListener> listener = + new LoaderListener(aResult, allowRedirects, testStreamFailure); + + rv = channel->SetNotificationCallbacks(listener); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally, open the channel + return channel->AsyncOpen(listener); +} + +void WebExecutorSupport::Fetch(jni::Object::Param aRequest, int32_t aFlags, + jni::Object::Param aResult) { + const auto request = java::WebRequest::LocalRef(aRequest); + auto result = java::GeckoResult::LocalRef(aResult); + + nsresult rv = CreateStreamLoader(request, aFlags, result); + if (NS_FAILED(rv)) { + CompleteWithError(result, rv); + } +} + +static nsresult ResolveHost(nsCString& host, java::GeckoResult::Param result) { + nsresult rv; + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsICancelable> cancelable; + RefPtr<DNSListener> listener = new DNSListener(host, result); + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, + listener, nullptr /* aListenerTarget */, + OriginAttributes(), getter_AddRefs(cancelable)); + return rv; +} + +void WebExecutorSupport::Resolve(jni::String::Param aUri, + jni::Object::Param aResult) { + auto result = java::GeckoResult::LocalRef(aResult); + + nsCString uri = aUri->ToCString(); + nsresult rv = ResolveHost(uri, result); + if (NS_FAILED(rv)) { + java::sdk::UnknownHostException::LocalRef error = + java::sdk::UnknownHostException::New(); + result->CompleteExceptionally(error.Cast<jni::Throwable>()); + } +} + +} // namespace widget +} // namespace mozilla |