forked from mirrors/gecko-dev
CookieJarSettings frequently gets populated in a place where we have ready access to the Document/Channel it is being constructed for. This lets us populate the boolean and pass it into CookieJarSetting's constructor easily. When it is created for LoadInfo, we need to plumb the URI through by adding it to LoadInfo::CreateForDocument. Differential Revision: https://phabricator.services.mozilla.com/D150588
465 lines
14 KiB
C++
465 lines
14 KiB
C++
/* -*- 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
|
|
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, 0,
|
|
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
|