1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
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 #include "nsURILoader.h"
8 #include "nsIURIContentListener.h"
9 #include "nsIContentHandler.h"
10 #include "nsILoadGroup.h"
11 #include "nsIDocumentLoader.h"
12 #include "nsIStreamListener.h"
13 #include "nsIURI.h"
14 #include "nsIChannel.h"
15 #include "nsIInterfaceRequestor.h"
16 #include "nsIInterfaceRequestorUtils.h"
17 #include "nsIInputStream.h"
18 #include "nsIStreamConverterService.h"
19 #include "nsIWeakReferenceUtils.h"
20 #include "nsIHttpChannel.h"
21 #include "netCore.h"
22 #include "nsCRT.h"
23 #include "nsIDocShell.h"
24 #include "nsIThreadRetargetableStreamListener.h"
25 #include "nsIChildChannel.h"
26 #include "nsExternalHelperAppService.h"
27 
28 #include "nsString.h"
29 #include "nsThreadUtils.h"
30 #include "nsReadableUtils.h"
31 #include "nsError.h"
32 
33 #include "nsICategoryManager.h"
34 #include "nsCExternalHandlerService.h"
35 
36 #include "nsNetCID.h"
37 
38 #include "nsMimeTypes.h"
39 
40 #include "nsDocLoader.h"
41 #include "mozilla/Attributes.h"
42 #include "mozilla/IntegerPrintfMacros.h"
43 #include "mozilla/Preferences.h"
44 #include "mozilla/Unused.h"
45 #include "mozilla/StaticPrefs_dom.h"
46 #include "mozilla/StaticPrefs_general.h"
47 #include "nsContentUtils.h"
48 
49 mozilla::LazyLogModule nsURILoader::mLog("URILoader");
50 
51 #define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args)
52 #define LOG_ERROR(args) \
53   MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args)
54 #define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug)
55 
56 NS_IMPL_ADDREF(nsDocumentOpenInfo)
NS_IMPL_RELEASE(nsDocumentOpenInfo)57 NS_IMPL_RELEASE(nsDocumentOpenInfo)
58 
59 NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
60   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
61   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
62   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
63   NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
64 NS_INTERFACE_MAP_END
65 
66 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
67                                        uint32_t aFlags, nsURILoader* aURILoader)
68     : m_originalContext(aWindowContext),
69       mFlags(aFlags),
70       mURILoader(aURILoader),
71       mDataConversionDepthLimit(
72           StaticPrefs::general_document_open_conversion_depth_limit()) {}
73 
nsDocumentOpenInfo(uint32_t aFlags,bool aAllowListenerConversions)74 nsDocumentOpenInfo::nsDocumentOpenInfo(uint32_t aFlags,
75                                        bool aAllowListenerConversions)
76     : m_originalContext(nullptr),
77       mFlags(aFlags),
78       mURILoader(nullptr),
79       mDataConversionDepthLimit(
80           StaticPrefs::general_document_open_conversion_depth_limit()),
81       mAllowListenerConversions(aAllowListenerConversions) {}
82 
~nsDocumentOpenInfo()83 nsDocumentOpenInfo::~nsDocumentOpenInfo() {}
84 
Prepare()85 nsresult nsDocumentOpenInfo::Prepare() {
86   LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
87 
88   nsresult rv;
89 
90   // ask our window context if it has a uri content listener...
91   m_contentListener = do_GetInterface(m_originalContext, &rv);
92   return rv;
93 }
94 
OnStartRequest(nsIRequest * request)95 NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest* request) {
96   LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
97   MOZ_ASSERT(request);
98   if (!request) {
99     return NS_ERROR_UNEXPECTED;
100   }
101 
102   nsresult rv = NS_OK;
103 
104   //
105   // Deal with "special" HTTP responses:
106   //
107   // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
108   //   not try to find a content handler.  Return NS_BINDING_ABORTED to cancel
109   //   the request.  This has the effect of ensuring that the DocLoader does
110   //   not try to interpret this as a real request.
111   //
112   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
113 
114   if (NS_SUCCEEDED(rv)) {
115     uint32_t responseCode = 0;
116 
117     rv = httpChannel->GetResponseStatus(&responseCode);
118 
119     if (NS_FAILED(rv)) {
120       LOG_ERROR(("  Failed to get HTTP response status"));
121 
122       // behave as in the canceled case
123       return NS_OK;
124     }
125 
126     LOG(("  HTTP response status: %d", responseCode));
127 
128     if (204 == responseCode || 205 == responseCode) {
129       return NS_BINDING_ABORTED;
130     }
131   }
132 
133   //
134   // Make sure that the transaction has succeeded, so far...
135   //
136   nsresult status;
137 
138   rv = request->GetStatus(&status);
139 
140   NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
141   if (NS_FAILED(rv)) return rv;
142 
143   if (NS_FAILED(status)) {
144     LOG_ERROR(("  Request failed, status: 0x%08" PRIX32,
145                static_cast<uint32_t>(status)));
146 
147     //
148     // The transaction has already reported an error - so it will be torn
149     // down. Therefore, it is not necessary to return an error code...
150     //
151     return NS_OK;
152   }
153 
154   rv = DispatchContent(request, nullptr);
155 
156   LOG(("  After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32,
157        m_targetStreamListener.get(), static_cast<uint32_t>(rv)));
158 
159   NS_ASSERTION(
160       NS_SUCCEEDED(rv) || !m_targetStreamListener,
161       "Must not have an m_targetStreamListener with a failure return!");
162 
163   NS_ENSURE_SUCCESS(rv, rv);
164 
165   if (m_targetStreamListener)
166     rv = m_targetStreamListener->OnStartRequest(request);
167 
168   LOG(("  OnStartRequest returning: 0x%08" PRIX32, static_cast<uint32_t>(rv)));
169 
170   return rv;
171 }
172 
173 NS_IMETHODIMP
CheckListenerChain()174 nsDocumentOpenInfo::CheckListenerChain() {
175   NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
176   nsresult rv = NS_OK;
177   nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
178       do_QueryInterface(m_targetStreamListener, &rv);
179   if (retargetableListener) {
180     rv = retargetableListener->CheckListenerChain();
181   }
182   LOG(
183       ("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv "
184        "%" PRIx32,
185        this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
186        (nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv)));
187   return rv;
188 }
189 
190 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)191 nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
192                                     uint64_t sourceOffset, uint32_t count) {
193   // if we have retarged to the end stream listener, then forward the call....
194   // otherwise, don't do anything
195 
196   nsresult rv = NS_OK;
197 
198   if (m_targetStreamListener)
199     rv = m_targetStreamListener->OnDataAvailable(request, inStr, sourceOffset,
200                                                  count);
201   return rv;
202 }
203 
OnStopRequest(nsIRequest * request,nsresult aStatus)204 NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request,
205                                                 nsresult aStatus) {
206   LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
207 
208   if (m_targetStreamListener) {
209     nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
210 
211     // If this is a multipart stream, we could get another
212     // OnStartRequest after this... reset state.
213     m_targetStreamListener = nullptr;
214     mContentType.Truncate();
215     listener->OnStopRequest(request, aStatus);
216   }
217   mUsedContentHandler = false;
218 
219   // Remember...
220   // In the case of multiplexed streams (such as multipart/x-mixed-replace)
221   // these stream listener methods could be called again :-)
222   //
223   return NS_OK;
224 }
225 
DispatchContent(nsIRequest * request,nsISupports * aCtxt)226 nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request,
227                                              nsISupports* aCtxt) {
228   LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this,
229        mContentType.get()));
230 
231   MOZ_ASSERT(!m_targetStreamListener,
232              "Why do we already have a target stream listener?");
233 
234   nsresult rv;
235   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
236   if (!aChannel) {
237     LOG_ERROR(("  Request is not a channel.  Bailing."));
238     return NS_ERROR_FAILURE;
239   }
240 
241   constexpr auto anyType = "*/*"_ns;
242   if (mContentType.IsEmpty() || mContentType == anyType) {
243     rv = aChannel->GetContentType(mContentType);
244     if (NS_FAILED(rv)) return rv;
245     LOG(("  Got type from channel: '%s'", mContentType.get()));
246   }
247 
248   bool isGuessFromExt =
249       mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
250   if (isGuessFromExt) {
251     // Reset to application/octet-stream for now; no one other than the
252     // external helper app service should see APPLICATION_GUESS_FROM_EXT.
253     mContentType = APPLICATION_OCTET_STREAM;
254     aChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
255   }
256 
257   // Check whether the data should be forced to be handled externally.  This
258   // could happen because the Content-Disposition header is set so, or, in the
259   // future, because the user has specified external handling for the MIME
260   // type.
261   bool forceExternalHandling = false;
262   uint32_t disposition;
263   rv = aChannel->GetContentDisposition(&disposition);
264 
265   if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) {
266     forceExternalHandling = true;
267   }
268 
269   LOG(("  forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
270 
271   if (!forceExternalHandling) {
272     //
273     // First step: See whether m_contentListener wants to handle this
274     // content type.
275     //
276     if (TryDefaultContentListener(aChannel)) {
277       LOG(("  Success!  Our default listener likes this type"));
278       // All done here
279       return NS_OK;
280     }
281 
282     // If we aren't allowed to try other listeners, just skip through to
283     // trying to convert the data.
284     if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
285       //
286       // Second step: See whether some other registered listener wants
287       // to handle this content type.
288       //
289       int32_t count = mURILoader ? mURILoader->m_listeners.Count() : 0;
290       nsCOMPtr<nsIURIContentListener> listener;
291       for (int32_t i = 0; i < count; i++) {
292         listener = do_QueryReferent(mURILoader->m_listeners[i]);
293         if (listener) {
294           if (TryContentListener(listener, aChannel)) {
295             LOG(("  Found listener registered on the URILoader"));
296             return NS_OK;
297           }
298         } else {
299           // remove from the listener list, reset i and update count
300           mURILoader->m_listeners.RemoveObjectAt(i--);
301           --count;
302         }
303       }
304 
305       //
306       // Third step: Try to find a content listener that has not yet had
307       // the chance to register, as it is contained in a not-yet-loaded
308       // module, but which has registered a contract ID.
309       //
310       nsCOMPtr<nsICategoryManager> catman =
311           do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
312       if (catman) {
313         nsCString contractidString;
314         rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
315                                       mContentType, contractidString);
316         if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
317           LOG(("  Listener contractid for '%s' is '%s'", mContentType.get(),
318                contractidString.get()));
319 
320           listener = do_CreateInstance(contractidString.get());
321           LOG(("  Listener from category manager: 0x%p", listener.get()));
322 
323           if (listener && TryContentListener(listener, aChannel)) {
324             LOG(("  Listener from category manager likes this type"));
325             return NS_OK;
326           }
327         }
328       }
329 
330       //
331       // Fourth step: try to find an nsIContentHandler for our type.
332       //
333       nsAutoCString handlerContractID(NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
334       handlerContractID += mContentType;
335 
336       nsCOMPtr<nsIContentHandler> contentHandler =
337           do_CreateInstance(handlerContractID.get());
338       if (contentHandler) {
339         LOG(("  Content handler found"));
340         // Note that m_originalContext can be nullptr when running this in
341         // the parent process on behalf on a docshell in the content process,
342         // and in that case we only support content handlers that don't need
343         // the context.
344         rv = contentHandler->HandleContent(mContentType.get(),
345                                            m_originalContext, request);
346         // XXXbz returning an error code to represent handling the
347         // content is just bizarre!
348         if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
349           if (NS_FAILED(rv)) {
350             // The content handler has unexpectedly failed.  Cancel the request
351             // just in case the handler didn't...
352             LOG(("  Content handler failed.  Aborting load"));
353             request->Cancel(rv);
354           } else {
355             LOG(("  Content handler taking over load"));
356             mUsedContentHandler = true;
357           }
358 
359           return rv;
360         }
361       }
362     } else {
363       LOG(
364           ("  DONT_RETARGET flag set, so skipped over random other content "
365            "listeners and content handlers"));
366     }
367 
368     //
369     // Fifth step:  If no listener prefers this type, see if any stream
370     //              converters exist to transform this content type into
371     //              some other.
372     //
373     // Don't do this if the server sent us a MIME type of "*/*" because they saw
374     // it in our Accept header and got confused.
375     // XXXbz have to be careful here; may end up in some sort of bizarre
376     // infinite decoding loop.
377     if (mContentType != anyType) {
378       rv = TryStreamConversion(aChannel);
379       if (NS_SUCCEEDED(rv)) {
380         return NS_OK;
381       }
382     }
383   }
384 
385   NS_ASSERTION(!m_targetStreamListener,
386                "If we found a listener, why are we not using it?");
387 
388   if (mFlags & nsIURILoader::DONT_RETARGET) {
389     LOG(
390         ("  External handling forced or (listener not interested and no "
391          "stream converter exists), and retargeting disallowed -> aborting"));
392     return NS_ERROR_WONT_HANDLE_CONTENT;
393   }
394 
395   // Before dispatching to the external helper app service, check for an HTTP
396   // error page.  If we got one, we don't want to handle it with a helper app,
397   // really.
398   // The WPT a-download-click-404.html requires us to silently handle this
399   // without displaying an error page, so we just return early here.
400   // See bug 1604308 for discussion around what the ideal behaviour is.
401   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
402   if (httpChannel) {
403     bool requestSucceeded;
404     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
405     if (NS_FAILED(rv) || !requestSucceeded) {
406       return NS_OK;
407     }
408   }
409 
410   // Sixth step:
411   //
412   // All attempts to dispatch this content have failed.  Just pass it off to
413   // the helper app service.
414   //
415 
416   nsCOMPtr<nsIExternalHelperAppService> helperAppService =
417       do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
418   if (helperAppService) {
419     LOG(("  Passing load off to helper app service"));
420 
421     // Set these flags to indicate that the channel has been targeted and that
422     // we are not using the original consumer.
423     nsLoadFlags loadFlags = 0;
424     request->GetLoadFlags(&loadFlags);
425     request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI |
426                           nsIChannel::LOAD_TARGETED);
427 
428     if (isGuessFromExt) {
429       mContentType = APPLICATION_GUESS_FROM_EXT;
430       aChannel->SetContentType(nsLiteralCString(APPLICATION_GUESS_FROM_EXT));
431     }
432 
433     rv = TryExternalHelperApp(helperAppService, aChannel);
434     if (NS_FAILED(rv)) {
435       request->SetLoadFlags(loadFlags);
436       m_targetStreamListener = nullptr;
437     }
438   }
439 
440   NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
441                "There is no way we should be successful at this point without "
442                "a m_targetStreamListener");
443   return rv;
444 }
445 
TryExternalHelperApp(nsIExternalHelperAppService * aHelperAppService,nsIChannel * aChannel)446 nsresult nsDocumentOpenInfo::TryExternalHelperApp(
447     nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel) {
448   return aHelperAppService->DoContent(mContentType, aChannel, m_originalContext,
449                                       false, nullptr,
450                                       getter_AddRefs(m_targetStreamListener));
451 }
452 
ConvertData(nsIRequest * request,nsIURIContentListener * aListener,const nsACString & aSrcContentType,const nsACString & aOutContentType)453 nsresult nsDocumentOpenInfo::ConvertData(nsIRequest* request,
454                                          nsIURIContentListener* aListener,
455                                          const nsACString& aSrcContentType,
456                                          const nsACString& aOutContentType) {
457   LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
458        PromiseFlatCString(aSrcContentType).get(),
459        PromiseFlatCString(aOutContentType).get()));
460 
461   if (mDataConversionDepthLimit == 0) {
462     LOG(
463         ("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion "
464          "limit!",
465          this));
466     // This will fall back to external helper app handling.
467     return NS_ERROR_ABORT;
468   }
469 
470   MOZ_ASSERT(aSrcContentType != aOutContentType,
471              "ConvertData called when the two types are the same!");
472 
473   nsresult rv = NS_OK;
474 
475   nsCOMPtr<nsIStreamConverterService> StreamConvService =
476       do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
477   if (NS_FAILED(rv)) return rv;
478 
479   LOG(("  Got converter service"));
480 
481   // When applying stream decoders, it is necessary to "insert" an
482   // intermediate nsDocumentOpenInfo instance to handle the targeting of
483   // the "final" stream or streams.
484   //
485   // For certain content types (ie. multi-part/x-mixed-replace) the input
486   // stream is split up into multiple destination streams.  This
487   // intermediate instance is used to target these "decoded" streams...
488   //
489   RefPtr<nsDocumentOpenInfo> nextLink = Clone();
490 
491   LOG(("  Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
492 
493   // Decrease the conversion recursion limit by one to prevent infinite loops.
494   nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1;
495 
496   // Make sure nextLink starts with the contentListener that said it wanted
497   // the results of this decode.
498   nextLink->m_contentListener = aListener;
499   // Also make sure it has to look for a stream listener to pump data into.
500   nextLink->m_targetStreamListener = nullptr;
501 
502   // Make sure that nextLink treats the data as aOutContentType when
503   // dispatching; that way even if the stream converters don't change the type
504   // on the channel we will still do the right thing.  If aOutContentType is
505   // */*, that's OK -- that will just indicate to nextLink that it should get
506   // the type off the channel.
507   nextLink->mContentType = aOutContentType;
508 
509   // The following call sets m_targetStreamListener to the input end of the
510   // stream converter and sets the output end of the stream converter to
511   // nextLink.  As we pump data into m_targetStreamListener the stream
512   // converter will convert it and pass the converted data to nextLink.
513   return StreamConvService->AsyncConvertData(
514       PromiseFlatCString(aSrcContentType).get(),
515       PromiseFlatCString(aOutContentType).get(), nextLink, request,
516       getter_AddRefs(m_targetStreamListener));
517 }
518 
TryStreamConversion(nsIChannel * aChannel)519 nsresult nsDocumentOpenInfo::TryStreamConversion(nsIChannel* aChannel) {
520   constexpr auto anyType = "*/*"_ns;
521   nsresult rv = ConvertData(aChannel, m_contentListener, mContentType, anyType);
522   if (NS_FAILED(rv)) {
523     m_targetStreamListener = nullptr;
524   } else if (m_targetStreamListener) {
525     // We found a converter for this MIME type.  We'll just pump data into
526     // it and let the downstream nsDocumentOpenInfo handle things.
527     LOG(("  Converter taking over now"));
528   }
529   return rv;
530 }
531 
TryContentListener(nsIURIContentListener * aListener,nsIChannel * aChannel)532 bool nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
533                                             nsIChannel* aChannel) {
534   LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x", this,
535        mFlags));
536 
537   MOZ_ASSERT(aListener, "Must have a non-null listener");
538   MOZ_ASSERT(aChannel, "Must have a channel");
539 
540   bool listenerWantsContent = false;
541   nsCString typeToUse;
542 
543   if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
544     aListener->IsPreferred(mContentType.get(), getter_Copies(typeToUse),
545                            &listenerWantsContent);
546   } else {
547     aListener->CanHandleContent(mContentType.get(), false,
548                                 getter_Copies(typeToUse),
549                                 &listenerWantsContent);
550   }
551   if (!listenerWantsContent) {
552     LOG(("  Listener is not interested"));
553     return false;
554   }
555 
556   if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
557     // Need to do a conversion here.
558 
559     nsresult rv = NS_ERROR_NOT_AVAILABLE;
560     if (mAllowListenerConversions) {
561       rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
562     }
563 
564     if (NS_FAILED(rv)) {
565       // No conversion path -- we don't want this listener, if we got one
566       m_targetStreamListener = nullptr;
567     }
568 
569     LOG(("  Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
570 
571     // m_targetStreamListener is now the input end of the converter, and we can
572     // just pump the data in there, if it exists.  If it does not, we need to
573     // try other nsIURIContentListeners.
574     return m_targetStreamListener != nullptr;
575   }
576 
577   // At this point, aListener wants data of type mContentType.  Let 'em have
578   // it.  But first, if we are retargeting, set an appropriate flag on the
579   // channel
580   nsLoadFlags loadFlags = 0;
581   aChannel->GetLoadFlags(&loadFlags);
582 
583   // Set this flag to indicate that the channel has been targeted at a final
584   // consumer.  This load flag is tested in nsDocLoader::OnProgress.
585   nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
586 
587   nsCOMPtr<nsIURIContentListener> originalListener =
588       do_GetInterface(m_originalContext);
589   if (originalListener != aListener) {
590     newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
591   }
592   aChannel->SetLoadFlags(loadFlags | newLoadFlags);
593 
594   bool abort = false;
595   bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
596   nsresult rv =
597       aListener->DoContent(mContentType, isPreferred, aChannel,
598                            getter_AddRefs(m_targetStreamListener), &abort);
599 
600   if (NS_FAILED(rv)) {
601     LOG_ERROR(("  DoContent failed"));
602 
603     // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
604     aChannel->SetLoadFlags(loadFlags);
605     m_targetStreamListener = nullptr;
606     return false;
607   }
608 
609   if (abort) {
610     // Nothing else to do here -- aListener is handling it all.  Make
611     // sure m_targetStreamListener is null so we don't do anything
612     // after this point.
613     LOG(("  Listener has aborted the load"));
614     m_targetStreamListener = nullptr;
615   }
616 
617   NS_ASSERTION(abort || m_targetStreamListener,
618                "DoContent returned no listener?");
619 
620   // aListener is handling the load from this point on.
621   return true;
622 }
623 
TryDefaultContentListener(nsIChannel * aChannel)624 bool nsDocumentOpenInfo::TryDefaultContentListener(nsIChannel* aChannel) {
625   if (m_contentListener) {
626     return TryContentListener(m_contentListener, aChannel);
627   }
628   return false;
629 }
630 
631 ///////////////////////////////////////////////////////////////////////////////////////////////
632 // Implementation of nsURILoader
633 ///////////////////////////////////////////////////////////////////////////////////////////////
634 
nsURILoader()635 nsURILoader::nsURILoader() {}
636 
~nsURILoader()637 nsURILoader::~nsURILoader() {}
638 
639 NS_IMPL_ADDREF(nsURILoader)
NS_IMPL_RELEASE(nsURILoader)640 NS_IMPL_RELEASE(nsURILoader)
641 
642 NS_INTERFACE_MAP_BEGIN(nsURILoader)
643   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
644   NS_INTERFACE_MAP_ENTRY(nsIURILoader)
645 NS_INTERFACE_MAP_END
646 
647 NS_IMETHODIMP nsURILoader::RegisterContentListener(
648     nsIURIContentListener* aContentListener) {
649   nsresult rv = NS_OK;
650 
651   nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
652   NS_ASSERTION(weakListener,
653                "your URIContentListener must support weak refs!\n");
654 
655   if (weakListener) m_listeners.AppendObject(weakListener);
656 
657   return rv;
658 }
659 
UnRegisterContentListener(nsIURIContentListener * aContentListener)660 NS_IMETHODIMP nsURILoader::UnRegisterContentListener(
661     nsIURIContentListener* aContentListener) {
662   nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
663   if (weakListener) m_listeners.RemoveObject(weakListener);
664 
665   return NS_OK;
666 }
667 
OpenURI(nsIChannel * channel,uint32_t aFlags,nsIInterfaceRequestor * aWindowContext)668 NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel* channel, uint32_t aFlags,
669                                    nsIInterfaceRequestor* aWindowContext) {
670   NS_ENSURE_ARG_POINTER(channel);
671 
672   if (LOG_ENABLED()) {
673     nsCOMPtr<nsIURI> uri;
674     channel->GetURI(getter_AddRefs(uri));
675     nsAutoCString spec;
676     uri->GetAsciiSpec(spec);
677     LOG(("nsURILoader::OpenURI for %s", spec.get()));
678   }
679 
680   nsCOMPtr<nsIStreamListener> loader;
681   nsresult rv = OpenChannel(channel, aFlags, aWindowContext, false,
682                             getter_AddRefs(loader));
683   if (NS_FAILED(rv)) {
684     if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
685       // Not really an error, from this method's point of view
686       return NS_OK;
687     }
688   }
689 
690   // This method is not complete. Eventually, we should first go
691   // to the content listener and ask them for a protocol handler...
692   // if they don't give us one, we need to go to the registry and get
693   // the preferred protocol handler.
694 
695   // But for now, I'm going to let necko do the work for us....
696   rv = channel->AsyncOpen(loader);
697 
698   // no content from this load - that's OK.
699   if (rv == NS_ERROR_NO_CONTENT) {
700     LOG(("  rv is NS_ERROR_NO_CONTENT -- doing nothing"));
701     return NS_OK;
702   }
703   return rv;
704 }
705 
OpenChannel(nsIChannel * channel,uint32_t aFlags,nsIInterfaceRequestor * aWindowContext,bool aChannelIsOpen,nsIStreamListener ** aListener)706 nsresult nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags,
707                                   nsIInterfaceRequestor* aWindowContext,
708                                   bool aChannelIsOpen,
709                                   nsIStreamListener** aListener) {
710   NS_ASSERTION(channel, "Trying to open a null channel!");
711   NS_ASSERTION(aWindowContext, "Window context must not be null");
712 
713   if (LOG_ENABLED()) {
714     nsCOMPtr<nsIURI> uri;
715     channel->GetURI(getter_AddRefs(uri));
716     nsAutoCString spec;
717     uri->GetAsciiSpec(spec);
718     LOG(("nsURILoader::OpenChannel for %s", spec.get()));
719   }
720 
721   // we need to create a DocumentOpenInfo object which will go ahead and open
722   // the url and discover the content type....
723   RefPtr<nsDocumentOpenInfo> loader =
724       new nsDocumentOpenInfo(aWindowContext, aFlags, this);
725 
726   // Set the correct loadgroup on the channel
727   nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
728 
729   if (!loadGroup) {
730     // XXXbz This context is violating what we'd like to be the new uriloader
731     // api.... Set up a nsDocLoader to handle the loadgroup for this context.
732     // This really needs to go away!
733     nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
734     if (listener) {
735       nsCOMPtr<nsISupports> cookie;
736       listener->GetLoadCookie(getter_AddRefs(cookie));
737       if (!cookie) {
738         RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
739         nsresult rv = newDocLoader->Init();
740         if (NS_FAILED(rv)) return rv;
741         rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
742         if (NS_FAILED(rv)) return rv;
743         cookie = nsDocLoader::GetAsSupports(newDocLoader);
744         listener->SetLoadCookie(cookie);
745       }
746       loadGroup = do_GetInterface(cookie);
747     }
748   }
749 
750   // If the channel is pending, then we need to remove it from its current
751   // loadgroup
752   nsCOMPtr<nsILoadGroup> oldGroup;
753   channel->GetLoadGroup(getter_AddRefs(oldGroup));
754   if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
755     // It is important to add the channel to the new group before
756     // removing it from the old one, so that the load isn't considered
757     // done as soon as the request is removed.
758     loadGroup->AddRequest(channel, nullptr);
759 
760     if (oldGroup) {
761       oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
762     }
763   }
764 
765   channel->SetLoadGroup(loadGroup);
766 
767   // prepare the loader for receiving data
768   nsresult rv = loader->Prepare();
769   if (NS_SUCCEEDED(rv)) NS_ADDREF(*aListener = loader);
770   return rv;
771 }
772 
OpenChannel(nsIChannel * channel,uint32_t aFlags,nsIInterfaceRequestor * aWindowContext,nsIStreamListener ** aListener)773 NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags,
774                                        nsIInterfaceRequestor* aWindowContext,
775                                        nsIStreamListener** aListener) {
776   bool pending;
777   if (NS_FAILED(channel->IsPending(&pending))) {
778     pending = false;
779   }
780 
781   return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
782 }
783 
Stop(nsISupports * aLoadCookie)784 NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie) {
785   nsresult rv;
786   nsCOMPtr<nsIDocumentLoader> docLoader;
787 
788   NS_ENSURE_ARG_POINTER(aLoadCookie);
789 
790   docLoader = do_GetInterface(aLoadCookie, &rv);
791   if (docLoader) {
792     rv = docLoader->Stop();
793   }
794   return rv;
795 }
796