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