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