1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
9
10 #include "HttpChannelParentListener.h"
11 #include "mozilla/dom/ServiceWorkerInterceptController.h"
12 #include "mozilla/dom/ServiceWorkerUtils.h"
13 #include "mozilla/net/HttpChannelParent.h"
14 #include "mozilla/Unused.h"
15 #include "nsIAuthPrompt.h"
16 #include "nsIAuthPrompt2.h"
17 #include "nsIHttpHeaderVisitor.h"
18 #include "nsIRedirectChannelRegistrar.h"
19 #include "nsIPromptFactory.h"
20 #include "nsIWindowWatcher.h"
21 #include "nsQueryObject.h"
22
23 using mozilla::Unused;
24 using mozilla::dom::ServiceWorkerInterceptController;
25 using mozilla::dom::ServiceWorkerParentInterceptEnabled;
26
27 namespace mozilla {
28 namespace net {
29
HttpChannelParentListener(HttpChannelParent * aInitialChannel)30 HttpChannelParentListener::HttpChannelParentListener(
31 HttpChannelParent* aInitialChannel)
32 : mNextListener(aInitialChannel),
33 mRedirectChannelId(0),
34 mSuspendedForDiversion(false),
35 mShouldIntercept(false),
36 mShouldSuspendIntercept(false),
37 mInterceptCanceled(false) {
38 LOG((
39 "HttpChannelParentListener::HttpChannelParentListener [this=%p, next=%p]",
40 this, aInitialChannel));
41
42 if (ServiceWorkerParentInterceptEnabled()) {
43 mInterceptController = new ServiceWorkerInterceptController();
44 }
45 }
46
~HttpChannelParentListener()47 HttpChannelParentListener::~HttpChannelParentListener() {}
48
49 //-----------------------------------------------------------------------------
50 // HttpChannelParentListener::nsISupports
51 //-----------------------------------------------------------------------------
52
53 NS_IMPL_ADDREF(HttpChannelParentListener)
54 NS_IMPL_RELEASE(HttpChannelParentListener)
55 NS_INTERFACE_MAP_BEGIN(HttpChannelParentListener)
56 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
57 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
58 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
59 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
60 NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
61 NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
62 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
63 if (aIID.Equals(NS_GET_IID(HttpChannelParentListener))) {
64 foundInterface = static_cast<nsIInterfaceRequestor*>(this);
65 } else
66 NS_INTERFACE_MAP_END
67
68 //-----------------------------------------------------------------------------
69 // HttpChannelParentListener::nsIRequestObserver
70 //-----------------------------------------------------------------------------
71
72 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)73 HttpChannelParentListener::OnStartRequest(nsIRequest* aRequest,
74 nsISupports* aContext) {
75 MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
76 "Cannot call OnStartRequest if suspended for diversion!");
77
78 if (!mNextListener) return NS_ERROR_UNEXPECTED;
79
80 LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
81 return mNextListener->OnStartRequest(aRequest, aContext);
82 }
83
84 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatusCode)85 HttpChannelParentListener::OnStopRequest(nsIRequest* aRequest,
86 nsISupports* aContext,
87 nsresult aStatusCode) {
88 MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
89 "Cannot call OnStopRequest if suspended for diversion!");
90
91 if (!mNextListener) return NS_ERROR_UNEXPECTED;
92
93 LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%" PRIu32
94 "]\n",
95 this, static_cast<uint32_t>(aStatusCode)));
96 nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
97
98 mNextListener = nullptr;
99 return rv;
100 }
101
102 //-----------------------------------------------------------------------------
103 // HttpChannelParentListener::nsIStreamListener
104 //-----------------------------------------------------------------------------
105
106 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)107 HttpChannelParentListener::OnDataAvailable(nsIRequest* aRequest,
108 nsISupports* aContext,
109 nsIInputStream* aInputStream,
110 uint64_t aOffset, uint32_t aCount) {
111 MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
112 "Cannot call OnDataAvailable if suspended for diversion!");
113
114 if (!mNextListener) return NS_ERROR_UNEXPECTED;
115
116 LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
117 return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream,
118 aOffset, aCount);
119 }
120
121 //-----------------------------------------------------------------------------
122 // HttpChannelParentListener::nsIInterfaceRequestor
123 //-----------------------------------------------------------------------------
124
125 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** result)126 HttpChannelParentListener::GetInterface(const nsIID& aIID, void** result) {
127 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
128 aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) ||
129 aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
130 return QueryInterface(aIID, result);
131 }
132
133 nsCOMPtr<nsIInterfaceRequestor> ir;
134 if (mNextListener && NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
135 getter_AddRefs(ir)))) {
136 return ir->GetInterface(aIID, result);
137 }
138
139 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
140 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
141 nsresult rv;
142 nsCOMPtr<nsIPromptFactory> wwatch =
143 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
144 NS_ENSURE_SUCCESS(rv, rv);
145
146 return wwatch->GetPrompt(nullptr, aIID, reinterpret_cast<void**>(result));
147 }
148
149 return NS_NOINTERFACE;
150 }
151
152 //-----------------------------------------------------------------------------
153 // HttpChannelParentListener::nsIChannelEventSink
154 //-----------------------------------------------------------------------------
155
156 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * oldChannel,nsIChannel * newChannel,uint32_t redirectFlags,nsIAsyncVerifyRedirectCallback * callback)157 HttpChannelParentListener::AsyncOnChannelRedirect(
158 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t redirectFlags,
159 nsIAsyncVerifyRedirectCallback* callback) {
160 LOG(
161 ("HttpChannelParentListener::AsyncOnChannelRedirect [this=%p, old=%p, "
162 "new=%p, flags=%u]",
163 this, oldChannel, newChannel, redirectFlags));
164
165 nsresult rv;
166
167 nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
168 do_QueryInterface(mNextListener);
169 if (!activeRedirectingChannel) {
170 NS_ERROR(
171 "Channel got a redirect response, but doesn't implement "
172 "nsIParentRedirectingChannel to handle it.");
173 return NS_ERROR_NOT_IMPLEMENTED;
174 }
175
176 // Register the new channel and obtain id for it
177 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
178 do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
179 NS_ENSURE_SUCCESS(rv, rv);
180
181 rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
182 NS_ENSURE_SUCCESS(rv, rv);
183
184 LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
185
186 return activeRedirectingChannel->StartRedirect(mRedirectChannelId, newChannel,
187 redirectFlags, callback);
188 }
189
190 //-----------------------------------------------------------------------------
191 // HttpChannelParentListener::nsIRedirectResultListener
192 //-----------------------------------------------------------------------------
193
194 NS_IMETHODIMP
OnRedirectResult(bool succeeded)195 HttpChannelParentListener::OnRedirectResult(bool succeeded) {
196 LOG(("HttpChannelParentListener::OnRedirectResult [this=%p, suc=%d]", this,
197 succeeded));
198
199 nsresult rv;
200
201 nsCOMPtr<nsIParentChannel> redirectChannel;
202 if (mRedirectChannelId) {
203 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
204 do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
205 NS_ENSURE_SUCCESS(rv, rv);
206
207 rv = registrar->GetParentChannel(mRedirectChannelId,
208 getter_AddRefs(redirectChannel));
209 if (NS_FAILED(rv) || !redirectChannel) {
210 // Redirect might get canceled before we got AsyncOnChannelRedirect
211 LOG(("Registered parent channel not found under id=%d",
212 mRedirectChannelId));
213
214 nsCOMPtr<nsIChannel> newChannel;
215 rv = registrar->GetRegisteredChannel(mRedirectChannelId,
216 getter_AddRefs(newChannel));
217 MOZ_ASSERT(newChannel, "Already registered channel not found");
218
219 if (NS_SUCCEEDED(rv)) newChannel->Cancel(NS_BINDING_ABORTED);
220 }
221
222 // Release all previously registered channels, they are no longer need to be
223 // kept in the registrar from this moment.
224 registrar->DeregisterChannels(mRedirectChannelId);
225
226 mRedirectChannelId = 0;
227 }
228
229 if (!redirectChannel) {
230 succeeded = false;
231 }
232
233 nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
234 do_QueryInterface(mNextListener);
235 MOZ_ASSERT(activeRedirectingChannel,
236 "Channel finished a redirect response, but doesn't implement "
237 "nsIParentRedirectingChannel to complete it.");
238
239 if (activeRedirectingChannel) {
240 activeRedirectingChannel->CompleteRedirect(succeeded);
241 } else {
242 succeeded = false;
243 }
244
245 if (succeeded) {
246 // Switch to redirect channel and delete the old one. Only do this
247 // if we are actually changing channels. During a service worker
248 // interception internal redirect we preserve the same HttpChannelParent.
249 if (!SameCOMIdentity(redirectChannel, mNextListener)) {
250 nsCOMPtr<nsIParentChannel> parent;
251 parent = do_QueryInterface(mNextListener);
252 MOZ_ASSERT(parent);
253 parent->Delete();
254 mInterceptCanceled = false;
255 mNextListener = do_QueryInterface(redirectChannel);
256 MOZ_ASSERT(mNextListener);
257 redirectChannel->SetParentListener(this);
258 }
259 } else if (redirectChannel) {
260 // Delete the redirect target channel: continue using old channel
261 redirectChannel->Delete();
262 }
263
264 return NS_OK;
265 }
266
267 //-----------------------------------------------------------------------------
268 // HttpChannelParentListener::nsINetworkInterceptController
269 //-----------------------------------------------------------------------------
270
271 NS_IMETHODIMP
ShouldPrepareForIntercept(nsIURI * aURI,nsIChannel * aChannel,bool * aShouldIntercept)272 HttpChannelParentListener::ShouldPrepareForIntercept(nsIURI* aURI,
273 nsIChannel* aChannel,
274 bool* aShouldIntercept) {
275 // If parent-side interception is enabled just forward to the real
276 // network controler.
277 if (mInterceptController) {
278 return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
279 aShouldIntercept);
280 }
281 *aShouldIntercept = mShouldIntercept;
282 return NS_OK;
283 }
284
285 class HeaderVisitor final : public nsIHttpHeaderVisitor {
286 nsCOMPtr<nsIInterceptedChannel> mChannel;
~HeaderVisitor()287 ~HeaderVisitor() {}
288
289 public:
HeaderVisitor(nsIInterceptedChannel * aChannel)290 explicit HeaderVisitor(nsIInterceptedChannel* aChannel)
291 : mChannel(aChannel) {}
292
293 NS_DECL_ISUPPORTS
294
VisitHeader(const nsACString & aHeader,const nsACString & aValue)295 NS_IMETHOD VisitHeader(const nsACString& aHeader,
296 const nsACString& aValue) override {
297 mChannel->SynthesizeHeader(aHeader, aValue);
298 return NS_OK;
299 }
300 };
301
302 NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
303
304 class FinishSynthesizedResponse : public Runnable {
305 nsCOMPtr<nsIInterceptedChannel> mChannel;
306
307 public:
FinishSynthesizedResponse(nsIInterceptedChannel * aChannel)308 explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel)
309 : Runnable("net::FinishSynthesizedResponse"), mChannel(aChannel) {}
310
Run()311 NS_IMETHOD Run() override {
312 // The URL passed as an argument here doesn't matter, since the child will
313 // receive a redirection notification as a result of this synthesized
314 // response.
315 mChannel->StartSynthesizedResponse(nullptr, nullptr, nullptr,
316 EmptyCString(), false);
317 mChannel->FinishSynthesizedResponse();
318 return NS_OK;
319 }
320 };
321
322 NS_IMETHODIMP
ChannelIntercepted(nsIInterceptedChannel * aChannel)323 HttpChannelParentListener::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
324 // If parent-side interception is enabled just forward to the real
325 // network controler.
326 if (mInterceptController) {
327 return mInterceptController->ChannelIntercepted(aChannel);
328 }
329
330 // Its possible for the child-side interception to complete and tear down
331 // the actor before we even get this parent-side interception notification.
332 // In this case we want to let the interception succeed, but then immediately
333 // cancel it. If we return an error code from here then it might get
334 // propagated back to the child process where the interception did not
335 // encounter an error. Therefore cancel the new channel asynchronously from a
336 // runnable.
337 if (mInterceptCanceled) {
338 nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsresult>(
339 "HttpChannelParentListener::CancelInterception", aChannel,
340 &nsIInterceptedChannel::CancelInterception, NS_BINDING_ABORTED);
341 MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
342 return NS_OK;
343 }
344
345 if (mShouldSuspendIntercept) {
346 mInterceptedChannel = aChannel;
347 return NS_OK;
348 }
349
350 nsAutoCString statusText;
351 mSynthesizedResponseHead->StatusText(statusText);
352 aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), statusText);
353 nsCOMPtr<nsIHttpHeaderVisitor> visitor = new HeaderVisitor(aChannel);
354 DebugOnly<nsresult> rv = mSynthesizedResponseHead->VisitHeaders(
355 visitor, nsHttpHeaderArray::eFilterResponse);
356 MOZ_ASSERT(NS_SUCCEEDED(rv));
357
358 nsCOMPtr<nsIRunnable> event = new FinishSynthesizedResponse(aChannel);
359 NS_DispatchToCurrentThread(event);
360
361 mSynthesizedResponseHead = nullptr;
362
363 MOZ_ASSERT(mNextListener);
364 RefPtr<HttpChannelParent> channel = do_QueryObject(mNextListener);
365 MOZ_ASSERT(channel);
366 channel->ResponseSynthesized();
367
368 return NS_OK;
369 }
370
371 //-----------------------------------------------------------------------------
372
SuspendForDiversion()373 nsresult HttpChannelParentListener::SuspendForDiversion() {
374 if (NS_WARN_IF(mSuspendedForDiversion)) {
375 MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
376 return NS_ERROR_UNEXPECTED;
377 }
378
379 // While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
380 // to mNextListener.
381 mSuspendedForDiversion = true;
382
383 return NS_OK;
384 }
385
ResumeForDiversion()386 nsresult HttpChannelParentListener::ResumeForDiversion() {
387 MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
388
389 // Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
390 mSuspendedForDiversion = false;
391
392 return NS_OK;
393 }
394
DivertTo(nsIStreamListener * aListener)395 nsresult HttpChannelParentListener::DivertTo(nsIStreamListener* aListener) {
396 MOZ_ASSERT(aListener);
397 MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
398
399 // Reset mInterceptCanceled back to false every time a new listener is set.
400 // We only want to cancel the interception if our current listener has
401 // signaled its cleaning up.
402 mInterceptCanceled = false;
403
404 mNextListener = aListener;
405
406 return ResumeForDiversion();
407 }
408
SetupInterception(const nsHttpResponseHead & aResponseHead)409 void HttpChannelParentListener::SetupInterception(
410 const nsHttpResponseHead& aResponseHead) {
411 mSynthesizedResponseHead = new nsHttpResponseHead(aResponseHead);
412 mShouldIntercept = true;
413 }
414
SetupInterceptionAfterRedirect(bool aShouldIntercept)415 void HttpChannelParentListener::SetupInterceptionAfterRedirect(
416 bool aShouldIntercept) {
417 mShouldIntercept = aShouldIntercept;
418 if (mShouldIntercept) {
419 // When an interception occurs, this channel should suspend all further
420 // activity. It will be torn down and recreated if necessary.
421 mShouldSuspendIntercept = true;
422 }
423 }
424
ClearInterceptedChannel(nsIStreamListener * aListener)425 void HttpChannelParentListener::ClearInterceptedChannel(
426 nsIStreamListener* aListener) {
427 // Only cancel the interception if this is from our current listener. We
428 // can get spurious calls here from other HttpChannelParent instances being
429 // destroyed asynchronously.
430 if (!SameCOMIdentity(mNextListener, aListener)) {
431 return;
432 }
433 if (mInterceptedChannel) {
434 mInterceptedChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
435 mInterceptedChannel = nullptr;
436 }
437 // Note that channel interception has been canceled. If we got this before
438 // the interception even occured we will trigger the cancel later.
439 mInterceptCanceled = true;
440 }
441
442 } // namespace net
443 } // namespace mozilla
444