Back to index

lightning-sunbird  0.9+nobinonly
nsURILoader.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsURILoader.h"
00039 #include "nsAutoPtr.h"
00040 #include "nsIURIContentListener.h"
00041 #include "nsIContentHandler.h"
00042 #include "nsILoadGroup.h"
00043 #include "nsIDocumentLoader.h"
00044 #include "nsIWebProgress.h"
00045 #include "nsIWebProgressListener.h"
00046 #include "nsIIOService.h"
00047 #include "nsIServiceManager.h"
00048 #include "nsIStreamListener.h"
00049 #include "nsIURI.h"
00050 #include "nsIChannel.h"
00051 #include "nsIInterfaceRequestor.h"
00052 #include "nsIInterfaceRequestorUtils.h"
00053 #include "nsIProgressEventSink.h"
00054 #include "nsIInputStream.h"
00055 #include "nsIStreamConverterService.h"
00056 #include "nsWeakReference.h"
00057 #include "nsIHttpChannel.h"
00058 #include "nsIMultiPartChannel.h"
00059 #include "netCore.h"
00060 #include "nsCRT.h"
00061 #include "nsIDocShell.h"
00062 #include "nsIDocShellTreeItem.h"
00063 #include "nsIDocShellTreeOwner.h"
00064 
00065 #include "nsXPIDLString.h"
00066 #include "nsString.h"
00067 
00068 #include "nsIDOMWindowInternal.h"
00069 #include "nsReadableUtils.h"
00070 #include "nsDOMError.h"
00071 
00072 #include "nsICategoryManager.h"
00073 #include "nsCExternalHandlerService.h" // contains contractids for the helper app service
00074 
00075 #include "nsIMIMEHeaderParam.h"
00076 #include "nsNetCID.h"
00077 
00078 #include "nsMimeTypes.h"
00079 
00080 #include "nsDocLoader.h"
00081 
00082 static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
00083 #ifdef PR_LOGGING
00084 PRLogModuleInfo* nsURILoader::mLog = nsnull;
00085 #endif
00086 
00087 #define LOG(args) PR_LOG(nsURILoader::mLog, PR_LOG_DEBUG, args)
00088 #define LOG_ERROR(args) PR_LOG(nsURILoader::mLog, PR_LOG_ERROR, args)
00089 #define LOG_ENABLED() PR_LOG_TEST(nsURILoader::mLog, PR_LOG_DEBUG)
00090 
00097 class nsDocumentOpenInfo : public nsIStreamListener
00098 {
00099 public:
00100   // Needed for nsCOMPtr to work right... Don't call this!
00101   nsDocumentOpenInfo();
00102 
00103   // Real constructor
00104   nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
00105                      PRBool aIsContentPreferred,
00106                      nsURILoader* aURILoader);
00107 
00108   NS_DECL_ISUPPORTS
00109 
00110   nsresult Open(nsIChannel* channel);
00111 
00112   // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
00113   // take the data off our hands.
00114   nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
00115 
00116   // Call this if we need to insert a stream converter from aSrcContentType to
00117   // aOutContentType into the StreamListener chain.  DO NOT call it if the two
00118   // types are the same, since no conversion is needed in that case.
00119   nsresult ConvertData(nsIRequest *request,
00120                        nsIURIContentListener *aListener,
00121                        const nsACString & aSrcContentType,
00122                        const nsACString & aOutContentType);
00123 
00130   PRBool TryContentListener(nsIURIContentListener* aListener,
00131                             nsIChannel* aChannel);
00132 
00133   // nsIRequestObserver methods:
00134   NS_DECL_NSIREQUESTOBSERVER
00135 
00136   // nsIStreamListener methods:
00137   NS_DECL_NSISTREAMLISTENER
00138 
00139 protected:
00140   ~nsDocumentOpenInfo();
00141 
00142 protected:
00147   nsCOMPtr<nsIURIContentListener> m_contentListener;
00148 
00153   nsCOMPtr<nsIStreamListener> m_targetStreamListener;
00154 
00159   nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
00160 
00165   PRBool mIsContentPreferred;
00166 
00170   nsCString mContentType;
00171 
00176   nsRefPtr<nsURILoader> mURILoader;
00177 };
00178 
00179 NS_IMPL_THREADSAFE_ADDREF(nsDocumentOpenInfo)
00180 NS_IMPL_THREADSAFE_RELEASE(nsDocumentOpenInfo)
00181 
00182 NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
00183   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
00184   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
00185   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
00186 NS_INTERFACE_MAP_END_THREADSAFE
00187 
00188 nsDocumentOpenInfo::nsDocumentOpenInfo()
00189 {
00190   NS_NOTREACHED("This should never be called\n");
00191 }
00192 
00193 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
00194                                        PRBool aIsContentPreferred,
00195                                        nsURILoader* aURILoader)
00196   : m_originalContext(aWindowContext),
00197     mIsContentPreferred(aIsContentPreferred),
00198     mURILoader(aURILoader)
00199 {
00200 }
00201 
00202 nsDocumentOpenInfo::~nsDocumentOpenInfo()
00203 {
00204 }
00205 
00206 nsresult nsDocumentOpenInfo::Open(nsIChannel *aChannel)
00207 {
00208   LOG(("[0x%p] nsDocumentOpenInfo::Open", this));
00209     
00210   // this method is not complete!!! Eventually, we should first go
00211   // to the content listener and ask them for a protocol handler...
00212   // if they don't give us one, we need to go to the registry and get
00213   // the preferred protocol handler. 
00214 
00215   // But for now, I'm going to let necko do the work for us....
00216 
00217   nsresult rv;
00218 
00219   // ask our window context if it has a uri content listener...
00220   m_contentListener = do_GetInterface(m_originalContext, &rv);
00221   if (NS_FAILED(rv)) return rv;
00222 
00223   // now just open the channel!
00224   rv = aChannel->AsyncOpen(this, nsnull);
00225 
00226   // no content from this load - that's OK.
00227   if (rv == NS_ERROR_DOM_RETVAL_UNDEFINED ||
00228       rv == NS_ERROR_NO_CONTENT) {
00229     LOG(("  rv is NS_ERROR_DOM_RETVAL_UNDEFINED or NS_ERROR_NO_CONTENT -- doing nothing"));
00230     rv = NS_OK;
00231   }
00232 
00233   return rv;
00234 }
00235 
00236 NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
00237 {
00238   LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
00239   
00240   nsresult rv = NS_OK;
00241 
00242   //
00243   // Deal with "special" HTTP responses:
00244   // 
00245   // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
00246   //   not try to find a content handler.  Return NS_BINDING_ABORTED to cancel
00247   //   the request.  This has the effect of ensuring that the DocLoader does
00248   //   not try to interpret this as a real request.
00249   // 
00250   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
00251 
00252   if (NS_SUCCEEDED(rv)) {
00253     PRUint32 responseCode = 0;
00254 
00255     rv = httpChannel->GetResponseStatus(&responseCode);
00256 
00257     if (NS_FAILED(rv)) {
00258       LOG_ERROR(("  Failed to get HTTP response status"));
00259       
00260       // behave as in the canceled case
00261       return NS_OK;
00262     }
00263 
00264     LOG(("  HTTP response status: %d", responseCode));
00265 
00266     if (204 == responseCode || 205 == responseCode) {
00267       return NS_BINDING_ABORTED;
00268     }
00269   }
00270 
00271   //
00272   // Make sure that the transaction has succeeded, so far...
00273   //
00274   nsresult status;
00275 
00276   rv = request->GetStatus(&status);
00277   
00278   NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
00279   if (NS_FAILED(rv)) return rv;
00280 
00281   if (NS_FAILED(status)) {
00282     LOG_ERROR(("  Request failed, status: 0x%08X", rv));
00283   
00284     //
00285     // The transaction has already reported an error - so it will be torn
00286     // down. Therefore, it is not necessary to return an error code...
00287     //
00288     return NS_OK;
00289   }
00290 
00291   if (httpChannel && mContentType.IsEmpty()) {
00292     // This is our initial dispatch, and this is an HTTP channel.  Check for
00293     // the text/plain mess.
00294     nsCAutoString contentTypeHdr;
00295     httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
00296                                    contentTypeHdr);
00297     nsCAutoString contentType;
00298     httpChannel->GetContentType(contentType);
00299     
00300     // Make sure to do a case-sensitive exact match comparison here.  Apache
00301     // 1.x just sends text/plain for "unknown", while Apache 2.x sends
00302     // text/plain with a ISO-8859-1 charset.  Debian's Apache version, just to
00303     // be different, sends text/plain with iso-8859-1 charset.  Don't do
00304     // general case-insensitive comparison, since we really want to apply this
00305     // crap as rarely as we can.
00306     if (contentType.EqualsLiteral("text/plain") &&
00307         (contentTypeHdr.EqualsLiteral("text/plain") ||
00308          contentTypeHdr.Equals(
00309              NS_LITERAL_CSTRING("text/plain; charset=ISO-8859-1")) ||
00310          contentTypeHdr.Equals(
00311              NS_LITERAL_CSTRING("text/plain; charset=iso-8859-1")))) {
00312       // Check whether we have content-encoding.  If we do, don't try to detect
00313       // the type, since that will lead to the content being automatically
00314       // decompressed....
00315       nsCAutoString contentEncoding;
00316       httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
00317                                      contentEncoding);
00318       if (contentEncoding.IsEmpty()) {
00319         // OK, this is initial dispatch of an HTTP response and its Content-Type
00320         // header is exactly "text/plain".  We need to check whether this is
00321         // really text....  Note that some of our listeners will actually
00322         // accept all types, including the APPLICATION_MAYBE_TEXT internal
00323         // type, so we need to call ConvertData here manually instead of
00324         // relying on DispatchContent to do it.
00325         LOG(("  Possibly bogus text/plain; trying to sniff for real type"));
00326         rv = ConvertData(request, m_contentListener,
00327                          NS_LITERAL_CSTRING(APPLICATION_MAYBE_TEXT),
00328                          NS_LITERAL_CSTRING("*/*"));
00329         if (NS_FAILED(rv)) {
00330           // We failed to convert.  Just go ahead and handle as the original
00331           // type.  If ConvertData happened to set our m_targetStreamListener,
00332           // we don't want it!
00333           m_targetStreamListener = nsnull;
00334         }
00335         else {
00336           LOG((APPLICATION_MAYBE_TEXT " converter taking over now"));
00337         }
00338       }
00339     }
00340   }
00341 
00342   // If we sniffed text/plain above, m_targetStreamListener may already be
00343   // non-null.
00344   if (!m_targetStreamListener) {
00345     rv = DispatchContent(request, aCtxt);
00346   }
00347 
00348   LOG(("  After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv));
00349 
00350   NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
00351                "Must not have an m_targetStreamListener with a failure return!");
00352 
00353   NS_ENSURE_SUCCESS(rv, rv);
00354   
00355   if (m_targetStreamListener)
00356     rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
00357 
00358   LOG(("  OnStartRequest returning: 0x%08X", rv));
00359   
00360   return rv;
00361 }
00362 
00363 NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
00364                                                   nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
00365 {
00366   // if we have retarged to the end stream listener, then forward the call....
00367   // otherwise, don't do anything
00368 
00369   nsresult rv = NS_OK;
00370   
00371   if (m_targetStreamListener)
00372     rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
00373   return rv;
00374 }
00375 
00376 NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 
00377                                                 nsresult aStatus)
00378 {
00379   LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
00380   
00381   if ( m_targetStreamListener)
00382   {
00383     nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
00384 
00385     // If this is a multipart stream, we could get another
00386     // OnStartRequest after this... reset state.
00387     m_targetStreamListener = 0;
00388     mContentType.Truncate();
00389     listener->OnStopRequest(request, aCtxt, aStatus);
00390   }
00391 
00392   // Remember...
00393   // In the case of multiplexed streams (such as multipart/x-mixed-replace)
00394   // these stream listener methods could be called again :-)
00395   //
00396   return NS_OK;
00397 }
00398 
00399 nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
00400 {
00401   LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
00402 
00403   NS_PRECONDITION(!m_targetStreamListener,
00404                   "Why do we already have a target stream listener?");
00405   
00406   nsresult rv;
00407   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
00408   if (!aChannel) {
00409     LOG_ERROR(("  Request is not a channel.  Bailing."));
00410     return NS_ERROR_FAILURE;
00411   }
00412 
00413   NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
00414   if (mContentType.IsEmpty() || mContentType == anyType) {
00415     rv = aChannel->GetContentType(mContentType);
00416     if (NS_FAILED(rv)) return rv;
00417     LOG(("  Got type from channel: '%s'", mContentType.get()));
00418   }
00419 
00420   PRBool isGuessFromExt =
00421     mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
00422   if (isGuessFromExt) {
00423     // Reset to application/octet-stream for now; no one other than the
00424     // external helper app service should see APPLICATION_GUESS_FROM_EXT.
00425     mContentType = APPLICATION_OCTET_STREAM;
00426     aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
00427   }
00428 
00429   // Check whether the data should be forced to be handled externally.  This
00430   // could happen because the Content-Disposition header is set so, or, in the
00431   // future, because the user has specified external handling for the MIME
00432   // type.
00433   PRBool forceExternalHandling = PR_FALSE;
00434   nsCAutoString disposition;
00435   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
00436   nsCOMPtr<nsIURI> uri;
00437   if (httpChannel)
00438   {
00439     rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
00440                                         disposition);
00441     httpChannel->GetURI(getter_AddRefs(uri));
00442   }
00443   else
00444   {
00445     nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(request));
00446     if (multipartChannel)
00447     {
00448       rv = multipartChannel->GetContentDisposition(disposition);
00449     }
00450   }
00451 
00452   LOG(("  Disposition header: '%s'", disposition.get()));
00453 
00454   if (NS_SUCCEEDED(rv) && !disposition.IsEmpty())
00455   {
00456     nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
00457     if (NS_SUCCEEDED(rv))
00458     {
00459       nsCAutoString fallbackCharset;
00460       if (uri)
00461         uri->GetOriginCharset(fallbackCharset);
00462       nsAutoString dispToken;
00463       // Get the disposition type
00464       rv = mimehdrpar->GetParameter(disposition, "", fallbackCharset,
00465                                     PR_TRUE, nsnull, dispToken);
00466       // RFC 2183, section 2.8 says that an unknown disposition
00467       // value should be treated as "attachment"
00468       // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
00469       // nsExternalHelperAppService.  Factor it out!
00470       if (NS_FAILED(rv) || 
00471           (// Some broken sites just send
00472            // Content-Disposition: ; filename="file"
00473            // screen those out here.
00474            !dispToken.IsEmpty() &&
00475            !dispToken.LowerCaseEqualsLiteral("inline") &&
00476           // Broken sites just send
00477           // Content-Disposition: filename="file"
00478           // without a disposition token... screen those out.
00479            !dispToken.EqualsIgnoreCase("filename", 8)) &&
00480           // Also in use is Content-Disposition: name="file"
00481            !dispToken.EqualsIgnoreCase("name", 4))
00482         // We have a content-disposition of "attachment" or unknown
00483         forceExternalHandling = PR_TRUE;
00484     }
00485   }
00486 
00487   LOG(("  forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
00488     
00489   // We're going to try to find a contentListener that can handle our data
00490   nsCOMPtr<nsIURIContentListener> contentListener;
00491   // The type or data the contentListener wants.
00492   nsXPIDLCString desiredContentType;
00493 
00494   if (!forceExternalHandling)
00495   {
00496     //
00497     // First step: See whether m_contentListener wants to handle this
00498     // content type.
00499     //
00500     if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
00501       LOG(("  Success!  Our default listener likes this type"));
00502       // All done here
00503       return NS_OK;
00504     }
00505 
00506     //
00507     // Second step: See whether some other registered listener wants
00508     // to handle this content type.
00509     //
00510     PRInt32 count = mURILoader->m_listeners.Count();
00511     nsCOMPtr<nsIURIContentListener> listener;
00512     for (PRInt32 i = 0; i < count; i++) {
00513       listener = do_QueryReferent(mURILoader->m_listeners[i]);
00514       if (listener) {
00515         if (TryContentListener(listener, aChannel)) {
00516           LOG(("  Found listener registered on the URILoader"));
00517           return NS_OK;
00518         }
00519       } else {
00520         // remove from the listener list, reset i and update count
00521         mURILoader->m_listeners.RemoveObjectAt(i--);
00522         --count;
00523       }
00524     }
00525 
00526     //
00527     // Third step: Try to find a content listener that has not yet had
00528     // the chance to register, as it is contained in a not-yet-loaded
00529     // module, but which has registered a contract ID.
00530     //
00531     nsCOMPtr<nsICategoryManager> catman =
00532       do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
00533     if (catman) {
00534       nsXPIDLCString contractidString;
00535       rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
00536                                     mContentType.get(),
00537                                     getter_Copies(contractidString));
00538       if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
00539         LOG(("  Listener contractid for '%s' is '%s'",
00540              mContentType.get(), contractidString.get()));
00541 
00542         listener = do_CreateInstance(contractidString);
00543         LOG(("  Listener from category manager: 0x%p", listener.get()));
00544         
00545         if (listener && TryContentListener(listener, aChannel)) {
00546           LOG(("  Listener from category manager likes this type"));
00547           return NS_OK;
00548         }
00549       }
00550     }
00551 
00552     //
00553     // Fourth step: try to find an nsIContentHandler for our type.
00554     //
00555     nsCAutoString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
00556     handlerContractID += mContentType;
00557 
00558     nsCOMPtr<nsIContentHandler> contentHandler =
00559       do_CreateInstance(handlerContractID.get());
00560     if (contentHandler) {
00561       LOG(("  Content handler found"));
00562       rv = contentHandler->HandleContent(mContentType.get(),
00563                                          m_originalContext, request);
00564       // XXXbz returning an error code to represent handling the
00565       // content is just bizarre!
00566       if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
00567         if (NS_FAILED(rv)) {
00568           // The content handler has unexpectedly failed.  Cancel the request
00569           // just in case the handler didn't...
00570           LOG(("  Content handler failed.  Aborting load"));
00571           request->Cancel(rv);
00572         }
00573 #ifdef PR_LOGGING
00574         else {
00575           LOG(("  Content handler taking over load"));
00576         }
00577 #endif
00578 
00579         return rv;
00580       }
00581     }
00582   }
00583 
00584   NS_ASSERTION(!m_targetStreamListener,
00585                "If we found a listener, why are we not using it?");
00586   
00587   //
00588   // Fifth step:  If no listener prefers this type, see if any stream
00589   //              converters exist to transform this content type into
00590   //              some other.
00591   //
00592 
00593   // We always want to do this, since even content being forced to
00594   // be handled externally may need decoding (eg via the unknown
00595   // content decoder).
00596   // Don't do this if the server sent us a MIME type of "*/*" because they saw
00597   // it in our Accept header and got confused.
00598   // XXXbz have to be careful here; may end up in some sort of bizarre infinite
00599   // decoding loop.
00600   if (mContentType != anyType) {
00601     rv = ConvertData(request, m_contentListener, mContentType, anyType);
00602     if (NS_FAILED(rv)) {
00603       m_targetStreamListener = nsnull;
00604     } else if (m_targetStreamListener) {
00605       // We found a converter for this MIME type.  We'll just pump data into it
00606       // and let the downstream nsDocumentOpenInfo handle things.
00607       LOG(("  Converter taking over now"));
00608       return NS_OK;
00609     }
00610   }
00611       
00612   // Sixth step:
00613   //
00614   // All attempts to dispatch this content have failed.  Just pass it off to
00615   // the helper app service.
00616   //
00617   nsCOMPtr<nsIExternalHelperAppService> helperAppService =
00618     do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
00619   if (helperAppService) {
00620     LOG(("  Passing load off to helper app service"));
00621 
00622     // Set these flags to indicate that the channel has been targeted and that
00623     // we are not using the original consumer.
00624     nsLoadFlags loadFlags = 0;
00625     request->GetLoadFlags(&loadFlags);
00626     request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
00627                                     | nsIChannel::LOAD_TARGETED);
00628 
00629     if (isGuessFromExt) {
00630       mContentType = APPLICATION_GUESS_FROM_EXT;
00631       aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
00632     }
00633 
00634     rv = helperAppService->DoContent(mContentType,
00635                                      request,
00636                                      m_originalContext,
00637                                      getter_AddRefs(m_targetStreamListener));
00638     if (NS_FAILED(rv)) {
00639       request->SetLoadFlags(loadFlags);
00640       m_targetStreamListener = nsnull;
00641     }
00642   }
00643       
00644   NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
00645                "There is no way we should be successful at this point without a m_targetStreamListener");
00646   return rv;
00647 }
00648 
00649 nsresult
00650 nsDocumentOpenInfo::ConvertData(nsIRequest *request,
00651                                 nsIURIContentListener* aListener,
00652                                 const nsACString& aSrcContentType,
00653                                 const nsACString& aOutContentType)
00654 {
00655   LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
00656        PromiseFlatCString(aSrcContentType).get(),
00657        PromiseFlatCString(aOutContentType).get()));
00658 
00659   NS_PRECONDITION(aSrcContentType != aOutContentType,
00660                   "ConvertData called when the two types are the same!");
00661   nsresult rv = NS_OK;
00662 
00663   nsCOMPtr<nsIStreamConverterService> StreamConvService = 
00664     do_GetService(kStreamConverterServiceCID, &rv);
00665   if (NS_FAILED(rv)) return rv;
00666 
00667   LOG(("  Got converter service"));
00668   
00669   // When applying stream decoders, it is necessary to "insert" an 
00670   // intermediate nsDocumentOpenInfo instance to handle the targeting of
00671   // the "final" stream or streams.
00672   //
00673   // For certain content types (ie. multi-part/x-mixed-replace) the input
00674   // stream is split up into multiple destination streams.  This
00675   // intermediate instance is used to target these "decoded" streams...
00676   //
00677   nsCOMPtr<nsDocumentOpenInfo> nextLink =
00678     new nsDocumentOpenInfo(m_originalContext, mIsContentPreferred, mURILoader);
00679   if (!nextLink) return NS_ERROR_OUT_OF_MEMORY;
00680 
00681   LOG(("  Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
00682   
00683   // Make sure nextLink starts with the contentListener that said it wanted the
00684   // results of this decode.
00685   nextLink->m_contentListener = aListener;
00686   // Also make sure it has to look for a stream listener to pump data into.
00687   nextLink->m_targetStreamListener = nsnull;
00688 
00689   // Make sure that nextLink treats the data as aOutContentType when
00690   // dispatching; that way even if the stream converters don't change the type
00691   // on the channel we will still do the right thing.  If aOutContentType is
00692   // */*, that's OK -- that will just indicate to nextLink that it should get
00693   // the type off the channel.
00694   nextLink->mContentType = aOutContentType;
00695 
00696   // The following call sets m_targetStreamListener to the input end of the
00697   // stream converter and sets the output end of the stream converter to
00698   // nextLink.  As we pump data into m_targetStreamListener the stream
00699   // converter will convert it and pass the converted data to nextLink.
00700   return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(), 
00701                                              PromiseFlatCString(aOutContentType).get(), 
00702                                              nextLink, 
00703                                              request,
00704                                              getter_AddRefs(m_targetStreamListener));
00705 }
00706 
00707 PRBool
00708 nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
00709                                        nsIChannel* aChannel)
00710 {
00711   LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mIsContentPreferred = %s",
00712        this, mIsContentPreferred ? "true" : "false"));
00713 
00714   NS_PRECONDITION(aListener, "Must have a non-null listener");
00715   NS_PRECONDITION(aChannel, "Must have a channel");
00716   
00717   PRBool listenerWantsContent = PR_FALSE;
00718   nsXPIDLCString typeToUse;
00719   
00720   if (mIsContentPreferred) {
00721     aListener->IsPreferred(mContentType.get(),
00722                            getter_Copies(typeToUse),
00723                            &listenerWantsContent);
00724   } else {
00725     aListener->CanHandleContent(mContentType.get(), PR_FALSE,
00726                                 getter_Copies(typeToUse),
00727                                 &listenerWantsContent);
00728   }
00729   if (!listenerWantsContent) {
00730     LOG(("  Listener is not interested"));
00731     return PR_FALSE;
00732   }
00733 
00734   if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
00735     // Need to do a conversion here.
00736 
00737     nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
00738 
00739     if (NS_FAILED(rv)) {
00740       // No conversion path -- we don't want this listener, if we got one
00741       m_targetStreamListener = nsnull;
00742     }
00743 
00744     LOG(("  Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
00745     
00746     // m_targetStreamListener is now the input end of the converter, and we can
00747     // just pump the data in there, if it exists.  If it does not, we need to
00748     // try other nsIURIContentListeners.
00749     return m_targetStreamListener != nsnull;
00750   }
00751 
00752   // At this point, aListener wants data of type mContentType.  Let 'em have
00753   // it.  But first, if we are retargeting, set an appropriate flag on the
00754   // channel
00755   nsLoadFlags loadFlags = 0;
00756   aChannel->GetLoadFlags(&loadFlags);
00757 
00758   // Set this flag to indicate that the channel has been targeted at a final
00759   // consumer.  This load flag is tested in nsDocLoader::OnProgress.
00760   nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
00761 
00762   nsCOMPtr<nsIURIContentListener> originalListener =
00763     do_GetInterface(m_originalContext);
00764   if (originalListener != aListener) {
00765     newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
00766   }
00767   aChannel->SetLoadFlags(loadFlags | newLoadFlags);
00768   
00769   PRBool abort = PR_FALSE;
00770   nsresult rv = aListener->DoContent(mContentType.get(),
00771                                      mIsContentPreferred,
00772                                      aChannel,
00773                                      getter_AddRefs(m_targetStreamListener),
00774                                      &abort);
00775     
00776   if (NS_FAILED(rv)) {
00777     LOG_ERROR(("  DoContent failed"));
00778     
00779     // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
00780     aChannel->SetLoadFlags(loadFlags);
00781     m_targetStreamListener = nsnull;
00782     return PR_FALSE;
00783   }
00784 
00785   if (abort) {
00786     // Nothing else to do here -- aListener is handling it all.  Make
00787     // sure m_targetStreamListener is null so we don't do anything
00788     // after this point.
00789     LOG(("  Listener has aborted the load"));
00790     m_targetStreamListener = nsnull;
00791   }
00792 
00793   NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
00794 
00795   // aListener is handling the load from this point on.  
00796   return PR_TRUE;
00797 }
00798 
00799 
00801 // Implementation of nsURILoader
00803 
00804 nsURILoader::nsURILoader()
00805 {
00806 #ifdef PR_LOGGING
00807   if (!mLog) {
00808     mLog = PR_NewLogModule("URILoader");
00809   }
00810 #endif
00811 }
00812 
00813 nsURILoader::~nsURILoader()
00814 {
00815 }
00816 
00817 NS_IMPL_ADDREF(nsURILoader)
00818 NS_IMPL_RELEASE(nsURILoader)
00819 
00820 NS_INTERFACE_MAP_BEGIN(nsURILoader)
00821    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
00822    NS_INTERFACE_MAP_ENTRY(nsIURILoader)
00823 NS_INTERFACE_MAP_END
00824 
00825 NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
00826 {
00827   nsresult rv = NS_OK;
00828 
00829   nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
00830   NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
00831   
00832   if (weakListener)
00833     m_listeners.AppendObject(weakListener);
00834 
00835   return rv;
00836 } 
00837 
00838 NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
00839 {
00840   nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
00841   if (weakListener)
00842     m_listeners.RemoveObject(weakListener);
00843 
00844   return NS_OK;
00845   
00846 }
00847 
00848 NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel, 
00849                                    PRBool aIsContentPreferred,
00850                                    nsIInterfaceRequestor *aWindowContext)
00851 {
00852   NS_ENSURE_ARG_POINTER(channel);
00853 
00854 #ifdef PR_LOGGING
00855   if (LOG_ENABLED()) {
00856     nsCOMPtr<nsIURI> uri;
00857     channel->GetURI(getter_AddRefs(uri));
00858     nsCAutoString spec;
00859     uri->GetAsciiSpec(spec);
00860     LOG(("nsURILoader::OpenURI for %s", spec.get()));
00861   }
00862 #endif
00863   
00864   // Let the window context's uriListener know that the open is starting.  This
00865   // gives that window a chance to abort the load process.
00866   nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
00867   if (winContextListener) {
00868     nsCOMPtr<nsIURI> uri;
00869     channel->GetURI(getter_AddRefs(uri));
00870     if (uri) {
00871       PRBool doAbort = PR_FALSE;
00872       winContextListener->OnStartURIOpen(uri, &doAbort);
00873          
00874       if (doAbort) {
00875         LOG(("  OnStartURIOpen aborted load"));
00876         return NS_OK;
00877       }
00878     }   
00879   }
00880 
00881   // we need to create a DocumentOpenInfo object which will go ahead and open
00882   // the url and discover the content type....
00883   nsCOMPtr<nsDocumentOpenInfo> loader =
00884     new nsDocumentOpenInfo(aWindowContext, aIsContentPreferred, this);
00885 
00886   if (!loader) return NS_ERROR_OUT_OF_MEMORY;
00887 
00888   // Set the correct loadgroup on the channel
00889   nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
00890 
00891   if (!loadGroup) {
00892     // XXXbz This context is violating what we'd like to be the new uriloader
00893     // api.... Set up a nsDocLoader to handle the loadgroup for this context.
00894     // This really needs to go away!    
00895     nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
00896     if (listener) {
00897       nsCOMPtr<nsISupports> cookie;
00898       listener->GetLoadCookie(getter_AddRefs(cookie));
00899       if (!cookie) {
00900         nsRefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
00901         if (!newDocLoader)
00902           return NS_ERROR_OUT_OF_MEMORY;
00903         nsresult rv = newDocLoader->Init();
00904         if (NS_FAILED(rv))
00905           return rv;
00906         rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
00907         if (NS_FAILED(rv))
00908           return rv;
00909         listener->SetLoadCookie(nsDocLoader::GetAsSupports(newDocLoader));
00910       }
00911     }
00912   }        
00913     
00914   channel->SetLoadGroup(loadGroup);
00915   
00916   // now instruct the loader to go ahead and open the url
00917   return loader->Open(channel);
00918 }
00919 
00920 NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
00921 {
00922   nsresult rv;
00923   nsCOMPtr<nsIDocumentLoader> docLoader;
00924 
00925   NS_ENSURE_ARG_POINTER(aLoadCookie);
00926 
00927   docLoader = do_GetInterface(aLoadCookie, &rv);
00928   if (docLoader) {
00929     rv = docLoader->Stop();
00930   }
00931   return rv;
00932 }
00933