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 
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "mozilla/net/FTPChannelParent.h"
9 #include "nsStringStream.h"
10 #include "mozilla/net/ChannelEventQueue.h"
11 #include "mozilla/dom/TabParent.h"
12 #include "nsFTPChannel.h"
13 #include "nsNetCID.h"
14 #include "nsNetUtil.h"
15 #include "nsQueryObject.h"
16 #include "nsFtpProtocolHandler.h"
17 #include "nsIAuthPrompt.h"
18 #include "nsIAuthPromptProvider.h"
19 #include "nsIEncodedChannel.h"
20 #include "nsIHttpChannelInternal.h"
21 #include "nsIForcePendingChannel.h"
22 #include "mozilla/ipc/InputStreamUtils.h"
23 #include "mozilla/ipc/URIUtils.h"
24 #include "mozilla/Unused.h"
25 #include "SerializedLoadContext.h"
26 #include "nsIContentPolicy.h"
27 #include "mozilla/ipc/BackgroundUtils.h"
28 #include "mozilla/LoadInfo.h"
29 
30 using namespace mozilla::dom;
31 using namespace mozilla::ipc;
32 
33 #undef LOG
34 #define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
35 
36 namespace mozilla {
37 namespace net {
38 
FTPChannelParent(const PBrowserOrId & aIframeEmbedding,nsILoadContext * aLoadContext,PBOverrideStatus aOverrideStatus)39 FTPChannelParent::FTPChannelParent(const PBrowserOrId& aIframeEmbedding,
40                                    nsILoadContext* aLoadContext,
41                                    PBOverrideStatus aOverrideStatus)
42   : mIPCClosed(false)
43   , mLoadContext(aLoadContext)
44   , mPBOverride(aOverrideStatus)
45   , mStatus(NS_OK)
46   , mDivertingFromChild(false)
47   , mDivertedOnStartRequest(false)
48   , mSuspendedForDiversion(false)
49   , mUseUTF8(false)
50 {
51   nsIProtocolHandler* handler;
52   CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
53   MOZ_ASSERT(handler, "no ftp handler");
54 
55   if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
56     mTabParent = static_cast<dom::TabParent*>(aIframeEmbedding.get_PBrowserParent());
57   }
58 
59   mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
60 }
61 
~FTPChannelParent()62 FTPChannelParent::~FTPChannelParent()
63 {
64   gFtpHandler->Release();
65 }
66 
67 void
ActorDestroy(ActorDestroyReason why)68 FTPChannelParent::ActorDestroy(ActorDestroyReason why)
69 {
70   // We may still have refcount>0 if the channel hasn't called OnStopRequest
71   // yet, but we must not send any more msgs to child.
72   mIPCClosed = true;
73 }
74 
75 //-----------------------------------------------------------------------------
76 // FTPChannelParent::nsISupports
77 //-----------------------------------------------------------------------------
78 
NS_IMPL_ISUPPORTS(FTPChannelParent,nsIStreamListener,nsIParentChannel,nsIInterfaceRequestor,nsIRequestObserver,nsIChannelEventSink,nsIFTPChannelParentInternal)79 NS_IMPL_ISUPPORTS(FTPChannelParent,
80                   nsIStreamListener,
81                   nsIParentChannel,
82                   nsIInterfaceRequestor,
83                   nsIRequestObserver,
84                   nsIChannelEventSink,
85                   nsIFTPChannelParentInternal)
86 
87 //-----------------------------------------------------------------------------
88 // FTPChannelParent::PFTPChannelParent
89 //-----------------------------------------------------------------------------
90 
91 //-----------------------------------------------------------------------------
92 // FTPChannelParent methods
93 //-----------------------------------------------------------------------------
94 
95 bool
96 FTPChannelParent::Init(const FTPChannelCreationArgs& aArgs)
97 {
98   switch (aArgs.type()) {
99   case FTPChannelCreationArgs::TFTPChannelOpenArgs:
100   {
101     const FTPChannelOpenArgs& a = aArgs.get_FTPChannelOpenArgs();
102     return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(),
103                        a.loadInfo());
104   }
105   case FTPChannelCreationArgs::TFTPChannelConnectArgs:
106   {
107     const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs();
108     return ConnectChannel(cArgs.channelId());
109   }
110   default:
111     NS_NOTREACHED("unknown open type");
112     return false;
113   }
114 }
115 
116 bool
DoAsyncOpen(const URIParams & aURI,const uint64_t & aStartPos,const nsCString & aEntityID,const OptionalInputStreamParams & aUploadStream,const OptionalLoadInfoArgs & aLoadInfoArgs)117 FTPChannelParent::DoAsyncOpen(const URIParams& aURI,
118                               const uint64_t& aStartPos,
119                               const nsCString& aEntityID,
120                               const OptionalInputStreamParams& aUploadStream,
121                               const OptionalLoadInfoArgs& aLoadInfoArgs)
122 {
123   nsresult rv;
124 
125   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
126   if (!uri)
127       return false;
128 
129 #ifdef DEBUG
130   LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n",
131        this, uri->GetSpecOrDefault().get()));
132 #endif
133 
134   nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
135   if (NS_FAILED(rv)) {
136     return SendFailedAsyncOpen(rv);
137   }
138 
139   nsCOMPtr<nsILoadInfo> loadInfo;
140   rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
141                                             getter_AddRefs(loadInfo));
142   if (NS_FAILED(rv)) {
143     return SendFailedAsyncOpen(rv);
144   }
145 
146   NeckoOriginAttributes attrs;
147   rv = loadInfo->GetOriginAttributes(&attrs);
148   if (NS_FAILED(rv)) {
149     return SendFailedAsyncOpen(rv);
150   }
151 
152   nsCOMPtr<nsIChannel> chan;
153   rv = NS_NewChannelInternal(getter_AddRefs(chan), uri, loadInfo,
154                              nullptr, nullptr,
155                              nsIRequest::LOAD_NORMAL, ios);
156 
157   if (NS_FAILED(rv))
158     return SendFailedAsyncOpen(rv);
159 
160   mChannel = chan;
161 
162   // later on mChannel may become an HTTP channel (we'll be redirected to one
163   // if we're using a proxy), but for now this is safe
164   nsFtpChannel* ftpChan = static_cast<nsFtpChannel*>(mChannel.get());
165 
166   if (mPBOverride != kPBOverride_Unset) {
167     ftpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
168   }
169   rv = ftpChan->SetNotificationCallbacks(this);
170   if (NS_FAILED(rv))
171     return SendFailedAsyncOpen(rv);
172 
173   nsTArray<mozilla::ipc::FileDescriptor> fds;
174   nsCOMPtr<nsIInputStream> upload = DeserializeInputStream(aUploadStream, fds);
175   if (upload) {
176     // contentType and contentLength are ignored
177     rv = ftpChan->SetUploadStream(upload, EmptyCString(), 0);
178     if (NS_FAILED(rv))
179       return SendFailedAsyncOpen(rv);
180   }
181 
182   rv = ftpChan->ResumeAt(aStartPos, aEntityID);
183   if (NS_FAILED(rv))
184     return SendFailedAsyncOpen(rv);
185 
186   if (loadInfo && loadInfo->GetEnforceSecurity()) {
187     rv = ftpChan->AsyncOpen2(this);
188   }
189   else {
190     rv = ftpChan->AsyncOpen(this, nullptr);
191   }
192 
193   if (NS_FAILED(rv))
194     return SendFailedAsyncOpen(rv);
195 
196   return true;
197 }
198 
199 bool
ConnectChannel(const uint32_t & channelId)200 FTPChannelParent::ConnectChannel(const uint32_t& channelId)
201 {
202   nsresult rv;
203 
204   LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId));
205 
206   nsCOMPtr<nsIChannel> channel;
207   rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
208   if (NS_SUCCEEDED(rv))
209     mChannel = channel;
210 
211   LOG(("  found channel %p, rv=%08x", mChannel.get(), rv));
212 
213   return true;
214 }
215 
216 bool
RecvCancel(const nsresult & status)217 FTPChannelParent::RecvCancel(const nsresult& status)
218 {
219   if (mChannel)
220     mChannel->Cancel(status);
221 
222   return true;
223 }
224 
225 bool
RecvSuspend()226 FTPChannelParent::RecvSuspend()
227 {
228   if (mChannel) {
229     SuspendChannel();
230   }
231   return true;
232 }
233 
234 bool
RecvResume()235 FTPChannelParent::RecvResume()
236 {
237   if (mChannel) {
238     ResumeChannel();
239   }
240   return true;
241 }
242 
243 class FTPDivertDataAvailableEvent : public ChannelEvent
244 {
245 public:
FTPDivertDataAvailableEvent(FTPChannelParent * aParent,const nsCString & data,const uint64_t & offset,const uint32_t & count)246   FTPDivertDataAvailableEvent(FTPChannelParent* aParent,
247                               const nsCString& data,
248                               const uint64_t& offset,
249                               const uint32_t& count)
250   : mParent(aParent)
251   , mData(data)
252   , mOffset(offset)
253   , mCount(count)
254   {
255   }
256 
Run()257   void Run()
258   {
259     mParent->DivertOnDataAvailable(mData, mOffset, mCount);
260   }
261 
262 private:
263   FTPChannelParent* mParent;
264   nsCString mData;
265   uint64_t mOffset;
266   uint32_t mCount;
267 };
268 
269 bool
RecvDivertOnDataAvailable(const nsCString & data,const uint64_t & offset,const uint32_t & count)270 FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
271                                             const uint64_t& offset,
272                                             const uint32_t& count)
273 {
274   if (NS_WARN_IF(!mDivertingFromChild)) {
275     MOZ_ASSERT(mDivertingFromChild,
276                "Cannot RecvDivertOnDataAvailable if diverting is not set!");
277     FailDiversion(NS_ERROR_UNEXPECTED);
278     return false;
279   }
280 
281   // Drop OnDataAvailables if the parent was canceled already.
282   if (NS_FAILED(mStatus)) {
283     return true;
284   }
285 
286   mEventQ->RunOrEnqueue(new FTPDivertDataAvailableEvent(this, data, offset,
287                                                         count));
288   return true;
289 }
290 
291 void
DivertOnDataAvailable(const nsCString & data,const uint64_t & offset,const uint32_t & count)292 FTPChannelParent::DivertOnDataAvailable(const nsCString& data,
293                                         const uint64_t& offset,
294                                         const uint32_t& count)
295 {
296   LOG(("FTPChannelParent::DivertOnDataAvailable [this=%p]\n", this));
297 
298   if (NS_WARN_IF(!mDivertingFromChild)) {
299     MOZ_ASSERT(mDivertingFromChild,
300                "Cannot DivertOnDataAvailable if diverting is not set!");
301     FailDiversion(NS_ERROR_UNEXPECTED);
302     return;
303   }
304 
305   // Drop OnDataAvailables if the parent was canceled already.
306   if (NS_FAILED(mStatus)) {
307     return;
308   }
309 
310   nsCOMPtr<nsIInputStream> stringStream;
311   nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
312                                       count, NS_ASSIGNMENT_DEPEND);
313   if (NS_FAILED(rv)) {
314     if (mChannel) {
315       mChannel->Cancel(rv);
316     }
317     mStatus = rv;
318     return;
319   }
320 
321   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
322 
323   rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
324 
325   stringStream->Close();
326   if (NS_FAILED(rv)) {
327     if (mChannel) {
328       mChannel->Cancel(rv);
329     }
330     mStatus = rv;
331   }
332 }
333 
334 class FTPDivertStopRequestEvent : public ChannelEvent
335 {
336 public:
FTPDivertStopRequestEvent(FTPChannelParent * aParent,const nsresult & statusCode)337   FTPDivertStopRequestEvent(FTPChannelParent* aParent,
338                             const nsresult& statusCode)
339   : mParent(aParent)
340   , mStatusCode(statusCode)
341   {
342   }
343 
Run()344   void Run() {
345     mParent->DivertOnStopRequest(mStatusCode);
346   }
347 
348 private:
349   FTPChannelParent* mParent;
350   nsresult mStatusCode;
351 };
352 
353 bool
RecvDivertOnStopRequest(const nsresult & statusCode)354 FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
355 {
356   if (NS_WARN_IF(!mDivertingFromChild)) {
357     MOZ_ASSERT(mDivertingFromChild,
358                "Cannot RecvDivertOnStopRequest if diverting is not set!");
359     FailDiversion(NS_ERROR_UNEXPECTED);
360     return false;
361   }
362 
363   mEventQ->RunOrEnqueue(new FTPDivertStopRequestEvent(this, statusCode));
364   return true;
365 }
366 
367 void
DivertOnStopRequest(const nsresult & statusCode)368 FTPChannelParent::DivertOnStopRequest(const nsresult& statusCode)
369 {
370   LOG(("FTPChannelParent::DivertOnStopRequest [this=%p]\n", this));
371 
372   if (NS_WARN_IF(!mDivertingFromChild)) {
373     MOZ_ASSERT(mDivertingFromChild,
374                "Cannot DivertOnStopRequest if diverting is not set!");
375     FailDiversion(NS_ERROR_UNEXPECTED);
376     return;
377   }
378 
379   // Honor the channel's status even if the underlying transaction completed.
380   nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
381 
382   // Reset fake pending status in case OnStopRequest has already been called.
383   if (mChannel) {
384     nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
385     if (forcePendingIChan) {
386       forcePendingIChan->ForcePending(false);
387     }
388   }
389 
390   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
391   OnStopRequest(mChannel, nullptr, status);
392 }
393 
394 class FTPDivertCompleteEvent : public ChannelEvent
395 {
396 public:
FTPDivertCompleteEvent(FTPChannelParent * aParent)397   explicit FTPDivertCompleteEvent(FTPChannelParent* aParent)
398   : mParent(aParent)
399   {
400   }
401 
Run()402   void Run() {
403     mParent->DivertComplete();
404   }
405 
406 private:
407   FTPChannelParent* mParent;
408 };
409 
410 bool
RecvDivertComplete()411 FTPChannelParent::RecvDivertComplete()
412 {
413   if (NS_WARN_IF(!mDivertingFromChild)) {
414     MOZ_ASSERT(mDivertingFromChild,
415                "Cannot RecvDivertComplete if diverting is not set!");
416     FailDiversion(NS_ERROR_UNEXPECTED);
417     return false;
418   }
419 
420   mEventQ->RunOrEnqueue(new FTPDivertCompleteEvent(this));
421   return true;
422 }
423 
424 void
DivertComplete()425 FTPChannelParent::DivertComplete()
426 {
427   LOG(("FTPChannelParent::DivertComplete [this=%p]\n", this));
428 
429   if (NS_WARN_IF(!mDivertingFromChild)) {
430     MOZ_ASSERT(mDivertingFromChild,
431                "Cannot DivertComplete if diverting is not set!");
432     FailDiversion(NS_ERROR_UNEXPECTED);
433     return;
434   }
435 
436   nsresult rv = ResumeForDiversion();
437   if (NS_WARN_IF(NS_FAILED(rv))) {
438     FailDiversion(NS_ERROR_UNEXPECTED);
439   }
440 }
441 
442 //-----------------------------------------------------------------------------
443 // FTPChannelParent::nsIRequestObserver
444 //-----------------------------------------------------------------------------
445 
446 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)447 FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
448 {
449   LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
450 
451   if (mDivertingFromChild) {
452     MOZ_RELEASE_ASSERT(mDivertToListener,
453                        "Cannot divert if listener is unset!");
454     return mDivertToListener->OnStartRequest(aRequest, aContext);
455   }
456 
457   nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
458   MOZ_ASSERT(chan);
459   NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
460 
461   int64_t contentLength;
462   chan->GetContentLength(&contentLength);
463   nsCString contentType;
464   chan->GetContentType(contentType);
465 
466   nsCString entityID;
467   nsCOMPtr<nsIResumableChannel> resChan = do_QueryInterface(aRequest);
468   MOZ_ASSERT(resChan); // both FTP and HTTP should implement nsIResumableChannel
469   if (resChan) {
470     resChan->GetEntityID(entityID);
471   }
472 
473   PRTime lastModified = 0;
474   nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(aRequest);
475   if (ftpChan) {
476     ftpChan->GetLastModifiedTime(&lastModified);
477   }
478   nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(aRequest);
479   if (httpChan) {
480     httpChan->GetLastModifiedTime(&lastModified);
481   }
482 
483   URIParams uriparam;
484   nsCOMPtr<nsIURI> uri;
485   chan->GetURI(getter_AddRefs(uri));
486   SerializeURI(uri, uriparam);
487 
488   if (mIPCClosed || !SendOnStartRequest(mStatus, contentLength, contentType,
489                                         lastModified, entityID, uriparam)) {
490     return NS_ERROR_UNEXPECTED;
491   }
492 
493   return NS_OK;
494 }
495 
496 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatusCode)497 FTPChannelParent::OnStopRequest(nsIRequest* aRequest,
498                                 nsISupports* aContext,
499                                 nsresult aStatusCode)
500 {
501   LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n",
502        this, aStatusCode));
503 
504   if (mDivertingFromChild) {
505     MOZ_RELEASE_ASSERT(mDivertToListener,
506                        "Cannot divert if listener is unset!");
507     return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode);
508   }
509 
510   if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) {
511     return NS_ERROR_UNEXPECTED;
512   }
513 
514   return NS_OK;
515 }
516 
517 //-----------------------------------------------------------------------------
518 // FTPChannelParent::nsIStreamListener
519 //-----------------------------------------------------------------------------
520 
521 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)522 FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
523                                   nsISupports* aContext,
524                                   nsIInputStream* aInputStream,
525                                   uint64_t aOffset,
526                                   uint32_t aCount)
527 {
528   LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this));
529 
530   if (mDivertingFromChild) {
531     MOZ_RELEASE_ASSERT(mDivertToListener,
532                        "Cannot divert if listener is unset!");
533     return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream,
534                                               aOffset, aCount);
535   }
536 
537   nsCString data;
538   nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
539   if (NS_FAILED(rv))
540     return rv;
541 
542   if (mIPCClosed || !SendOnDataAvailable(mStatus, data, aOffset, aCount))
543     return NS_ERROR_UNEXPECTED;
544 
545   return NS_OK;
546 }
547 
548 //-----------------------------------------------------------------------------
549 // FTPChannelParent::nsIParentChannel
550 //-----------------------------------------------------------------------------
551 
552 NS_IMETHODIMP
SetParentListener(HttpChannelParentListener * aListener)553 FTPChannelParent::SetParentListener(HttpChannelParentListener* aListener)
554 {
555   // Do not need ptr to HttpChannelParentListener.
556   return NS_OK;
557 }
558 
559 NS_IMETHODIMP
NotifyTrackingProtectionDisabled()560 FTPChannelParent::NotifyTrackingProtectionDisabled()
561 {
562   // One day, this should probably be filled in.
563   return NS_OK;
564 }
565 
566 NS_IMETHODIMP
Delete()567 FTPChannelParent::Delete()
568 {
569   if (mIPCClosed || !SendDeleteSelf())
570     return NS_ERROR_UNEXPECTED;
571 
572   return NS_OK;
573 }
574 
575 //-----------------------------------------------------------------------------
576 // FTPChannelParent::nsIInterfaceRequestor
577 //-----------------------------------------------------------------------------
578 
579 NS_IMETHODIMP
GetInterface(const nsIID & uuid,void ** result)580 FTPChannelParent::GetInterface(const nsIID& uuid, void** result)
581 {
582   if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
583       uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) {
584     if (mTabParent) {
585       return mTabParent->QueryInterface(uuid, result);
586     }
587   } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) ||
588              uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) {
589     nsCOMPtr<nsIAuthPromptProvider> provider(do_QueryObject(mTabParent));
590     if (provider) {
591       return provider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL,
592                                      uuid,
593                                      result);
594     }
595   }
596 
597   // Only support nsILoadContext if child channel's callbacks did too
598   if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
599     nsCOMPtr<nsILoadContext> copy = mLoadContext;
600     copy.forget(result);
601     return NS_OK;
602   }
603 
604   return QueryInterface(uuid, result);
605 }
606 
607 nsresult
SuspendChannel()608 FTPChannelParent::SuspendChannel()
609 {
610   nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
611     do_QueryInterface(mChannel);
612   if (chan) {
613     return chan->SuspendInternal();
614   } else {
615     return mChannel->Suspend();
616   }
617 }
618 
619 nsresult
ResumeChannel()620 FTPChannelParent::ResumeChannel()
621 {
622   nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
623     do_QueryInterface(mChannel);
624   if (chan) {
625     return chan->ResumeInternal();
626   } else {
627     return mChannel->Resume();
628   }
629 }
630 
631 //-----------------------------------------------------------------------------
632 // FTPChannelParent::ADivertableParentChannel
633 //-----------------------------------------------------------------------------
634 nsresult
SuspendForDiversion()635 FTPChannelParent::SuspendForDiversion()
636 {
637   MOZ_ASSERT(mChannel);
638   if (NS_WARN_IF(mDivertingFromChild)) {
639     MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
640     return NS_ERROR_UNEXPECTED;
641   }
642 
643   // Try suspending the channel. Allow it to fail, since OnStopRequest may have
644   // been called and thus the channel may not be pending.
645   nsresult rv = SuspendChannel();
646   MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
647   mSuspendedForDiversion = NS_SUCCEEDED(rv);
648 
649   // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
650   // to the child.
651   mDivertingFromChild = true;
652 
653   nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
654     do_QueryInterface(mChannel);
655   if (chan) {
656     chan->MessageDiversionStarted(this);
657   }
658 
659   return NS_OK;
660 }
661 
662 /* private, supporting function for ADivertableParentChannel */
663 nsresult
ResumeForDiversion()664 FTPChannelParent::ResumeForDiversion()
665 {
666   MOZ_ASSERT(mChannel);
667   MOZ_ASSERT(mDivertToListener);
668   if (NS_WARN_IF(!mDivertingFromChild)) {
669     MOZ_ASSERT(mDivertingFromChild,
670                "Cannot ResumeForDiversion if not diverting!");
671     return NS_ERROR_UNEXPECTED;
672   }
673 
674   nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
675     do_QueryInterface(mChannel);
676   if (chan) {
677     chan->MessageDiversionStop();
678   }
679 
680   if (mSuspendedForDiversion) {
681     nsresult rv = ResumeChannel();
682     if (NS_WARN_IF(NS_FAILED(rv))) {
683       FailDiversion(NS_ERROR_UNEXPECTED, true);
684       return rv;
685     }
686     mSuspendedForDiversion = false;
687   }
688 
689   // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will
690   // keep us alive if there's more data to be delivered to listener.
691   if (NS_WARN_IF(NS_FAILED(Delete()))) {
692     FailDiversion(NS_ERROR_UNEXPECTED);
693     return NS_ERROR_UNEXPECTED;
694   }
695   return NS_OK;
696 }
697 
698 nsresult
SuspendMessageDiversion()699 FTPChannelParent::SuspendMessageDiversion()
700 {
701   // This only need to suspend message queue.
702   mEventQ->Suspend();
703   return NS_OK;
704 }
705 
706 nsresult
ResumeMessageDiversion()707 FTPChannelParent::ResumeMessageDiversion()
708 {
709   // This only need to resumes message queue.
710   mEventQ->Resume();
711   return NS_OK;
712 }
713 
714 void
DivertTo(nsIStreamListener * aListener)715 FTPChannelParent::DivertTo(nsIStreamListener *aListener)
716 {
717   MOZ_ASSERT(aListener);
718   if (NS_WARN_IF(!mDivertingFromChild)) {
719     MOZ_ASSERT(mDivertingFromChild,
720                "Cannot DivertTo new listener if diverting is not set!");
721     return;
722   }
723 
724   if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
725     FailDiversion(NS_ERROR_UNEXPECTED);
726     return;
727   }
728 
729   mDivertToListener = aListener;
730 
731   // Call OnStartRequest and SendDivertMessages asynchronously to avoid
732   // reentering client context.
733   NS_DispatchToCurrentThread(
734     NewRunnableMethod(this, &FTPChannelParent::StartDiversion));
735   return;
736 }
737 
738 void
StartDiversion()739 FTPChannelParent::StartDiversion()
740 {
741   if (NS_WARN_IF(!mDivertingFromChild)) {
742     MOZ_ASSERT(mDivertingFromChild,
743                "Cannot StartDiversion if diverting is not set!");
744     return;
745   }
746 
747   // Fake pending status in case OnStopRequest has already been called.
748   if (mChannel) {
749     nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
750     if (forcePendingIChan) {
751       forcePendingIChan->ForcePending(true);
752     }
753   }
754 
755   {
756     AutoEventEnqueuer ensureSerialDispatch(mEventQ);
757     // Call OnStartRequest for the "DivertTo" listener.
758     nsresult rv = OnStartRequest(mChannel, nullptr);
759     if (NS_FAILED(rv)) {
760       if (mChannel) {
761         mChannel->Cancel(rv);
762       }
763       mStatus = rv;
764       return;
765     }
766   }
767 
768   // After OnStartRequest has been called, tell FTPChannelChild to divert the
769   // OnDataAvailables and OnStopRequest to this FTPChannelParent.
770   if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
771     FailDiversion(NS_ERROR_UNEXPECTED);
772     return;
773   }
774 }
775 
776 class FTPFailDiversionEvent : public Runnable
777 {
778 public:
FTPFailDiversionEvent(FTPChannelParent * aChannelParent,nsresult aErrorCode,bool aSkipResume)779   FTPFailDiversionEvent(FTPChannelParent *aChannelParent,
780                         nsresult aErrorCode,
781                         bool aSkipResume)
782     : mChannelParent(aChannelParent)
783     , mErrorCode(aErrorCode)
784     , mSkipResume(aSkipResume)
785   {
786     MOZ_RELEASE_ASSERT(aChannelParent);
787     MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
788   }
Run()789   NS_IMETHOD Run() override
790   {
791     mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
792     return NS_OK;
793   }
794 private:
795   RefPtr<FTPChannelParent> mChannelParent;
796   nsresult mErrorCode;
797   bool mSkipResume;
798 };
799 
800 void
FailDiversion(nsresult aErrorCode,bool aSkipResume)801 FTPChannelParent::FailDiversion(nsresult aErrorCode,
802                                             bool aSkipResume)
803 {
804   MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
805   MOZ_RELEASE_ASSERT(mDivertingFromChild);
806   MOZ_RELEASE_ASSERT(mDivertToListener);
807   MOZ_RELEASE_ASSERT(mChannel);
808 
809   NS_DispatchToCurrentThread(
810     new FTPFailDiversionEvent(this, aErrorCode, aSkipResume));
811 }
812 
813 void
NotifyDiversionFailed(nsresult aErrorCode,bool aSkipResume)814 FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
815                                         bool aSkipResume)
816 {
817   MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
818   MOZ_RELEASE_ASSERT(mDivertingFromChild);
819   MOZ_RELEASE_ASSERT(mDivertToListener);
820   MOZ_RELEASE_ASSERT(mChannel);
821 
822   mChannel->Cancel(aErrorCode);
823   nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
824   if (forcePendingIChan) {
825     forcePendingIChan->ForcePending(false);
826   }
827 
828   bool isPending = false;
829   nsresult rv = mChannel->IsPending(&isPending);
830   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
831 
832   // Resume only we suspended earlier.
833   if (mSuspendedForDiversion) {
834     ResumeChannel();
835   }
836   // Channel has already sent OnStartRequest to the child, so ensure that we
837   // call it here if it hasn't already been called.
838   if (!mDivertedOnStartRequest) {
839     nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
840     if (forcePendingIChan) {
841       forcePendingIChan->ForcePending(true);
842     }
843     mDivertToListener->OnStartRequest(mChannel, nullptr);
844 
845     if (forcePendingIChan) {
846       forcePendingIChan->ForcePending(false);
847     }
848   }
849   // If the channel is pending, it will call OnStopRequest itself; otherwise, do
850   // it here.
851   if (!isPending) {
852     mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode);
853   }
854   mDivertToListener = nullptr;
855   mChannel = nullptr;
856 
857   if (!mIPCClosed) {
858     Unused << SendDeleteSelf();
859   }
860 }
861 
862 //-----------------------------------------------------------------------------
863 // FTPChannelParent::nsIChannelEventSink
864 //-----------------------------------------------------------------------------
865 
866 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * oldChannel,nsIChannel * newChannel,uint32_t redirectFlags,nsIAsyncVerifyRedirectCallback * callback)867 FTPChannelParent::AsyncOnChannelRedirect(
868                             nsIChannel *oldChannel,
869                             nsIChannel *newChannel,
870                             uint32_t redirectFlags,
871                             nsIAsyncVerifyRedirectCallback* callback)
872 {
873   nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(newChannel);
874   if (!ftpChan) {
875     // when FTP is set to use HTTP proxying, we wind up getting redirected to an HTTP channel.
876     nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(newChannel);
877     if (!httpChan)
878       return NS_ERROR_UNEXPECTED;
879   }
880   mChannel = newChannel;
881   callback->OnRedirectVerifyCallback(NS_OK);
882   return NS_OK;
883 }
884 
885 NS_IMETHODIMP
SetErrorMsg(const char * aMsg,bool aUseUTF8)886 FTPChannelParent::SetErrorMsg(const char *aMsg, bool aUseUTF8)
887 {
888   mErrorMsg = aMsg;
889   mUseUTF8 = aUseUTF8;
890   return NS_OK;
891 }
892 
893 //---------------------
894 } // namespace net
895 } // namespace mozilla
896 
897