/* -*- 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 "OhttpHelper.h" #include "WebExecutorSupport.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/java/WebMessageWrappers.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIChannel.h" #include "nsIChannelEventSink.h" #include "nsIHttpChannel.h" #include "nsIInputStream.h" #include "nsIObliviousHttp.h" #include "nsIStreamListener.h" #include "nsIUploadChannel2.h" #include "nsNetUtil.h" namespace mozilla::widget { class CallbackResponseListener : public nsIStreamListener, public nsIChannelEventSink { public: NS_DECL_ISUPPORTS CallbackResponseListener( nsIChannel* aChannel, std::function&)> aCallback) : mCallback(std::move(aCallback)), mChannel(aChannel) {} NS_IMETHODIMP OnStartRequest(nsIRequest* aRequest) override { return NS_OK; } NS_IMETHODIMP OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) override { nsTArray buffer(aCount); void* dest = buffer.Elements(); nsresult rv = NS_ReadInputStreamToBuffer(aInputStream, &dest, static_cast(aCount)); if (NS_FAILED(rv)) { mChannel->Cancel(NS_BINDING_ABORTED); } mBuffer.AppendElements(buffer.Elements(), aCount); return NS_OK; } #define ENSURE_SUCCESS(rv) \ if (NS_FAILED(rv)) { \ mCallback(rv, 0, mBuffer); \ return NS_OK; \ } #define ENSURE_TRUE(instance) \ if (!(instance)) { \ mCallback(NS_ERROR_FAILURE, 0, mBuffer); \ return NS_OK; \ } NS_IMETHODIMP OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) override { auto defer = MakeScopeExit([&] { mChannel = nullptr; }); ENSURE_SUCCESS(aStatusCode) nsCOMPtr httpChannel = do_QueryInterface(aRequest); ENSURE_TRUE(httpChannel); uint32_t status; nsresult rv = httpChannel->GetResponseStatus(&status); ENSURE_SUCCESS(rv) ENSURE_TRUE(status <= INT32_MAX); mCallback(NS_OK, static_cast(status), mBuffer); return NS_OK; } #undef ENSURE_SUCCESS #undef ENSURE_TRUE NS_IMETHODIMP AsyncOnChannelRedirect( nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, nsIAsyncVerifyRedirectCallback* callback) override { // We don't support redirects. callback->OnRedirectVerifyCallback(NS_ERROR_ABORT); return NS_OK; } private: virtual ~CallbackResponseListener() {} // nsresult, http status code, http headers, response data // only called from OnStopRequest std::function&)> mCallback; nsCOMPtr mChannel; nsTArray mBuffer; }; NS_IMPL_ISUPPORTS(CallbackResponseListener, nsIStreamListener, nsIChannelEventSink); nsresult OhttpHelper::EnsurePrefsRead() { if (sInitializationBitset & InitializationBit::PREFS) { return NS_OK; } nsresult rv = Preferences::GetCString("network.ohttp.configURL", sConfigUrl); NS_ENSURE_SUCCESS(rv, rv); rv = Preferences::GetCString("network.ohttp.relayURL", sRelayUrl); NS_ENSURE_SUCCESS(rv, rv); sInitializationBitset |= PREFS; return NS_OK; } void OhttpHelper::QueueOhttpRequest(OhttpRequest* aRequest) { // Initialize the pending requests array if it hasn't been initialized yet. if (!(sInitializationBitset & InitializationBit::PENDING_REQUESTS)) { sPendingRequests = new nsTArray>(); ClearOnShutdown(&sPendingRequests); sInitializationBitset |= InitializationBit::PENDING_REQUESTS; } // Queue the request. sPendingRequests->AppendElement(aRequest); } nsresult OhttpHelper::CreateChannel(java::WebRequest::Param aRequest, nsIURI* aUri, nsIChannel** outChannel) { MOZ_ASSERT(sInitializationBitset & InitializationBit::CONFIG_FETCHED); const auto reqBase = java::WebMessage::LocalRef(aRequest.Cast()); nsCOMPtr ohttpService( do_GetService("@mozilla.org/network/oblivious-http-service;1")); NS_ENSURE_TRUE(ohttpService, NS_ERROR_FAILURE); RefPtr relayUri; nsresult rv = NS_NewURI(getter_AddRefs(relayUri), sRelayUrl); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr channel; rv = ohttpService->NewChannel(relayUri, aUri, *sConfigData, getter_AddRefs(channel)); NS_ENSURE_SUCCESS(rv, rv); channel.forget(outChannel); return NS_OK; } nsresult OhttpHelper::FetchConfigAndFulfillRequests() { if (sInitializationBitset & InitializationBit::CONFIG_FETCHING) { return NS_OK; } nsCOMPtr httpChannel; nsresult rv = CreateConfigRequest(getter_AddRefs(httpChannel)); NS_ENSURE_SUCCESS(rv, rv); RefPtr listener = new CallbackResponseListener( httpChannel, [](nsresult rv, int64_t status, const nsTArray& buffer) { sInitializationBitset &= ~InitializationBit::CONFIG_FETCHING; if (NS_FAILED(rv) || status != 200) { FailRequests(rv); return; } sConfigData = new nsTArray(buffer.Length()); ClearOnShutdown(&sConfigData); sConfigData->Assign(buffer); sInitializationBitset |= InitializationBit::CONFIG_FETCHED; for (auto& request : *sPendingRequests) { rv = WebExecutorSupport::CreateStreamLoader( request->request, request->flags, request->result); if (NS_FAILED(rv)) { WebExecutorSupport::CompleteWithError(request->result, rv); } } sPendingRequests->Clear(); }); rv = httpChannel->AsyncOpen(listener); NS_ENSURE_SUCCESS(rv, rv); sInitializationBitset |= InitializationBit::CONFIG_FETCHING; return NS_OK; } void OhttpHelper::FailRequests(nsresult aStatus) { for (auto& request : *sPendingRequests) { WebExecutorSupport::CompleteWithError(request->result, aStatus); } sPendingRequests->Clear(); } nsresult OhttpHelper::CreateConfigRequest(nsIChannel** outChannel) { nsresult rv = EnsurePrefsRead(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr configURI; rv = NS_NewURI(getter_AddRefs(configURI), sConfigUrl); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), configURI, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL | nsILoadInfo::SEC_COOKIES_OMIT, nsIContentPolicy::TYPE_OTHER); NS_ENSURE_SUCCESS(rv, rv); // Flags from // https://searchfox.org/mozilla-central/rev/6fc1a107c44e011a585614cbbe8826f9e01ec802/netwerk/protocol/http/ObliviousHttpService.cpp#87-88 rv = channel->SetLoadFlags( nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr httpChannel = do_QueryInterface(channel); rv = httpChannel->SetRequestMethod("GET"_ns); NS_ENSURE_SUCCESS(rv, rv); httpChannel.forget(outChannel); return NS_OK; } } // namespace mozilla::widget