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