Files
firefox/widget/android/WebExecutorSupport.cpp
T
Ciure Andrei 899292bc3f Backed out 16 changesets (bug 1525245) for perma failing test_document_cookie.html CLOSED TREE
Backed out changeset 3fd27215698f (bug 1525245)
Backed out changeset d9a9e8e77873 (bug 1525245)
Backed out changeset 0e6f7be92e3f (bug 1525245)
Backed out changeset 6790802e2fa5 (bug 1525245)
Backed out changeset a5a9e01116ed (bug 1525245)
Backed out changeset 66e19a0c38dd (bug 1525245)
Backed out changeset fb90d51ba853 (bug 1525245)
Backed out changeset 4772db3625b3 (bug 1525245)
Backed out changeset 9affaf0cb998 (bug 1525245)
Backed out changeset a91b7ebe8bdd (bug 1525245)
Backed out changeset c2a13a7480e1 (bug 1525245)
Backed out changeset abe4482fa137 (bug 1525245)
Backed out changeset b3920c0bcf84 (bug 1525245)
Backed out changeset 0821b81f2724 (bug 1525245)
Backed out changeset 70bed2ad7899 (bug 1525245)
Backed out changeset 5f72ba232cc8 (bug 1525245)
2019-03-07 17:54:18 +02:00

565 lines
16 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 "WebExecutorSupport.h"
#include "nsIChannelEventSink.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIInputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsIStreamLoader.h"
#include "nsINSSErrorsService.h"
#include "nsIUploadChannel2.h"
#include "nsIDNSService.h"
#include "nsIDNSListener.h"
#include "nsIDNSRecord.h"
#include "mozilla/net/DNS.h" // for NetAddr
#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader
#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException
namespace mozilla {
using namespace net;
namespace widget {
static void CompleteWithError(java::GeckoResult::Param aResult,
nsresult aStatus) {
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;
}
java::WebRequestError::LocalRef error = java::WebRequestError::FromGeckoError(
int64_t(aStatus), NS_ERROR_GET_MODULE(aStatus), errorClass);
aResult->CompleteExceptionally(error.Cast<jni::Throwable>());
}
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(), *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 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::DisposeNative;
using Base::GetNative;
explicit StreamSupport(nsIRequest* aRequest) : mRequest(aRequest) {}
void Resume() { mRequest->Resume(); }
private:
nsCOMPtr<nsIRequest> mRequest;
};
class LoaderListener final : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink {
public:
NS_DECL_THREADSAFE_ISUPPORTS
explicit LoaderListener(java::GeckoResult::Param aResult,
bool aAllowRedirects)
: mResult(aResult), mAllowRedirects(aAllowRedirects) {
MOZ_ASSERT(mResult);
}
NS_IMETHOD
OnStartRequest(nsIRequest* aRequest) override {
MOZ_ASSERT(!mStream);
nsresult status;
aRequest->GetStatus(&status);
if (NS_FAILED(status)) {
CompleteWithError(mResult, status);
return NS_OK;
}
StreamSupport::Init();
// We're expecting data later via OnDataAvailable, so create the stream now.
mSupport = java::GeckoInputStream::Support::New();
StreamSupport::AttachNative(mSupport,
mozilla::MakeUnique<StreamSupport>(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)) {
CompleteWithError(mResult, rv);
return NS_OK;
}
return NS_OK;
}
NS_IMETHOD
OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) override {
if (mStream) {
mStream->SendEof();
}
return NS_OK;
}
NS_IMETHOD
OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream, uint64_t aOffset,
uint32_t aCount) override {
MOZ_ASSERT(mStream);
// We only need this for the ReadSegments call, the value is unused.
uint32_t countRead;
return aInputStream->ReadSegments(WriteSegment, this, aCount, &countRead);
}
NS_IMETHOD
GetInterface(const nsIID& aIID, void** aResultOut) override {
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
*aResultOut = static_cast<nsIChannelEventSink*>(this);
NS_ADDREF_THIS();
return NS_OK;
}
return NS_ERROR_NO_INTERFACE;
}
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;
}
private:
static nsresult WriteSegment(nsIInputStream* aInputStream, void* aClosure,
const char* aFromSegment, uint32_t aToOffset,
uint32_t aCount, uint32_t* aWriteCount) {
LoaderListener* self = static_cast<LoaderListener*>(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);
if (NS_FAILED(self->mStream->AppendBuffer(buffer))) {
// The stream was closed or something, abort reading this channel.
return NS_ERROR_ABORT;
}
return NS_OK;
}
NS_IMETHOD
HandleWebResponse(nsIRequest* aRequest) {
nsresult rv;
nsCOMPtr<nsIHttpChannel> 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 spec;
rv = uri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
java::WebResponse::Builder::LocalRef builder =
java::WebResponse::Builder::New(spec);
// Status code
uint32_t statusCode;
rv = channel->GetResponseStatus(&statusCode);
NS_ENSURE_SUCCESS(rv, rv);
builder->StatusCode(statusCode);
// Headers
RefPtr<HeaderVisitor> visitor = new HeaderVisitor(builder);
rv = channel->VisitResponseHeaders(visitor);
NS_ENSURE_SUCCESS(rv, rv);
// Redirected
nsCOMPtr<nsILoadInfo> loadInfo;
rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
NS_ENSURE_SUCCESS(rv, rv);
builder->Redirected(!loadInfo->RedirectChain().IsEmpty());
// Body stream
if (mStream) {
builder->Body(mStream);
}
mResult->Complete(builder->Build());
return NS_OK;
}
virtual ~LoaderListener() {}
const java::GeckoResult::GlobalRef mResult;
java::GeckoInputStream::GlobalRef mStream;
java::GeckoInputStream::Support::GlobalRef mSupport;
bool mAllowRedirects;
};
NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamListener, nsIInterfaceRequestor,
nsIChannelEventSink)
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>());
}
NS_IMETHOD
OnLookupByTypeComplete(nsICancelable* aRequest, nsIDNSByTypeRecord* aRecord,
nsresult aStatus) override {
MOZ_ASSERT_UNREACHABLE("unxpected nsIDNSListener callback");
return NS_ERROR_UNEXPECTED;
}
private:
nsresult CompleteWithRecord(nsIDNSRecord* aRecord) {
nsTArray<NetAddr> addrs;
nsresult rv = aRecord->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;
}
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_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
NS_ENSURE_SUCCESS(rv, rv);
if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_ANONYMOUS) {
channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS);
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv));
NS_ENSURE_SUCCESS(rv, rv);
// Method
rv = httpChannel->SetRequestMethod(aRequest->Method()->ToCString());
NS_ENSURE_SUCCESS(rv, rv);
// Headers
const auto keys = reqBase->GetHeaderKeys();
const auto values = reqBase->GetHeaderValues();
auto contentType = EmptyCString();
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 = httpChannel->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(channel, &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);
}
rv = httpChannel->SetReferrer(referrerUri);
NS_ENSURE_SUCCESS(rv, rv);
// Cache mode
nsCOMPtr<nsIHttpChannelInternal> internalChannel(
do_QueryInterface(channel, &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);
// We don't have any UI
rv = internalChannel->SetBlockAuthPrompt(true);
NS_ENSURE_SUCCESS(rv, rv);
const bool allowRedirects =
!(aFlags & java::GeckoWebExecutor::FETCH_FLAGS_NO_REDIRECTS);
// All done, set up the listener
RefPtr<LoaderListener> listener = new LoaderListener(aResult, allowRedirects);
rv = channel->SetNotificationCallbacks(listener);
NS_ENSURE_SUCCESS(rv, rv);
// Finally, open the channel
rv = httpChannel->AsyncOpen(listener);
return NS_OK;
}
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, 0, 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