1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5 #include "mozilla/ViaductRequest.h"
7 #include "mozilla/ClearOnShutdown.h"
8 #include "mozilla/ErrorNames.h"
9 #include "mozilla/ResultExtensions.h"
10 #include "mozilla/ScopeExit.h"
11 #include "mozilla/Services.h"
12 #include "mozilla/Try.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsContentUtils.h"
16 #include "nsIAsyncVerifyRedirectCallback.h"
17 #include "nsIHttpChannel.h"
18 #include "nsIHttpHeaderVisitor.h"
19 #include "nsIInputStream.h"
20 #include "nsIUploadChannel2.h"
22 #include "nsNetUtil.h"
23 #include "nsPrintfCString.h"
25 #include "nsStringStream.h"
26 #include "nsThreadUtils.h"
33 ViaductByteBuffer
viaduct_alloc_bytebuffer(int32_t);
34 void viaduct_destroy_bytebuffer(ViaductByteBuffer
);
39 class HeaderVisitor final
: public nsIHttpHeaderVisitor
{
42 NS_DECL_NSIHTTPHEADERVISITOR
44 explicit HeaderVisitor(
45 google::protobuf::Map
<std::string
, std::string
>* aHeaders
)
46 : mHeaders(aHeaders
) {}
49 google::protobuf::Map
<std::string
, std::string
>* mHeaders
;
50 ~HeaderVisitor() = default;
54 HeaderVisitor::VisitHeader(const nsACString
& aHeader
,
55 const nsACString
& aValue
) {
56 (*mHeaders
)[aHeader
.BeginReading()] = aValue
.BeginReading();
59 NS_IMPL_ISUPPORTS(HeaderVisitor
, nsIHttpHeaderVisitor
)
61 nsCString
ConvertMethod(
62 appservices::httpconfig::protobuf::Request_Method method
);
64 ///////////////////////////////////////////////////////////////////////////////
65 // ViaductRequest implementation
67 ViaductByteBuffer
ViaductRequest::MakeRequest(ViaductByteBuffer reqBuf
) {
68 MOZ_ASSERT(!NS_IsMainThread(), "Background thread only!");
69 auto clearBuf
= MakeScopeExit([&] { viaduct_destroy_bytebuffer(reqBuf
); });
70 // We keep the protobuf parsing/serializing in the background thread.
71 appservices::httpconfig::protobuf::Request request
;
72 if (!request
.ParseFromArray(static_cast<const void*>(reqBuf
.data
),
74 // We still need to return something!
75 return ViaductByteBuffer
{.len
= 0, .data
= nullptr};
77 MonitorAutoLock
lock(mMonitor
);
78 NS_DispatchToMainThread(NS_NewRunnableFunction(
79 "ViaductRequest::LaunchRequest", [this, &request
]() {
80 nsresult rv
= LaunchRequest(request
);
81 if (NS_WARN_IF(NS_FAILED(rv
))) {
82 // Something went very very wrong, but we still have to unblock
83 // the calling thread.
90 ViaductByteBuffer respBuf
=
91 viaduct_alloc_bytebuffer(mResponse
.ByteSizeLong());
92 if (!mResponse
.SerializeToArray(respBuf
.data
, respBuf
.len
)) {
93 viaduct_destroy_bytebuffer(respBuf
);
94 return ViaductByteBuffer
{.len
= 0, .data
= nullptr};
99 nsresult
ViaductRequest::LaunchRequest(
100 appservices::httpconfig::protobuf::Request
& request
) {
101 if (PastShutdownPhase(ShutdownPhase::AppShutdownNetTeardown
)) {
102 return NS_ERROR_FAILURE
;
104 nsCOMPtr
<nsIURI
> uri
;
105 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), request
.url().c_str());
106 NS_ENSURE_SUCCESS(rv
, rv
);
108 nsSecurityFlags secFlags
=
109 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
|
110 nsILoadInfo::SEC_COOKIES_OMIT
;
111 uint32_t loadFlags
= 0;
113 if (!request
.use_caches()) {
114 loadFlags
|= nsIRequest::LOAD_BYPASS_CACHE
;
117 if (!request
.follow_redirects()) {
118 secFlags
|= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS
;
121 rv
= NS_NewChannel(getter_AddRefs(mChannel
), uri
,
122 nsContentUtils::GetSystemPrincipal(), secFlags
,
123 nsIContentPolicy::TYPE_OTHER
,
124 nullptr, // nsICookieJarSettings
125 nullptr, // aPerformanceStorage
126 nullptr, // aLoadGroup
128 NS_ENSURE_SUCCESS(rv
, rv
);
130 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
);
131 nsCString method
= ConvertMethod(request
.method());
132 rv
= httpChannel
->SetRequestMethod(method
);
133 NS_ENSURE_SUCCESS(rv
, rv
);
135 for (auto& header
: request
.headers()) {
136 rv
= httpChannel
->SetRequestHeader(
137 nsDependentCString(header
.first
.c_str(), header
.first
.size()),
138 nsDependentCString(header
.second
.c_str(), header
.second
.size()),
140 NS_ENSURE_SUCCESS(rv
, rv
);
144 if (request
.has_body()) {
145 const std::string
& body
= request
.body();
146 nsCOMPtr
<nsIStringInputStream
> stream(
147 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID
));
148 rv
= stream
->CopyData(body
.data(), body
.size());
149 NS_ENSURE_SUCCESS(rv
, rv
);
150 nsCOMPtr
<nsIUploadChannel2
> uploadChannel
= do_QueryInterface(mChannel
);
151 uploadChannel
->ExplicitSetUploadStream(stream
, VoidCString(), -1, method
,
152 false /* aStreamHasHeaders */);
156 mConnectTimeoutTimer
,
157 NS_NewTimerWithCallback(this, request
.connect_timeout_secs() * 1000,
158 nsITimer::TYPE_ONE_SHOT
));
159 MOZ_TRY_VAR(mReadTimeoutTimer
,
160 NS_NewTimerWithCallback(this, request
.read_timeout_secs() * 1000,
161 nsITimer::TYPE_ONE_SHOT
));
163 rv
= httpChannel
->AsyncOpen(this);
164 NS_ENSURE_SUCCESS(rv
, rv
);
169 nsCString
ConvertMethod(
170 appservices::httpconfig::protobuf::Request_Method method
) {
171 using appservices::httpconfig::protobuf::Request_Method
;
173 case Request_Method::Request_Method_GET
:
175 case Request_Method::Request_Method_HEAD
:
177 case Request_Method::Request_Method_POST
:
179 case Request_Method::Request_Method_PUT
:
181 case Request_Method::Request_Method_DELETE
:
183 case Request_Method::Request_Method_CONNECT
:
185 case Request_Method::Request_Method_OPTIONS
:
187 case Request_Method::Request_Method_TRACE
:
189 case Request_Method::Request_Method_PATCH
:
195 void ViaductRequest::ClearTimers() {
196 if (mConnectTimeoutTimer
) {
197 mConnectTimeoutTimer
->Cancel();
198 mConnectTimeoutTimer
= nullptr;
200 if (mReadTimeoutTimer
) {
201 mReadTimeoutTimer
->Cancel();
202 mReadTimeoutTimer
= nullptr;
206 void ViaductRequest::NotifyMonitor() {
207 MonitorAutoLock
lock(mMonitor
);
212 ViaductRequest::~ViaductRequest() {
215 mChannel
->Cancel(NS_ERROR_ABORT
);
221 NS_IMPL_ISUPPORTS(ViaductRequest
, nsIStreamListener
, nsITimerCallback
, nsINamed
,
224 ///////////////////////////////////////////////////////////////////////////////
225 // nsIStreamListener implementation
228 ViaductRequest::OnStartRequest(nsIRequest
* aRequest
) {
229 if (mConnectTimeoutTimer
) {
230 mConnectTimeoutTimer
->Cancel();
231 mConnectTimeoutTimer
= nullptr;
236 static nsresult
AssignResponseToBuffer(nsIInputStream
* aIn
, void* aClosure
,
237 const char* aFromRawSegment
,
238 uint32_t aToOffset
, uint32_t aCount
,
239 uint32_t* aWriteCount
) {
240 nsCString
* buf
= static_cast<nsCString
*>(aClosure
);
241 buf
->Append(aFromRawSegment
, aCount
);
242 *aWriteCount
= aCount
;
247 ViaductRequest::OnDataAvailable(nsIRequest
* aRequest
,
248 nsIInputStream
* aInputStream
, uint64_t aOffset
,
252 rv
= aInputStream
->ReadSegments(AssignResponseToBuffer
, &mBodyBuffer
, aCount
,
254 NS_ENSURE_SUCCESS(rv
, rv
);
259 ViaductRequest::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
261 auto defer
= MakeScopeExit([&] {
266 if (NS_FAILED(aStatusCode
)) {
268 GetErrorName(aStatusCode
, errorName
);
269 nsPrintfCString
msg("Request error: %s", errorName
.get());
270 mResponse
.set_exception_message(msg
.BeginReading());
273 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
, &rv
);
274 // Early return is OK because MakeScopeExit will call Notify()
275 // and unblock the original calling thread.
276 NS_ENSURE_SUCCESS(rv
, rv
);
279 rv
= httpChannel
->GetResponseStatus(&httpStatus
);
280 NS_ENSURE_SUCCESS(rv
, rv
);
281 mResponse
.set_status(httpStatus
);
283 nsCOMPtr
<nsIURI
> uri
;
284 httpChannel
->GetURI(getter_AddRefs(uri
));
285 nsAutoCString uriStr
;
286 uri
->GetSpec(uriStr
);
287 mResponse
.set_url(uriStr
.BeginReading());
289 auto* headers
= mResponse
.mutable_headers();
290 nsCOMPtr
<nsIHttpHeaderVisitor
> visitor
= new HeaderVisitor(headers
);
291 rv
= httpChannel
->VisitResponseHeaders(visitor
);
292 NS_ENSURE_SUCCESS(rv
, rv
);
294 mResponse
.set_body(mBodyBuffer
.BeginReading(), mBodyBuffer
.Length());
300 ///////////////////////////////////////////////////////////////////////////////
301 // nsIChannelEventSink implementation
304 ViaductRequest::AsyncOnChannelRedirect(
305 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t flags
,
306 nsIAsyncVerifyRedirectCallback
* callback
) {
307 mChannel
= aNewChannel
;
308 callback
->OnRedirectVerifyCallback(NS_OK
);
312 ///////////////////////////////////////////////////////////////////////////////
313 // nsITimerCallback implementation
316 ViaductRequest::Notify(nsITimer
* timer
) {
318 // Cancelling the channel will trigger OnStopRequest.
320 mChannel
->Cancel(NS_ERROR_ABORT
);
326 ///////////////////////////////////////////////////////////////////////////////
327 // nsINamed implementation
330 ViaductRequest::GetName(nsACString
& aName
) {
331 aName
.AssignLiteral("ViaductRequest");
335 }; // namespace mozilla