Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/uriloader/base/nsURILoader.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsAutoPtr.h"
9
#include "nsIURIContentListener.h"
10
#include "nsIContentHandler.h"
11
#include "nsILoadGroup.h"
12
#include "nsIDocumentLoader.h"
13
#include "nsIWebProgress.h"
14
#include "nsIWebProgressListener.h"
15
#include "nsIIOService.h"
16
#include "nsIServiceManager.h"
17
#include "nsIStreamListener.h"
18
#include "nsIURI.h"
19
#include "nsIChannel.h"
20
#include "nsIInterfaceRequestor.h"
21
#include "nsIInterfaceRequestorUtils.h"
22
#include "nsIProgressEventSink.h"
23
#include "nsIInputStream.h"
24
#include "nsIStreamConverterService.h"
25
#include "nsWeakReference.h"
26
#include "nsIHttpChannel.h"
27
#include "nsIMultiPartChannel.h"
28
#include "netCore.h"
29
#include "nsCRT.h"
30
#include "nsIDocShell.h"
31
#include "nsIDocShellTreeItem.h"
32
#include "nsIDocShellTreeOwner.h"
33
#include "nsIThreadRetargetableStreamListener.h"
34
35
#include "nsString.h"
36
#include "nsThreadUtils.h"
37
#include "nsReadableUtils.h"
38
#include "nsError.h"
39
40
#include "nsICategoryManager.h"
41
#include "nsCExternalHandlerService.h" // contains contractids for the helper app service
42
43
#include "nsIMIMEHeaderParam.h"
44
#include "nsNetCID.h"
45
46
#include "nsMimeTypes.h"
47
48
#include "nsDocLoader.h"
49
#include "mozilla/Attributes.h"
50
#include "mozilla/IntegerPrintfMacros.h"
51
#include "mozilla/Preferences.h"
52
#include "mozilla/Unused.h"
53
#include "nsContentUtils.h"
54
55
mozilla::LazyLogModule nsURILoader::mLog("URILoader");
56
57
0
#define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args)
58
0
#define LOG_ERROR(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args)
59
0
#define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug)
60
61
#define NS_PREF_DISABLE_BACKGROUND_HANDLING \
62
0
    "security.exthelperapp.disable_background_handling"
63
64
static uint32_t sConvertDataLimit = 20;
65
66
static bool InitPreferences()
67
0
{
68
0
  nsresult rv = mozilla::Preferences::AddUintVarCache(
69
0
    &sConvertDataLimit,
70
0
    "general.document_open_conversion_depth_limit",
71
0
    20);
72
0
  return NS_SUCCEEDED(rv);
73
0
}
74
75
/**
76
 * The nsDocumentOpenInfo contains the state required when a single
77
 * document is being opened in order to discover the content type...
78
 * Each instance remains alive until its target URL has been loaded
79
 * (or aborted).
80
 */
81
class nsDocumentOpenInfo final : public nsIStreamListener
82
                               , public nsIThreadRetargetableStreamListener
83
{
84
public:
85
86
  // Real constructor
87
  // aFlags is a combination of the flags on nsIURILoader
88
  nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
89
                     uint32_t aFlags,
90
                     nsURILoader* aURILoader);
91
92
  NS_DECL_THREADSAFE_ISUPPORTS
93
94
  /**
95
   * Prepares this object for receiving data. The stream
96
   * listener methods of this class must not be called before calling this
97
   * method.
98
   */
99
  nsresult Prepare();
100
101
  // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
102
  // take the data off our hands.
103
  nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
104
105
  // Call this if we need to insert a stream converter from aSrcContentType to
106
  // aOutContentType into the StreamListener chain.  DO NOT call it if the two
107
  // types are the same, since no conversion is needed in that case.
108
  nsresult ConvertData(nsIRequest *request,
109
                       nsIURIContentListener *aListener,
110
                       const nsACString & aSrcContentType,
111
                       const nsACString & aOutContentType);
112
113
  /**
114
   * Function to attempt to use aListener to handle the load.  If
115
   * true is returned, nothing else needs to be done; if false
116
   * is returned, then a different way of handling the load should be
117
   * tried.
118
   */
119
  bool TryContentListener(nsIURIContentListener* aListener,
120
                            nsIChannel* aChannel);
121
122
  // nsIRequestObserver methods:
123
  NS_DECL_NSIREQUESTOBSERVER
124
125
  // nsIStreamListener methods:
126
  NS_DECL_NSISTREAMLISTENER
127
128
  // nsIThreadRetargetableStreamListener
129
  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
130
protected:
131
  ~nsDocumentOpenInfo();
132
133
protected:
134
  /**
135
   * The first content listener to try dispatching data to.  Typically
136
   * the listener associated with the entity that originated the load.
137
   */
138
  nsCOMPtr<nsIURIContentListener> m_contentListener;
139
140
  /**
141
   * The stream listener to forward nsIStreamListener notifications
142
   * to.  This is set once the load is dispatched.
143
   */
144
  nsCOMPtr<nsIStreamListener> m_targetStreamListener;
145
146
  /**
147
   * A pointer to the entity that originated the load. We depend on getting
148
   * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
149
   */
150
  nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
151
152
  /**
153
   * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
154
   * (also determines whether we use CanHandleContent or IsPreferred).
155
   * DONT_RETARGET means that we will only try m_originalContext, no other
156
   * listeners.
157
   */
158
  uint32_t mFlags;
159
160
  /**
161
   * The type of the data we will be trying to dispatch.
162
   */
163
  nsCString mContentType;
164
165
  /**
166
   * Reference to the URILoader service so we can access its list of
167
   * nsIURIContentListeners.
168
   */
169
  RefPtr<nsURILoader> mURILoader;
170
171
  /**
172
   * Limit of data conversion depth to prevent infinite conversion loops
173
   */
174
  uint32_t mDataConversionDepthLimit;
175
};
176
177
NS_IMPL_ADDREF(nsDocumentOpenInfo)
178
NS_IMPL_RELEASE(nsDocumentOpenInfo)
179
180
0
NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
181
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
182
0
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
183
0
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
184
0
  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
185
0
NS_INTERFACE_MAP_END
186
187
nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
188
                                       uint32_t aFlags,
189
                                       nsURILoader* aURILoader)
190
  : m_originalContext(aWindowContext),
191
    mFlags(aFlags),
192
    mURILoader(aURILoader),
193
    mDataConversionDepthLimit(sConvertDataLimit)
194
0
{
195
0
}
196
197
nsDocumentOpenInfo::~nsDocumentOpenInfo()
198
0
{
199
0
}
200
201
nsresult nsDocumentOpenInfo::Prepare()
202
0
{
203
0
  LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
204
0
205
0
  nsresult rv;
206
0
207
0
  // ask our window context if it has a uri content listener...
208
0
  m_contentListener = do_GetInterface(m_originalContext, &rv);
209
0
  return rv;
210
0
}
211
212
NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
213
0
{
214
0
  LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
215
0
  MOZ_ASSERT(request);
216
0
  if (!request) {
217
0
    return NS_ERROR_UNEXPECTED;
218
0
  }
219
0
220
0
  nsresult rv = NS_OK;
221
0
222
0
  //
223
0
  // Deal with "special" HTTP responses:
224
0
  // 
225
0
  // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
226
0
  //   not try to find a content handler.  Return NS_BINDING_ABORTED to cancel
227
0
  //   the request.  This has the effect of ensuring that the DocLoader does
228
0
  //   not try to interpret this as a real request.
229
0
  // 
230
0
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
231
0
232
0
  if (NS_SUCCEEDED(rv)) {
233
0
    uint32_t responseCode = 0;
234
0
235
0
    rv = httpChannel->GetResponseStatus(&responseCode);
236
0
237
0
    if (NS_FAILED(rv)) {
238
0
      LOG_ERROR(("  Failed to get HTTP response status"));
239
0
      
240
0
      // behave as in the canceled case
241
0
      return NS_OK;
242
0
    }
243
0
244
0
    LOG(("  HTTP response status: %d", responseCode));
245
0
246
0
    if (204 == responseCode || 205 == responseCode) {
247
0
      return NS_BINDING_ABORTED;
248
0
    }
249
0
250
0
    static bool sLargeAllocationTestingAllHttpLoads = false;
251
0
    static bool sLargeAllocationHeaderEnabled = false;
252
0
    static bool sCachedLargeAllocationPref = false;
253
0
    if (!sCachedLargeAllocationPref) {
254
0
      sCachedLargeAllocationPref = true;
255
0
      mozilla::Preferences::AddBoolVarCache(&sLargeAllocationHeaderEnabled,
256
0
                                            "dom.largeAllocationHeader.enabled");
257
0
      mozilla::Preferences::AddBoolVarCache(&sLargeAllocationTestingAllHttpLoads,
258
0
                                            "dom.largeAllocation.testing.allHttpLoads");
259
0
    }
260
0
261
0
    if (sLargeAllocationHeaderEnabled) {
262
0
      if (sLargeAllocationTestingAllHttpLoads) {
263
0
        nsCOMPtr<nsIURI> uri;
264
0
        rv = httpChannel->GetURI(getter_AddRefs(uri));
265
0
        if (NS_SUCCEEDED(rv) && uri) {
266
0
          bool httpScheme = false;
267
0
          bool httpsScheme = false;
268
0
          uri->SchemeIs("http", &httpScheme);
269
0
          uri->SchemeIs("https", &httpsScheme);
270
0
          if ((httpScheme || httpsScheme) &&
271
0
              nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
272
0
            return NS_BINDING_ABORTED;
273
0
          }
274
0
        }
275
0
      }
276
0
277
0
      // If we have a Large-Allocation header, let's check if we should perform a process switch.
278
0
      nsAutoCString largeAllocationHeader;
279
0
      rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Large-Allocation"), largeAllocationHeader);
280
0
      if (NS_SUCCEEDED(rv) && nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
281
0
        return NS_BINDING_ABORTED;
282
0
      }
283
0
    }
284
0
  }
285
0
286
0
  //
287
0
  // Make sure that the transaction has succeeded, so far...
288
0
  //
289
0
  nsresult status;
290
0
291
0
  rv = request->GetStatus(&status);
292
0
  
293
0
  NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
294
0
  if (NS_FAILED(rv)) return rv;
295
0
296
0
  if (NS_FAILED(status)) {
297
0
    LOG_ERROR(("  Request failed, status: 0x%08" PRIX32, static_cast<uint32_t>(rv)));
298
0
  
299
0
    //
300
0
    // The transaction has already reported an error - so it will be torn
301
0
    // down. Therefore, it is not necessary to return an error code...
302
0
    //
303
0
    return NS_OK;
304
0
  }
305
0
306
0
  rv = DispatchContent(request, aCtxt);
307
0
308
0
  LOG(("  After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32,
309
0
       m_targetStreamListener.get(), static_cast<uint32_t>(rv)));
310
0
311
0
  NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
312
0
               "Must not have an m_targetStreamListener with a failure return!");
313
0
314
0
  NS_ENSURE_SUCCESS(rv, rv);
315
0
  
316
0
  if (m_targetStreamListener)
317
0
    rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
318
0
319
0
  LOG(("  OnStartRequest returning: 0x%08" PRIX32, static_cast<uint32_t>(rv)));
320
0
  
321
0
  return rv;
322
0
}
323
324
NS_IMETHODIMP
325
nsDocumentOpenInfo::CheckListenerChain()
326
0
{
327
0
  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
328
0
  nsresult rv = NS_OK;
329
0
  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
330
0
    do_QueryInterface(m_targetStreamListener, &rv);
331
0
  if (retargetableListener) {
332
0
    rv = retargetableListener->CheckListenerChain();
333
0
  }
334
0
  LOG(("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv %" PRIx32,
335
0
       this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
336
0
       (nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv)));
337
0
  return rv;
338
0
}
339
340
NS_IMETHODIMP
341
nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
342
                                    nsIInputStream * inStr,
343
                                    uint64_t sourceOffset, uint32_t count)
344
0
{
345
0
  // if we have retarged to the end stream listener, then forward the call....
346
0
  // otherwise, don't do anything
347
0
348
0
  nsresult rv = NS_OK;
349
0
  
350
0
  if (m_targetStreamListener)
351
0
    rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
352
0
  return rv;
353
0
}
354
355
NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 
356
                                                nsresult aStatus)
357
0
{
358
0
  LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
359
0
  
360
0
  if ( m_targetStreamListener)
361
0
  {
362
0
    nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
363
0
364
0
    // If this is a multipart stream, we could get another
365
0
    // OnStartRequest after this... reset state.
366
0
    m_targetStreamListener = nullptr;
367
0
    mContentType.Truncate();
368
0
    listener->OnStopRequest(request, aCtxt, aStatus);
369
0
  }
370
0
371
0
  // Remember...
372
0
  // In the case of multiplexed streams (such as multipart/x-mixed-replace)
373
0
  // these stream listener methods could be called again :-)
374
0
  //
375
0
  return NS_OK;
376
0
}
377
378
nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
379
0
{
380
0
  LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
381
0
382
0
  MOZ_ASSERT(!m_targetStreamListener,
383
0
             "Why do we already have a target stream listener?");
384
0
385
0
  nsresult rv;
386
0
  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
387
0
  if (!aChannel) {
388
0
    LOG_ERROR(("  Request is not a channel.  Bailing."));
389
0
    return NS_ERROR_FAILURE;
390
0
  }
391
0
392
0
  NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
393
0
  if (mContentType.IsEmpty() || mContentType == anyType) {
394
0
    rv = aChannel->GetContentType(mContentType);
395
0
    if (NS_FAILED(rv)) return rv;
396
0
    LOG(("  Got type from channel: '%s'", mContentType.get()));
397
0
  }
398
0
399
0
  bool isGuessFromExt =
400
0
    mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
401
0
  if (isGuessFromExt) {
402
0
    // Reset to application/octet-stream for now; no one other than the
403
0
    // external helper app service should see APPLICATION_GUESS_FROM_EXT.
404
0
    mContentType = APPLICATION_OCTET_STREAM;
405
0
    aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
406
0
  }
407
0
408
0
  // Check whether the data should be forced to be handled externally.  This
409
0
  // could happen because the Content-Disposition header is set so, or, in the
410
0
  // future, because the user has specified external handling for the MIME
411
0
  // type.
412
0
  bool forceExternalHandling = false;
413
0
  uint32_t disposition;
414
0
  rv = aChannel->GetContentDisposition(&disposition);
415
0
416
0
  if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) {
417
0
    forceExternalHandling = true;
418
0
  }
419
0
420
0
  LOG(("  forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
421
0
422
0
  if (!forceExternalHandling)
423
0
  {
424
0
    //
425
0
    // First step: See whether m_contentListener wants to handle this
426
0
    // content type.
427
0
    //
428
0
    if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
429
0
      LOG(("  Success!  Our default listener likes this type"));
430
0
      // All done here
431
0
      return NS_OK;
432
0
    }
433
0
434
0
    // If we aren't allowed to try other listeners, just skip through to
435
0
    // trying to convert the data.
436
0
    if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
437
0
438
0
      //
439
0
      // Second step: See whether some other registered listener wants
440
0
      // to handle this content type.
441
0
      //
442
0
      int32_t count = mURILoader->m_listeners.Count();
443
0
      nsCOMPtr<nsIURIContentListener> listener;
444
0
      for (int32_t i = 0; i < count; i++) {
445
0
        listener = do_QueryReferent(mURILoader->m_listeners[i]);
446
0
        if (listener) {
447
0
          if (TryContentListener(listener, aChannel)) {
448
0
            LOG(("  Found listener registered on the URILoader"));
449
0
            return NS_OK;
450
0
          }
451
0
        } else {
452
0
          // remove from the listener list, reset i and update count
453
0
          mURILoader->m_listeners.RemoveObjectAt(i--);
454
0
          --count;
455
0
        }
456
0
      }
457
0
458
0
      //
459
0
      // Third step: Try to find a content listener that has not yet had
460
0
      // the chance to register, as it is contained in a not-yet-loaded
461
0
      // module, but which has registered a contract ID.
462
0
      //
463
0
      nsCOMPtr<nsICategoryManager> catman =
464
0
        do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
465
0
      if (catman) {
466
0
        nsCString contractidString;
467
0
        rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
468
0
                                      mContentType, contractidString);
469
0
        if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
470
0
          LOG(("  Listener contractid for '%s' is '%s'",
471
0
               mContentType.get(), contractidString.get()));
472
0
473
0
          listener = do_CreateInstance(contractidString.get());
474
0
          LOG(("  Listener from category manager: 0x%p", listener.get()));
475
0
476
0
          if (listener && TryContentListener(listener, aChannel)) {
477
0
            LOG(("  Listener from category manager likes this type"));
478
0
            return NS_OK;
479
0
          }
480
0
        }
481
0
      }
482
0
483
0
      //
484
0
      // Fourth step: try to find an nsIContentHandler for our type.
485
0
      //
486
0
      nsAutoCString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
487
0
      handlerContractID += mContentType;
488
0
489
0
      nsCOMPtr<nsIContentHandler> contentHandler =
490
0
        do_CreateInstance(handlerContractID.get());
491
0
      if (contentHandler) {
492
0
        LOG(("  Content handler found"));
493
0
        rv = contentHandler->HandleContent(mContentType.get(),
494
0
                                           m_originalContext, request);
495
0
        // XXXbz returning an error code to represent handling the
496
0
        // content is just bizarre!
497
0
        if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
498
0
          if (NS_FAILED(rv)) {
499
0
            // The content handler has unexpectedly failed.  Cancel the request
500
0
            // just in case the handler didn't...
501
0
            LOG(("  Content handler failed.  Aborting load"));
502
0
            request->Cancel(rv);
503
0
          }
504
0
          else {
505
0
            LOG(("  Content handler taking over load"));
506
0
          }
507
0
508
0
          return rv;
509
0
        }
510
0
      }
511
0
    } else {
512
0
      LOG(("  DONT_RETARGET flag set, so skipped over random other content "
513
0
           "listeners and content handlers"));
514
0
    }
515
0
516
0
    //
517
0
    // Fifth step:  If no listener prefers this type, see if any stream
518
0
    //              converters exist to transform this content type into
519
0
    //              some other.
520
0
    //
521
0
    // Don't do this if the server sent us a MIME type of "*/*" because they saw
522
0
    // it in our Accept header and got confused.
523
0
    // XXXbz have to be careful here; may end up in some sort of bizarre infinite
524
0
    // decoding loop.
525
0
    if (mContentType != anyType) {
526
0
      rv = ConvertData(request, m_contentListener, mContentType, anyType);
527
0
      if (NS_FAILED(rv)) {
528
0
        m_targetStreamListener = nullptr;
529
0
      } else if (m_targetStreamListener) {
530
0
        // We found a converter for this MIME type.  We'll just pump data into it
531
0
        // and let the downstream nsDocumentOpenInfo handle things.
532
0
        LOG(("  Converter taking over now"));
533
0
        return NS_OK;
534
0
      }
535
0
    }
536
0
  }
537
0
538
0
  NS_ASSERTION(!m_targetStreamListener,
539
0
               "If we found a listener, why are we not using it?");
540
0
  
541
0
  if (mFlags & nsIURILoader::DONT_RETARGET) {
542
0
    LOG(("  External handling forced or (listener not interested and no "
543
0
         "stream converter exists), and retargeting disallowed -> aborting"));
544
0
    return NS_ERROR_WONT_HANDLE_CONTENT;
545
0
  }
546
0
547
0
  // Before dispatching to the external helper app service, check for an HTTP
548
0
  // error page.  If we got one, we don't want to handle it with a helper app,
549
0
  // really.
550
0
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
551
0
  if (httpChannel) {
552
0
    bool requestSucceeded;
553
0
    rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
554
0
    if (NS_FAILED(rv) || !requestSucceeded) {
555
0
      // returning error from OnStartRequest will cancel the channel
556
0
      return NS_ERROR_FILE_NOT_FOUND;
557
0
    }
558
0
  }
559
0
  
560
0
  // Sixth step:
561
0
  //
562
0
  // All attempts to dispatch this content have failed.  Just pass it off to
563
0
  // the helper app service.
564
0
  //
565
0
566
0
  //
567
0
  // Optionally, we may want to disable background handling by the external
568
0
  // helper application service.
569
0
  //
570
0
  if (mozilla::Preferences::GetBool(NS_PREF_DISABLE_BACKGROUND_HANDLING,
571
0
                                    false)) {
572
0
    // First, we will ensure that the parent docshell is in an active
573
0
    // state as we will disallow all external application handling unless it is
574
0
    // in the foreground.
575
0
    nsCOMPtr<nsIDocShell> docShell(do_GetInterface(m_originalContext));
576
0
    if (!docShell) {
577
0
      // If we can't perform our security check we definitely don't want to go
578
0
      // any further!
579
0
      LOG(("Failed to get DocShell to ensure it is active before anding off to "
580
0
           "helper app service. Aborting."));
581
0
      return NS_ERROR_FAILURE;
582
0
    }
583
0
584
0
    // Ensure the DocShell is active before continuing.
585
0
    bool isActive = false;
586
0
    docShell->GetIsActive(&isActive);
587
0
    if (!isActive) {
588
0
      LOG(("  Check for active DocShell returned false. Aborting hand off to "
589
0
           "helper app service."));
590
0
      return NS_ERROR_DOM_SECURITY_ERR;
591
0
    }
592
0
  }
593
0
594
0
  nsCOMPtr<nsIExternalHelperAppService> helperAppService =
595
0
    do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
596
0
  if (helperAppService) {
597
0
    LOG(("  Passing load off to helper app service"));
598
0
599
0
    // Set these flags to indicate that the channel has been targeted and that
600
0
    // we are not using the original consumer.
601
0
    nsLoadFlags loadFlags = 0;
602
0
    request->GetLoadFlags(&loadFlags);
603
0
    request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
604
0
                                    | nsIChannel::LOAD_TARGETED);
605
0
606
0
    if (isGuessFromExt) {
607
0
      mContentType = APPLICATION_GUESS_FROM_EXT;
608
0
      aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
609
0
    }
610
0
611
0
    rv = helperAppService->DoContent(mContentType,
612
0
                                     request,
613
0
                                     m_originalContext,
614
0
                                     false,
615
0
                                     nullptr,
616
0
                                     getter_AddRefs(m_targetStreamListener));
617
0
    if (NS_FAILED(rv)) {
618
0
      request->SetLoadFlags(loadFlags);
619
0
      m_targetStreamListener = nullptr;
620
0
    }
621
0
  }
622
0
      
623
0
  NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
624
0
               "There is no way we should be successful at this point without a m_targetStreamListener");
625
0
  return rv;
626
0
}
627
628
nsresult
629
nsDocumentOpenInfo::ConvertData(nsIRequest *request,
630
                                nsIURIContentListener* aListener,
631
                                const nsACString& aSrcContentType,
632
                                const nsACString& aOutContentType)
633
0
{
634
0
  LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
635
0
       PromiseFlatCString(aSrcContentType).get(),
636
0
       PromiseFlatCString(aOutContentType).get()));
637
0
638
0
  if (mDataConversionDepthLimit == 0) {
639
0
    LOG(("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion limit!", this));
640
0
    // This will fall back to external helper app handling.
641
0
    return NS_ERROR_ABORT;
642
0
  }
643
0
644
0
  MOZ_ASSERT(aSrcContentType != aOutContentType,
645
0
             "ConvertData called when the two types are the same!");
646
0
647
0
  nsresult rv = NS_OK;
648
0
649
0
  nsCOMPtr<nsIStreamConverterService> StreamConvService = 
650
0
    do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
651
0
  if (NS_FAILED(rv)) return rv;
652
0
653
0
  LOG(("  Got converter service"));
654
0
  
655
0
  // When applying stream decoders, it is necessary to "insert" an 
656
0
  // intermediate nsDocumentOpenInfo instance to handle the targeting of
657
0
  // the "final" stream or streams.
658
0
  //
659
0
  // For certain content types (ie. multi-part/x-mixed-replace) the input
660
0
  // stream is split up into multiple destination streams.  This
661
0
  // intermediate instance is used to target these "decoded" streams...
662
0
  //
663
0
  RefPtr<nsDocumentOpenInfo> nextLink =
664
0
    new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
665
0
666
0
  LOG(("  Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
667
0
  
668
0
  // Decrease the conversion recursion limit by one to prevent infinite loops.
669
0
  nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1;
670
0
671
0
  // Make sure nextLink starts with the contentListener that said it wanted the
672
0
  // results of this decode.
673
0
  nextLink->m_contentListener = aListener;
674
0
  // Also make sure it has to look for a stream listener to pump data into.
675
0
  nextLink->m_targetStreamListener = nullptr;
676
0
677
0
  // Make sure that nextLink treats the data as aOutContentType when
678
0
  // dispatching; that way even if the stream converters don't change the type
679
0
  // on the channel we will still do the right thing.  If aOutContentType is
680
0
  // */*, that's OK -- that will just indicate to nextLink that it should get
681
0
  // the type off the channel.
682
0
  nextLink->mContentType = aOutContentType;
683
0
684
0
  // The following call sets m_targetStreamListener to the input end of the
685
0
  // stream converter and sets the output end of the stream converter to
686
0
  // nextLink.  As we pump data into m_targetStreamListener the stream
687
0
  // converter will convert it and pass the converted data to nextLink.
688
0
  return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(), 
689
0
                                             PromiseFlatCString(aOutContentType).get(), 
690
0
                                             nextLink, 
691
0
                                             request,
692
0
                                             getter_AddRefs(m_targetStreamListener));
693
0
}
694
695
bool
696
nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
697
                                       nsIChannel* aChannel)
698
0
{
699
0
  LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x",
700
0
       this, mFlags));
701
0
702
0
  MOZ_ASSERT(aListener, "Must have a non-null listener");
703
0
  MOZ_ASSERT(aChannel, "Must have a channel");
704
0
  
705
0
  bool listenerWantsContent = false;
706
0
  nsCString typeToUse;
707
0
  
708
0
  if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
709
0
    aListener->IsPreferred(mContentType.get(),
710
0
                           getter_Copies(typeToUse),
711
0
                           &listenerWantsContent);
712
0
  } else {
713
0
    aListener->CanHandleContent(mContentType.get(), false,
714
0
                                getter_Copies(typeToUse),
715
0
                                &listenerWantsContent);
716
0
  }
717
0
  if (!listenerWantsContent) {
718
0
    LOG(("  Listener is not interested"));
719
0
    return false;
720
0
  }
721
0
722
0
  if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
723
0
    // Need to do a conversion here.
724
0
725
0
    nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
726
0
727
0
    if (NS_FAILED(rv)) {
728
0
      // No conversion path -- we don't want this listener, if we got one
729
0
      m_targetStreamListener = nullptr;
730
0
    }
731
0
732
0
    LOG(("  Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
733
0
    
734
0
    // m_targetStreamListener is now the input end of the converter, and we can
735
0
    // just pump the data in there, if it exists.  If it does not, we need to
736
0
    // try other nsIURIContentListeners.
737
0
    return m_targetStreamListener != nullptr;
738
0
  }
739
0
740
0
  // At this point, aListener wants data of type mContentType.  Let 'em have
741
0
  // it.  But first, if we are retargeting, set an appropriate flag on the
742
0
  // channel
743
0
  nsLoadFlags loadFlags = 0;
744
0
  aChannel->GetLoadFlags(&loadFlags);
745
0
746
0
  // Set this flag to indicate that the channel has been targeted at a final
747
0
  // consumer.  This load flag is tested in nsDocLoader::OnProgress.
748
0
  nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
749
0
750
0
  nsCOMPtr<nsIURIContentListener> originalListener =
751
0
    do_GetInterface(m_originalContext);
752
0
  if (originalListener != aListener) {
753
0
    newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
754
0
  }
755
0
  aChannel->SetLoadFlags(loadFlags | newLoadFlags);
756
0
  
757
0
  bool abort = false;
758
0
  bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
759
0
  nsresult rv = aListener->DoContent(mContentType,
760
0
                                     isPreferred,
761
0
                                     aChannel,
762
0
                                     getter_AddRefs(m_targetStreamListener),
763
0
                                     &abort);
764
0
    
765
0
  if (NS_FAILED(rv)) {
766
0
    LOG_ERROR(("  DoContent failed"));
767
0
    
768
0
    // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
769
0
    aChannel->SetLoadFlags(loadFlags);
770
0
    m_targetStreamListener = nullptr;
771
0
    return false;
772
0
  }
773
0
774
0
  if (abort) {
775
0
    // Nothing else to do here -- aListener is handling it all.  Make
776
0
    // sure m_targetStreamListener is null so we don't do anything
777
0
    // after this point.
778
0
    LOG(("  Listener has aborted the load"));
779
0
    m_targetStreamListener = nullptr;
780
0
  }
781
0
782
0
  NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
783
0
784
0
  // aListener is handling the load from this point on.  
785
0
  return true;
786
0
}
787
788
789
///////////////////////////////////////////////////////////////////////////////////////////////
790
// Implementation of nsURILoader
791
///////////////////////////////////////////////////////////////////////////////////////////////
792
793
nsURILoader::nsURILoader()
794
0
{
795
0
}
796
797
nsURILoader::~nsURILoader()
798
0
{
799
0
}
800
801
NS_IMPL_ADDREF(nsURILoader)
802
NS_IMPL_RELEASE(nsURILoader)
803
804
0
NS_INTERFACE_MAP_BEGIN(nsURILoader)
805
0
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
806
0
   NS_INTERFACE_MAP_ENTRY(nsIURILoader)
807
0
NS_INTERFACE_MAP_END
808
809
NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
810
0
{
811
0
  nsresult rv = NS_OK;
812
0
813
0
  nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
814
0
  NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
815
0
  
816
0
  if (weakListener)
817
0
    m_listeners.AppendObject(weakListener);
818
0
819
0
  return rv;
820
0
} 
821
822
NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
823
0
{
824
0
  nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
825
0
  if (weakListener)
826
0
    m_listeners.RemoveObject(weakListener);
827
0
828
0
  return NS_OK;
829
0
  
830
0
}
831
832
NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel, 
833
                                   uint32_t aFlags,
834
                                   nsIInterfaceRequestor *aWindowContext)
835
0
{
836
0
  NS_ENSURE_ARG_POINTER(channel);
837
0
838
0
  if (LOG_ENABLED()) {
839
0
    nsCOMPtr<nsIURI> uri;
840
0
    channel->GetURI(getter_AddRefs(uri));
841
0
    nsAutoCString spec;
842
0
    uri->GetAsciiSpec(spec);
843
0
    LOG(("nsURILoader::OpenURI for %s", spec.get()));
844
0
  }
845
0
846
0
  nsCOMPtr<nsIStreamListener> loader;
847
0
  nsresult rv = OpenChannel(channel,
848
0
                            aFlags,
849
0
                            aWindowContext,
850
0
                            false,
851
0
                            getter_AddRefs(loader));
852
0
853
0
  if (NS_SUCCEEDED(rv)) {
854
0
    // this method is not complete!!! Eventually, we should first go
855
0
    // to the content listener and ask them for a protocol handler...
856
0
    // if they don't give us one, we need to go to the registry and get
857
0
    // the preferred protocol handler. 
858
0
859
0
    // But for now, I'm going to let necko do the work for us....
860
0
    rv = channel->AsyncOpen2(loader);
861
0
862
0
    // no content from this load - that's OK.
863
0
    if (rv == NS_ERROR_NO_CONTENT) {
864
0
      LOG(("  rv is NS_ERROR_NO_CONTENT -- doing nothing"));
865
0
      rv = NS_OK;
866
0
    }
867
0
  } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
868
0
    // Not really an error, from this method's point of view
869
0
    rv = NS_OK;
870
0
  }
871
0
  return rv;
872
0
}
873
874
nsresult nsURILoader::OpenChannel(nsIChannel* channel,
875
                                  uint32_t aFlags,
876
                                  nsIInterfaceRequestor* aWindowContext,
877
                                  bool aChannelIsOpen,
878
                                  nsIStreamListener** aListener)
879
0
{
880
0
  NS_ASSERTION(channel, "Trying to open a null channel!");
881
0
  NS_ASSERTION(aWindowContext, "Window context must not be null");
882
0
883
0
  if (LOG_ENABLED()) {
884
0
    nsCOMPtr<nsIURI> uri;
885
0
    channel->GetURI(getter_AddRefs(uri));
886
0
    nsAutoCString spec;
887
0
    uri->GetAsciiSpec(spec);
888
0
    LOG(("nsURILoader::OpenChannel for %s", spec.get()));
889
0
  }
890
0
891
0
  // Let the window context's uriListener know that the open is starting.  This
892
0
  // gives that window a chance to abort the load process.
893
0
  nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
894
0
  if (winContextListener) {
895
0
    nsCOMPtr<nsIURI> uri;
896
0
    channel->GetURI(getter_AddRefs(uri));
897
0
    if (uri) {
898
0
      bool doAbort = false;
899
0
      winContextListener->OnStartURIOpen(uri, &doAbort);
900
0
901
0
      if (doAbort) {
902
0
        LOG(("  OnStartURIOpen aborted load"));
903
0
        return NS_ERROR_WONT_HANDLE_CONTENT;
904
0
      }
905
0
    }
906
0
  }
907
0
908
0
  static bool once = InitPreferences();
909
0
  mozilla::Unused << once;
910
0
911
0
  // we need to create a DocumentOpenInfo object which will go ahead and open
912
0
  // the url and discover the content type....
913
0
  RefPtr<nsDocumentOpenInfo> loader =
914
0
    new nsDocumentOpenInfo(aWindowContext, aFlags, this);
915
0
916
0
  // Set the correct loadgroup on the channel
917
0
  nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
918
0
919
0
  if (!loadGroup) {
920
0
    // XXXbz This context is violating what we'd like to be the new uriloader
921
0
    // api.... Set up a nsDocLoader to handle the loadgroup for this context.
922
0
    // This really needs to go away!
923
0
    nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
924
0
    if (listener) {
925
0
      nsCOMPtr<nsISupports> cookie;
926
0
      listener->GetLoadCookie(getter_AddRefs(cookie));
927
0
      if (!cookie) {
928
0
        RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
929
0
        nsresult rv = newDocLoader->Init();
930
0
        if (NS_FAILED(rv))
931
0
          return rv;
932
0
        rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
933
0
        if (NS_FAILED(rv))
934
0
          return rv;
935
0
        cookie = nsDocLoader::GetAsSupports(newDocLoader);
936
0
        listener->SetLoadCookie(cookie);
937
0
      }
938
0
      loadGroup = do_GetInterface(cookie);
939
0
    }
940
0
  }
941
0
942
0
  // If the channel is pending, then we need to remove it from its current
943
0
  // loadgroup
944
0
  nsCOMPtr<nsILoadGroup> oldGroup;
945
0
  channel->GetLoadGroup(getter_AddRefs(oldGroup));
946
0
  if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
947
0
    // It is important to add the channel to the new group before
948
0
    // removing it from the old one, so that the load isn't considered
949
0
    // done as soon as the request is removed.
950
0
    loadGroup->AddRequest(channel, nullptr);
951
0
952
0
   if (oldGroup) {
953
0
      oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
954
0
    }
955
0
  }
956
0
957
0
  channel->SetLoadGroup(loadGroup);
958
0
959
0
  // prepare the loader for receiving data
960
0
  nsresult rv = loader->Prepare();
961
0
  if (NS_SUCCEEDED(rv))
962
0
    NS_ADDREF(*aListener = loader);
963
0
  return rv;
964
0
}
965
966
NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel,
967
                                       uint32_t aFlags,
968
                                       nsIInterfaceRequestor* aWindowContext,
969
                                       nsIStreamListener** aListener)
970
0
{
971
0
  bool pending;
972
0
  if (NS_FAILED(channel->IsPending(&pending))) {
973
0
    pending = false;
974
0
  }
975
0
976
0
  return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
977
0
}
978
979
NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
980
0
{
981
0
  nsresult rv;
982
0
  nsCOMPtr<nsIDocumentLoader> docLoader;
983
0
984
0
  NS_ENSURE_ARG_POINTER(aLoadCookie);
985
0
986
0
  docLoader = do_GetInterface(aLoadCookie, &rv);
987
0
  if (docLoader) {
988
0
    rv = docLoader->Stop();
989
0
  }
990
0
  return rv;
991
0
}