mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			335 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* 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 https://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "mozilla/ViaductRequest.h"
 | 
						|
 | 
						|
#include "mozilla/ClearOnShutdown.h"
 | 
						|
#include "mozilla/ErrorNames.h"
 | 
						|
#include "mozilla/ResultExtensions.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/Services.h"
 | 
						|
#include "mozilla/Try.h"
 | 
						|
 | 
						|
#include "nsComponentManagerUtils.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "nsIAsyncVerifyRedirectCallback.h"
 | 
						|
#include "nsIHttpChannel.h"
 | 
						|
#include "nsIHttpHeaderVisitor.h"
 | 
						|
#include "nsIInputStream.h"
 | 
						|
#include "nsIUploadChannel2.h"
 | 
						|
#include "nsIURI.h"
 | 
						|
#include "nsNetUtil.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "nsString.h"
 | 
						|
#include "nsStringStream.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
extern "C" {
 | 
						|
ViaductByteBuffer viaduct_alloc_bytebuffer(int32_t);
 | 
						|
void viaduct_destroy_bytebuffer(ViaductByteBuffer);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
class HeaderVisitor final : public nsIHttpHeaderVisitor {
 | 
						|
 public:
 | 
						|
  NS_DECL_ISUPPORTS
 | 
						|
  NS_DECL_NSIHTTPHEADERVISITOR
 | 
						|
 | 
						|
  explicit HeaderVisitor(
 | 
						|
      google::protobuf::Map<std::string, std::string>* aHeaders)
 | 
						|
      : mHeaders(aHeaders) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  google::protobuf::Map<std::string, std::string>* mHeaders;
 | 
						|
  ~HeaderVisitor() = default;
 | 
						|
};
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
HeaderVisitor::VisitHeader(const nsACString& aHeader,
 | 
						|
                           const nsACString& aValue) {
 | 
						|
  (*mHeaders)[aHeader.BeginReading()] = aValue.BeginReading();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
 | 
						|
 | 
						|
nsCString ConvertMethod(
 | 
						|
    appservices::httpconfig::protobuf::Request_Method method);
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
// ViaductRequest implementation
 | 
						|
 | 
						|
ViaductByteBuffer ViaductRequest::MakeRequest(ViaductByteBuffer reqBuf) {
 | 
						|
  MOZ_ASSERT(!NS_IsMainThread(), "Background thread only!");
 | 
						|
  auto clearBuf = MakeScopeExit([&] { viaduct_destroy_bytebuffer(reqBuf); });
 | 
						|
  // We keep the protobuf parsing/serializing in the background thread.
 | 
						|
  appservices::httpconfig::protobuf::Request request;
 | 
						|
  if (!request.ParseFromArray(static_cast<const void*>(reqBuf.data),
 | 
						|
                              reqBuf.len)) {
 | 
						|
    // We still need to return something!
 | 
						|
    return ViaductByteBuffer{.len = 0, .data = nullptr};
 | 
						|
  }
 | 
						|
  MonitorAutoLock lock(mMonitor);
 | 
						|
  NS_DispatchToMainThread(NS_NewRunnableFunction(
 | 
						|
      "ViaductRequest::LaunchRequest", [this, &request]() {
 | 
						|
        nsresult rv = LaunchRequest(request);
 | 
						|
        if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
          // Something went very very wrong, but we still have to unblock
 | 
						|
          // the calling thread.
 | 
						|
          NotifyMonitor();
 | 
						|
        }
 | 
						|
      }));
 | 
						|
  while (!mDone) {
 | 
						|
    mMonitor.Wait();
 | 
						|
  }
 | 
						|
  ViaductByteBuffer respBuf =
 | 
						|
      viaduct_alloc_bytebuffer(mResponse.ByteSizeLong());
 | 
						|
  if (!mResponse.SerializeToArray(respBuf.data, respBuf.len)) {
 | 
						|
    viaduct_destroy_bytebuffer(respBuf);
 | 
						|
    return ViaductByteBuffer{.len = 0, .data = nullptr};
 | 
						|
  }
 | 
						|
  return respBuf;
 | 
						|
}
 | 
						|
 | 
						|
nsresult ViaductRequest::LaunchRequest(
 | 
						|
    appservices::httpconfig::protobuf::Request& request) {
 | 
						|
  if (PastShutdownPhase(ShutdownPhase::AppShutdownNetTeardown)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  nsCOMPtr<nsIURI> uri;
 | 
						|
  nsresult rv = NS_NewURI(getter_AddRefs(uri), request.url().c_str());
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  nsSecurityFlags secFlags =
 | 
						|
      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
 | 
						|
      nsILoadInfo::SEC_COOKIES_OMIT;
 | 
						|
  uint32_t loadFlags = 0;
 | 
						|
 | 
						|
  if (!request.use_caches()) {
 | 
						|
    loadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!request.follow_redirects()) {
 | 
						|
    secFlags |= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS;
 | 
						|
  }
 | 
						|
 | 
						|
  rv = NS_NewChannel(getter_AddRefs(mChannel), uri,
 | 
						|
                     nsContentUtils::GetSystemPrincipal(), secFlags,
 | 
						|
                     nsIContentPolicy::TYPE_OTHER,
 | 
						|
                     nullptr,  // nsICookieJarSettings
 | 
						|
                     nullptr,  // aPerformanceStorage
 | 
						|
                     nullptr,  // aLoadGroup
 | 
						|
                     nullptr, loadFlags);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
 | 
						|
  nsCString method = ConvertMethod(request.method());
 | 
						|
  rv = httpChannel->SetRequestMethod(method);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  for (auto& header : request.headers()) {
 | 
						|
    rv = httpChannel->SetRequestHeader(
 | 
						|
        nsDependentCString(header.first.c_str(), header.first.size()),
 | 
						|
        nsDependentCString(header.second.c_str(), header.second.size()),
 | 
						|
        false /* merge */);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  }
 | 
						|
 | 
						|
  // Body
 | 
						|
  if (request.has_body()) {
 | 
						|
    const std::string& body = request.body();
 | 
						|
    nsCOMPtr<nsIStringInputStream> stream(
 | 
						|
        do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
 | 
						|
    rv = stream->SetData(body.data(), body.size());
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(mChannel);
 | 
						|
    uploadChannel->ExplicitSetUploadStream(stream, VoidCString(), -1, method,
 | 
						|
                                           false /* aStreamHasHeaders */);
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_TRY_VAR(
 | 
						|
      mConnectTimeoutTimer,
 | 
						|
      NS_NewTimerWithCallback(this, request.connect_timeout_secs() * 1000,
 | 
						|
                              nsITimer::TYPE_ONE_SHOT));
 | 
						|
  MOZ_TRY_VAR(mReadTimeoutTimer,
 | 
						|
              NS_NewTimerWithCallback(this, request.read_timeout_secs() * 1000,
 | 
						|
                                      nsITimer::TYPE_ONE_SHOT));
 | 
						|
 | 
						|
  rv = httpChannel->AsyncOpen(this);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsCString ConvertMethod(
 | 
						|
    appservices::httpconfig::protobuf::Request_Method method) {
 | 
						|
  using appservices::httpconfig::protobuf::Request_Method;
 | 
						|
  switch (method) {
 | 
						|
    case Request_Method::Request_Method_GET:
 | 
						|
      return "GET"_ns;
 | 
						|
    case Request_Method::Request_Method_HEAD:
 | 
						|
      return "HEAD"_ns;
 | 
						|
    case Request_Method::Request_Method_POST:
 | 
						|
      return "POST"_ns;
 | 
						|
    case Request_Method::Request_Method_PUT:
 | 
						|
      return "PUT"_ns;
 | 
						|
    case Request_Method::Request_Method_DELETE:
 | 
						|
      return "DELETE"_ns;
 | 
						|
    case Request_Method::Request_Method_CONNECT:
 | 
						|
      return "CONNECT"_ns;
 | 
						|
    case Request_Method::Request_Method_OPTIONS:
 | 
						|
      return "OPTIONS"_ns;
 | 
						|
    case Request_Method::Request_Method_TRACE:
 | 
						|
      return "TRACE"_ns;
 | 
						|
    case Request_Method::Request_Method_PATCH:
 | 
						|
      return "PATCH"_ns;
 | 
						|
  }
 | 
						|
  return "UNKNOWN"_ns;
 | 
						|
}
 | 
						|
 | 
						|
void ViaductRequest::ClearTimers() {
 | 
						|
  if (mConnectTimeoutTimer) {
 | 
						|
    mConnectTimeoutTimer->Cancel();
 | 
						|
    mConnectTimeoutTimer = nullptr;
 | 
						|
  }
 | 
						|
  if (mReadTimeoutTimer) {
 | 
						|
    mReadTimeoutTimer->Cancel();
 | 
						|
    mReadTimeoutTimer = nullptr;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ViaductRequest::NotifyMonitor() {
 | 
						|
  MonitorAutoLock lock(mMonitor);
 | 
						|
  mDone = true;
 | 
						|
  mMonitor.Notify();
 | 
						|
}
 | 
						|
 | 
						|
ViaductRequest::~ViaductRequest() {
 | 
						|
  ClearTimers();
 | 
						|
  if (mChannel) {
 | 
						|
    mChannel->Cancel(NS_ERROR_ABORT);
 | 
						|
    mChannel = nullptr;
 | 
						|
  }
 | 
						|
  NotifyMonitor();
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(ViaductRequest, nsIStreamListener, nsITimerCallback, nsINamed,
 | 
						|
                  nsIChannelEventSink)
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
// nsIStreamListener implementation
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::OnStartRequest(nsIRequest* aRequest) {
 | 
						|
  if (mConnectTimeoutTimer) {
 | 
						|
    mConnectTimeoutTimer->Cancel();
 | 
						|
    mConnectTimeoutTimer = nullptr;
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static nsresult AssignResponseToBuffer(nsIInputStream* aIn, void* aClosure,
 | 
						|
                                       const char* aFromRawSegment,
 | 
						|
                                       uint32_t aToOffset, uint32_t aCount,
 | 
						|
                                       uint32_t* aWriteCount) {
 | 
						|
  nsCString* buf = static_cast<nsCString*>(aClosure);
 | 
						|
  buf->Append(aFromRawSegment, aCount);
 | 
						|
  *aWriteCount = aCount;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::OnDataAvailable(nsIRequest* aRequest,
 | 
						|
                                nsIInputStream* aInputStream, uint64_t aOffset,
 | 
						|
                                uint32_t aCount) {
 | 
						|
  nsresult rv;
 | 
						|
  uint32_t readCount;
 | 
						|
  rv = aInputStream->ReadSegments(AssignResponseToBuffer, &mBodyBuffer, aCount,
 | 
						|
                                  &readCount);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
 | 
						|
  ClearTimers();
 | 
						|
  auto defer = MakeScopeExit([&] {
 | 
						|
    mChannel = nullptr;
 | 
						|
    NotifyMonitor();
 | 
						|
  });
 | 
						|
 | 
						|
  if (NS_FAILED(aStatusCode)) {
 | 
						|
    nsCString errorName;
 | 
						|
    GetErrorName(aStatusCode, errorName);
 | 
						|
    nsPrintfCString msg("Request error: %s", errorName.get());
 | 
						|
    mResponse.set_exception_message(msg.BeginReading());
 | 
						|
  } else {
 | 
						|
    nsresult rv;
 | 
						|
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
 | 
						|
    // Early return is OK because MakeScopeExit will call Notify()
 | 
						|
    // and unblock the original calling thread.
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    uint32_t httpStatus;
 | 
						|
    rv = httpChannel->GetResponseStatus(&httpStatus);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    mResponse.set_status(httpStatus);
 | 
						|
 | 
						|
    nsCOMPtr<nsIURI> uri;
 | 
						|
    httpChannel->GetURI(getter_AddRefs(uri));
 | 
						|
    nsAutoCString uriStr;
 | 
						|
    uri->GetSpec(uriStr);
 | 
						|
    mResponse.set_url(uriStr.BeginReading());
 | 
						|
 | 
						|
    auto* headers = mResponse.mutable_headers();
 | 
						|
    nsCOMPtr<nsIHttpHeaderVisitor> visitor = new HeaderVisitor(headers);
 | 
						|
    rv = httpChannel->VisitResponseHeaders(visitor);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    mResponse.set_body(mBodyBuffer.BeginReading(), mBodyBuffer.Length());
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
// nsIChannelEventSink implementation
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::AsyncOnChannelRedirect(
 | 
						|
    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t flags,
 | 
						|
    nsIAsyncVerifyRedirectCallback* callback) {
 | 
						|
  mChannel = aNewChannel;
 | 
						|
  callback->OnRedirectVerifyCallback(NS_OK);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
// nsITimerCallback implementation
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::Notify(nsITimer* timer) {
 | 
						|
  ClearTimers();
 | 
						|
  // Cancelling the channel will trigger OnStopRequest.
 | 
						|
  if (mChannel) {
 | 
						|
    mChannel->Cancel(NS_ERROR_ABORT);
 | 
						|
    mChannel = nullptr;
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////
 | 
						|
// nsINamed implementation
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ViaductRequest::GetName(nsACString& aName) {
 | 
						|
  aName.AssignLiteral("ViaductRequest");
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
};  // namespace mozilla
 |