зеркало из https://github.com/mozilla/gecko-dev.git
570 строки
16 KiB
C++
570 строки
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 "mozilla/net/CookieSettings.h"
|
|
|
|
#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader
|
|
|
|
#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException
|
|
#include "ReferrerInfo.h"
|
|
|
|
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() + 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 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 = channel->LoadInfo();
|
|
|
|
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<nsICookieSettings> cookieSettings = CookieSettings::Create();
|
|
MOZ_ASSERT(cookieSettings);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
loadInfo->SetCookieSettings(cookieSettings);
|
|
|
|
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);
|
|
}
|
|
|
|
nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrerUri);
|
|
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
|
|
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
|