diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/viaduct/ViaductRequest.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/viaduct/ViaductRequest.cpp')
-rw-r--r-- | toolkit/components/viaduct/ViaductRequest.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/toolkit/components/viaduct/ViaductRequest.cpp b/toolkit/components/viaduct/ViaductRequest.cpp new file mode 100644 index 0000000000..a8a012b6d8 --- /dev/null +++ b/toolkit/components/viaduct/ViaductRequest.cpp @@ -0,0 +1,334 @@ +/* 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 "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()); + } + + 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 |