Back to index

lightning-sunbird  0.9+nobinonly
nsExternalHelperAppService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  * vim:expandtab:shiftwidth=2:tabstop=2:cin:
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the Mozilla browser.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications, Inc.
00020  * Portions created by the Initial Developer are Copyright (C) 1999
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Scott MacGregor <mscott@netscape.com>
00025  *   Bill Law <law@netscape.com>
00026  *   Christian Biesinger <cbiesinger@web.de>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either of the GNU General Public License Version 2 or later (the "GPL"),
00030  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include "nsExternalHelperAppService.h"
00043 #include "nsIURI.h"
00044 #include "nsIURL.h"
00045 #include "nsIFile.h"
00046 #include "nsIFileURL.h"
00047 #include "nsIChannel.h"
00048 #include "nsIDirectoryService.h"
00049 #include "nsAppDirectoryServiceDefs.h"
00050 #include "nsICategoryManager.h"
00051 #include "nsXPIDLString.h"
00052 #include "nsUnicharUtils.h"
00053 #include "nsIStringEnumerator.h"
00054 #include "nsMemory.h"
00055 #include "nsIStreamListener.h"
00056 #include "nsIMIMEService.h"
00057 #include "nsILoadGroup.h"
00058 #include "nsIWebProgressListener.h"
00059 #include "nsITransfer.h"
00060 #include "nsReadableUtils.h"
00061 #include "nsIRequest.h"
00062 #include "nsDirectoryServiceDefs.h"
00063 #include "nsIInterfaceRequestor.h"
00064 #include "nsAutoPtr.h"
00065 
00066 // used to manage our in memory data source of helper applications
00067 #include "nsRDFCID.h"
00068 #include "rdf.h"
00069 #include "nsIRDFService.h"
00070 #include "nsIRDFRemoteDataSource.h"
00071 #include "nsHelperAppRDF.h"
00072 #include "nsIMIMEInfo.h"
00073 #include "nsDirectoryServiceDefs.h"
00074 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
00075 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
00076 #include "nsIHelperAppLauncherDialog.h"
00077 #include "nsNetUtil.h"
00078 #include "nsIIOService.h"
00079 #include "nsNetCID.h"
00080 #include "nsChannelProperties.h"
00081 
00082 #include "nsMimeTypes.h"
00083 // used for header disposition information.
00084 #include "nsIHttpChannel.h"
00085 #include "nsIEncodedChannel.h"
00086 #include "nsIMultiPartChannel.h"
00087 #include "nsIObserverService.h" // so we can be a profile change observer
00088 #include "nsIPropertyBag2.h" // for the 64-bit content length
00089 
00090 #if defined(XP_MAC) || defined (XP_MACOSX)
00091 #include "nsILocalFileMac.h"
00092 #include "nsIInternetConfigService.h"
00093 #include "nsIAppleFileDecoder.h"
00094 #elif defined(XP_OS2)
00095 #include "nsILocalFileOS2.h"
00096 #endif
00097 
00098 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
00099 #include "nsEscape.h"
00100 
00101 #include "nsIStringBundle.h" // XXX needed to localize error msgs
00102 #include "nsIPrompt.h"
00103 
00104 #include "nsITextToSubURI.h" // to unescape the filename
00105 #include "nsIMIMEHeaderParam.h"
00106 
00107 #include "nsIPrefService.h"
00108 #include "nsIWindowWatcher.h"
00109 
00110 #include "nsIGlobalHistory.h" // to mark downloads as visited
00111 #include "nsIGlobalHistory2.h" // to mark downloads as visited
00112 
00113 #include "nsIDOMWindow.h"
00114 #include "nsIDOMWindowInternal.h"
00115 #include "nsIDocShell.h"
00116 
00117 #include "nsCRT.h"
00118 
00119 #ifdef PR_LOGGING
00120 PRLogModuleInfo* nsExternalHelperAppService::mLog = nsnull;
00121 #endif
00122 
00123 // Using level 3 here because the OSHelperAppServices use a log level
00124 // of PR_LOG_DEBUG (4), and we want less detailed output here
00125 // Using 3 instead of PR_LOG_WARN because we don't output warnings
00126 #define LOG(args) PR_LOG(mLog, 3, args)
00127 #define LOG_ENABLED() PR_LOG_TEST(mLog, 3)
00128 
00129 static const char NEVER_ASK_PREF_BRANCH[] = "browser.helperApps.neverAsk.";
00130 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] = "saveToDisk";
00131 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[]    = "openFile";
00132 
00133 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
00134 static NS_DEFINE_CID(kPluginManagerCID, NS_PLUGINMANAGER_CID);
00135 
00139 static nsExternalHelperAppService* sSrv;
00140 
00141 // Helper functions for Content-Disposition headers
00142 
00150 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
00151                                  nsAString& aResult)
00152 {
00153   // First, we need a charset
00154   nsCAutoString originCharset;
00155   nsresult rv = aURI->GetOriginCharset(originCharset);
00156   NS_ENSURE_SUCCESS(rv, rv);
00157 
00158   // Now, we need the unescaper
00159   nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
00160   NS_ENSURE_SUCCESS(rv, rv);
00161 
00162   return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
00163 }
00164 
00174 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
00175                                  nsACString& aResult)
00176 {
00177   nsAutoString result;
00178   nsresult rv = UnescapeFragment(aFragment, aURI, result);
00179   if (NS_SUCCEEDED(rv))
00180     CopyUTF16toUTF8(result, aResult);
00181   return rv;
00182 }
00183 
00189 static void ExtractDisposition(nsIChannel* aChannel, nsACString& aDisposition)
00190 {
00191   aDisposition.Truncate();
00192   // First see whether this is an http channel
00193   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
00194   if (httpChannel) 
00195   {
00196     httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"), aDisposition);
00197   }
00198   if (aDisposition.IsEmpty())
00199   {
00200     nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(aChannel));
00201     if (multipartChannel)
00202     {
00203       multipartChannel->GetContentDisposition(aDisposition);
00204     }
00205   }
00206 
00207 }
00208 
00217 static void GetFilenameFromDisposition(nsAString& aFilename,
00218                                        const nsACString& aDisposition,
00219                                        nsIURI* aURI = nsnull,
00220                                        nsIMIMEHeaderParam* aMIMEHeaderParam = nsnull)
00221 {
00222   aFilename.Truncate();
00223   nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar(aMIMEHeaderParam);
00224   if (!mimehdrpar) {
00225     mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID);
00226     if (!mimehdrpar)
00227       return;
00228   }
00229 
00230   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
00231 
00232   nsCAutoString fallbackCharset;
00233   if (url)
00234     url->GetOriginCharset(fallbackCharset);
00235   // Get the value of 'filename' parameter
00236   nsresult rv = mimehdrpar->GetParameter(aDisposition, "filename", fallbackCharset, 
00237                                          PR_TRUE, nsnull, aFilename);
00238   if (NS_FAILED(rv) || aFilename.IsEmpty())
00239     // Try 'name' parameter, instead.
00240     rv = mimehdrpar->GetParameter(aDisposition, "name", fallbackCharset, PR_TRUE, 
00241                                   nsnull, aFilename);
00242 }
00243 
00262 static PRBool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
00263                                                  nsString& aFileName,
00264                                                  nsCString& aExtension,
00265                                                  PRBool aAllowURLExtension = PR_TRUE)
00266 {
00267   aExtension.Truncate();
00268   /*
00269    * If the channel is an http or part of a multipart channel and we
00270    * have a content disposition header set, then use the file name
00271    * suggested there as the preferred file name to SUGGEST to the
00272    * user.  we shouldn't actually use that without their
00273    * permission... otherwise just use our temp file
00274    */
00275   nsCAutoString disp;
00276   ExtractDisposition(aChannel, disp);
00277   PRBool handleExternally = PR_FALSE;
00278   nsCOMPtr<nsIURI> uri;
00279   nsresult rv;
00280   aChannel->GetURI(getter_AddRefs(uri));
00281   // content-disposition: has format:
00282   // disposition-type < ; name=value >* < ; filename=value > < ; name=value >*
00283   if (!disp.IsEmpty()) 
00284   {
00285     nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
00286     if (NS_FAILED(rv))
00287       return PR_FALSE;
00288 
00289     nsCAutoString fallbackCharset;
00290     uri->GetOriginCharset(fallbackCharset);
00291     // Get the disposition type
00292     nsAutoString dispToken;
00293     rv = mimehdrpar->GetParameter(disp, "", fallbackCharset, PR_TRUE, 
00294                                   nsnull, dispToken);
00295     // RFC 2183, section 2.8 says that an unknown disposition
00296     // value should be treated as "attachment"
00297     // XXXbz this code is duplicated in nsDocumentOpenInfo::DispatchContent.
00298     // Factor it out!  Maybe store it in the nsDocumentOpenInfo?
00299     if (NS_FAILED(rv) || 
00300         (// Some broken sites just send
00301          // Content-Disposition: ; filename="file"
00302          // screen those out here.
00303          !dispToken.IsEmpty() &&
00304          !dispToken.LowerCaseEqualsLiteral("inline") &&
00305         // Broken sites just send
00306         // Content-Disposition: filename="file"
00307         // without a disposition token... screen those out.
00308         !dispToken.EqualsIgnoreCase("filename", 8)) &&
00309         // Also in use is Content-Disposition: name="file"
00310         !dispToken.EqualsIgnoreCase("name", 4)) 
00311     {
00312       // We have a content-disposition of "attachment" or unknown
00313       handleExternally = PR_TRUE;
00314     }
00315 
00316     // We may not have a disposition type listed; some servers suck.
00317     // But they could have listed a filename anyway.
00318     GetFilenameFromDisposition(aFileName, disp, uri, mimehdrpar);
00319 
00320   } // we had a disp header 
00321 
00322   // If the disposition header didn't work, try the filename from nsIURL
00323   nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
00324   if (url && aFileName.IsEmpty())
00325   {
00326     if (aAllowURLExtension) {
00327       url->GetFileExtension(aExtension);
00328       UnescapeFragment(aExtension, url, aExtension);
00329 
00330       // Windows ignores terminating dots. So we have to as well, so
00331       // that our security checks do "the right thing"
00332       // In case the aExtension consisted only of the dot, the code below will
00333       // extract an aExtension from the filename
00334       aExtension.Trim(".", PR_FALSE);
00335     }
00336 
00337     // try to extract the file name from the url and use that as a first pass as the
00338     // leaf name of our temp file...
00339     nsCAutoString leafName;
00340     url->GetFileName(leafName);
00341     if (!leafName.IsEmpty())
00342     {
00343       rv = UnescapeFragment(leafName, url, aFileName);
00344       if (NS_FAILED(rv))
00345       {
00346         CopyUTF8toUTF16(leafName, aFileName); // use escaped name
00347       }
00348     }
00349   }
00350 
00351   // Extract Extension, if we have a filename; otherwise,
00352   // truncate the string
00353   if (aExtension.IsEmpty()) {
00354     if (!aFileName.IsEmpty())
00355     {
00356       // Windows ignores terminating dots. So we have to as well, so
00357       // that our security checks do "the right thing"
00358       aFileName.Trim(".", PR_FALSE);
00359 
00360       // XXX RFindCharInReadable!!
00361       nsAutoString fileNameStr(aFileName);
00362       PRInt32 idx = fileNameStr.RFindChar(PRUnichar('.'));
00363       if (idx != kNotFound)
00364         CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
00365     }
00366   }
00367 
00368 
00369   return handleExternally;
00370 }
00371 
00376 struct nsDefaultMimeTypeEntry {
00377   const char* mMimeType;
00378   const char* mFileExtension;
00379 };
00380 
00385 static nsDefaultMimeTypeEntry defaultMimeEntries [] = 
00386 {
00387   // The following are those extensions that we're asked about during startup,
00388   // sorted by order used
00389   { IMAGE_GIF, "gif" },
00390   { TEXT_XML, "xml" },
00391   { APPLICATION_RDF, "rdf" },
00392   { TEXT_XUL, "xul" },
00393   { IMAGE_PNG, "png" },
00394   // -- end extensions used during startup
00395   { TEXT_CSS, "css" },
00396   { IMAGE_JPG, "jpeg" },
00397   { IMAGE_JPG, "jpg" },
00398   { TEXT_HTML, "html" },
00399   { TEXT_HTML, "htm" },
00400   { APPLICATION_XPINSTALL, "xpi" },
00401   { "application/xhtml+xml", "xhtml" },
00402   { "application/xhtml+xml", "xht" },
00403   { TEXT_PLAIN, "txt" }
00404 };
00405 
00410 struct nsExtraMimeTypeEntry {
00411   const char* mMimeType; 
00412   const char* mFileExtensions;
00413   const char* mDescription;
00414   PRUint32 mMactype;
00415   PRUint32 mMacCreator;
00416 };
00417 
00418 #if defined(XP_MACOSX) || defined(XP_MAC)
00419 #define MAC_TYPE(x) x
00420 #else
00421 #define MAC_TYPE(x) 0
00422 #endif
00423 
00431 static nsExtraMimeTypeEntry extraMimeEntries [] =
00432 {
00433 #if defined(VMS)
00434   { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File", 0, 0 },
00435 #elif defined(XP_MAC) || defined (XP_MACOSX)// don't define .bin on the mac...use internet config to look that up...
00436   { APPLICATION_OCTET_STREAM, "exe,com", "Binary File", 0, 0 },
00437 #else
00438   { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File", 0, 0 },
00439 #endif
00440   { APPLICATION_GZIP2, "gz", "gzip", 0, 0 },
00441   { "application/x-arj", "arj", "ARJ file", 0,0 },
00442   { APPLICATION_XPINSTALL, "xpi", "XPInstall Install", MAC_TYPE('xpi*'), MAC_TYPE('MOSS') },
00443   { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File", 0, 0 },
00444   { APPLICATION_JAVASCRIPT, "js", "Javascript Source File", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00445   { IMAGE_ART, "art", "ART Image", 0, 0 },
00446   { IMAGE_BMP, "bmp", "BMP Image", 0, 0 },
00447   { IMAGE_GIF, "gif", "GIF Image", 0,0 },
00448   { IMAGE_ICO, "ico,cur", "ICO Image", 0, 0 },
00449   { IMAGE_JPG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image", 0, 0 },
00450   { IMAGE_PNG, "png", "PNG Image", 0, 0 },
00451   { IMAGE_TIFF, "tiff,tif", "TIFF Image", 0, 0 },
00452   { IMAGE_XBM, "xbm", "XBM Image", 0, 0 },
00453   { "image/svg+xml", "svg", "Scalable Vector Graphics", MAC_TYPE('svg '), MAC_TYPE('ttxt') },
00454   { MESSAGE_RFC822, "eml", "RFC-822 data", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
00455   { TEXT_PLAIN, "txt,text", "Text File", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00456   { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
00457   { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00458   { APPLICATION_RDF, "rdf", "Resource Description Framework", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00459   { TEXT_XUL, "xul", "XML-Based User Interface Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00460   { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00461   { TEXT_CSS, "css", "Style Sheet", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
00462 };
00463 
00464 #undef MAC_TYPE
00465 
00470 static nsDefaultMimeTypeEntry nonDecodableExtensions [] = {
00471   { APPLICATION_GZIP, "gz" }, 
00472   { APPLICATION_GZIP, "tgz" },
00473   { APPLICATION_ZIP, "zip" },
00474   { APPLICATION_COMPRESS, "z" },
00475   { APPLICATION_GZIP, "svgz" }
00476 };
00477 
00478 NS_IMPL_ISUPPORTS6(
00479   nsExternalHelperAppService,
00480   nsIExternalHelperAppService,
00481   nsPIExternalAppLauncher,
00482   nsIExternalProtocolService,
00483   nsIMIMEService,
00484   nsIObserver,
00485   nsISupportsWeakReference)
00486 
00487 nsExternalHelperAppService::nsExternalHelperAppService()
00488 : mDataSourceInitialized(PR_FALSE)
00489 {
00490   sSrv = this;
00491 }
00492 nsresult nsExternalHelperAppService::Init()
00493 {
00494   // Add an observer for profile change
00495   nsresult rv = NS_OK;
00496   nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1", &rv);
00497   NS_ENSURE_SUCCESS(rv, rv);
00498 
00499 #ifdef PR_LOGGING
00500   if (!mLog) {
00501     mLog = PR_NewLogModule("HelperAppService");
00502     if (!mLog)
00503       return NS_ERROR_OUT_OF_MEMORY;
00504   }
00505 #endif
00506 
00507   // turn PR_Now() into microseconds since epoch and seed rand with that.
00508   double fpTime;
00509   LL_L2D(fpTime, PR_Now());
00510   srand((uint)(fpTime));
00511 
00512   return obs->AddObserver(this, "profile-before-change", PR_TRUE);
00513 }
00514 
00515 nsExternalHelperAppService::~nsExternalHelperAppService()
00516 {
00517   sSrv = nsnull;
00518 }
00519 
00520 nsresult nsExternalHelperAppService::InitDataSource()
00521 {
00522   nsresult rv = NS_OK;
00523 
00524   // don't re-initialize the data source if we've already done so...
00525   if (mDataSourceInitialized)
00526     return NS_OK;
00527 
00528   nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
00529   NS_ENSURE_SUCCESS(rv, rv);
00530 
00531   // Get URI of the mimeTypes.rdf data source.  Do this the same way it's done in
00532   // pref-applications-edit.xul, for example, to ensure we get the same data source!
00533   // Note that the way it was done previously (using nsIFileSpec) worked better, but it
00534   // produced a different uri (it had "file:///C|" instead of "file:///C:".  What can you do?
00535   nsCOMPtr<nsIFile> mimeTypesFile;
00536   rv = NS_GetSpecialDirectory(NS_APP_USER_MIMETYPES_50_FILE, getter_AddRefs(mimeTypesFile));
00537   NS_ENSURE_SUCCESS(rv, rv);
00538   
00539   // Get file url spec to be used to initialize the DS.
00540   nsCAutoString urlSpec;
00541   rv = NS_GetURLSpecFromFile(mimeTypesFile, urlSpec);
00542   NS_ENSURE_SUCCESS(rv, rv);
00543 
00544   // Get the data source; if it is going to be created, then load is synchronous.
00545   rv = rdf->GetDataSourceBlocking( urlSpec.get(), getter_AddRefs( mOverRideDataSource ) );
00546   NS_ENSURE_SUCCESS(rv, rv);
00547 
00548   // initialize our resources if we haven't done so already...
00549   if (!kNC_Description)
00550   {
00551     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_DESCRIPTION),
00552                      getter_AddRefs(kNC_Description));
00553     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_VALUE),
00554                      getter_AddRefs(kNC_Value));
00555     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FILEEXTENSIONS),
00556                      getter_AddRefs(kNC_FileExtensions));
00557     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_PATH),
00558                      getter_AddRefs(kNC_Path));
00559     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SAVETODISK),
00560                      getter_AddRefs(kNC_SaveToDisk));
00561     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_USESYSTEMDEFAULT),
00562                      getter_AddRefs(kNC_UseSystemDefault));
00563     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_HANDLEINTERNAL),
00564                      getter_AddRefs(kNC_HandleInternal));
00565     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ALWAYSASK),
00566                      getter_AddRefs(kNC_AlwaysAsk));  
00567     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_PRETTYNAME),
00568                      getter_AddRefs(kNC_PrettyName));  
00569   }
00570   
00571   mDataSourceInitialized = PR_TRUE;
00572 
00573   return rv;
00574 }
00575 
00576 NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
00577                                                     nsIRequest *aRequest,
00578                                                     nsIInterfaceRequestor *aWindowContext,
00579                                                     nsIStreamListener ** aStreamListener)
00580 {
00581   nsAutoString fileName;
00582   nsCAutoString fileExtension;
00583   PRUint32 reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
00584   nsresult rv;
00585 
00586   // Get the file extension and name that we will need later
00587   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
00588   if (channel) {
00589     // Check if we have a POST request, in which case we don't want to use
00590     // the url's extension
00591     PRBool allowURLExt = PR_TRUE;
00592     nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
00593     if (httpChan) {
00594       nsCAutoString requestMethod;
00595       httpChan->GetRequestMethod(requestMethod);
00596       allowURLExt = !requestMethod.Equals("POST");
00597     }
00598 
00599     nsCOMPtr<nsIURI> uri;
00600     channel->GetURI(getter_AddRefs(uri));
00601 
00602     // Check if we had a query string - we don't want to check the URL
00603     // extension if a query is present in the URI
00604     // If we already know we don't want to check the URL extension, don't
00605     // bother checking the query
00606     if (uri && allowURLExt) {
00607       nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
00608 
00609       if (url) {
00610         nsCAutoString query;
00611 
00612         // We only care about the query for HTTP and HTTPS URLs
00613         PRBool isHTTP, isHTTPS;
00614         rv = uri->SchemeIs("http", &isHTTP);
00615         if (NS_FAILED(rv))
00616           isHTTP = PR_FALSE;
00617         rv = uri->SchemeIs("https", &isHTTPS);
00618         if (NS_FAILED(rv))
00619           isHTTPS = PR_FALSE;
00620 
00621         if (isHTTP || isHTTPS)
00622           url->GetQuery(query);
00623 
00624         // Only get the extension if the query is empty; if it isn't, then the
00625         // extension likely belongs to a cgi script and isn't helpful
00626         allowURLExt = query.IsEmpty();
00627       }
00628     }
00629     // Extract name & extension
00630     PRBool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
00631                                                              fileExtension,
00632                                                              allowURLExt);
00633     LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
00634          fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
00635          isAttachment));
00636     if (isAttachment)
00637       reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
00638   }
00639 
00640   LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
00641        PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
00642 
00643   // Try to find a mime object by looking at the mime type/extension
00644   nsCOMPtr<nsIMIMEInfo> mimeInfo;
00645   if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
00646     nsCAutoString mimeType;
00647     if (!fileExtension.IsEmpty()) {
00648       GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
00649       if (mimeInfo) {
00650         mimeInfo->GetMIMEType(mimeType);
00651 
00652         LOG(("OS-Provided mime type '%s' for extension '%s'\n", 
00653              mimeType.get(), fileExtension.get()));
00654       }
00655     }
00656 
00657     if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
00658       // Extension lookup gave us no useful match
00659       GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
00660                               getter_AddRefs(mimeInfo));
00661       mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
00662     }
00663     if (channel)
00664       channel->SetContentType(mimeType);
00665     // Don't overwrite SERVERREQUEST
00666     if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
00667       reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
00668   } 
00669   else {
00670     GetFromTypeAndExtension(aMimeContentType, fileExtension,
00671                             getter_AddRefs(mimeInfo));
00672   } 
00673   LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
00674 
00675   // No mimeinfo -> we can't continue. probably OOM.
00676   if (!mimeInfo)
00677     return NS_ERROR_OUT_OF_MEMORY;
00678 
00679   *aStreamListener = nsnull;
00680   // We want the mimeInfo's primary extension to pass it to
00681   // nsExternalAppHandler
00682   nsCAutoString buf;
00683   mimeInfo->GetPrimaryExtension(buf);
00684 
00685   nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
00686                                                             buf,
00687                                                             aWindowContext,
00688                                                             fileName,
00689                                                             reason);
00690   if (!handler)
00691     return NS_ERROR_OUT_OF_MEMORY;
00692   NS_ADDREF(*aStreamListener = handler);
00693   
00694   return NS_OK;
00695 }
00696 
00697 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
00698                                                                     const nsACString& aEncodingType,
00699                                                                     PRBool *aApplyDecoding)
00700 {
00701   *aApplyDecoding = PR_TRUE;
00702   PRUint32 i;
00703   for(i = 0; i < NS_ARRAY_LENGTH(nonDecodableExtensions); ++i) {
00704     if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
00705         aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
00706       *aApplyDecoding = PR_FALSE;
00707       break;
00708     }
00709   }
00710   return NS_OK;
00711 }
00712 
00713 nsresult nsExternalHelperAppService::FillTopLevelProperties(nsIRDFResource * aContentTypeNodeResource, 
00714                                                             nsIRDFService * aRDFService, nsIMIMEInfo * aMIMEInfo)
00715 {
00716   nsresult rv = NS_OK;
00717   nsCOMPtr<nsIRDFNode> target;
00718   nsCOMPtr<nsIRDFLiteral> literal;
00719   const PRUnichar * stringValue;
00720   
00721   rv = InitDataSource();
00722   if (NS_FAILED(rv)) return NS_OK;
00723 
00724   // set the pretty name description, if nonempty
00725   FillLiteralValueFromTarget(aContentTypeNodeResource,kNC_Description, &stringValue);
00726   if (stringValue && *stringValue)
00727     aMIMEInfo->SetDescription(nsDependentString(stringValue));
00728 
00729   // now iterate over all the file type extensions...
00730   nsCOMPtr<nsISimpleEnumerator> fileExtensions;
00731   mOverRideDataSource->GetTargets(aContentTypeNodeResource, kNC_FileExtensions, PR_TRUE, getter_AddRefs(fileExtensions));
00732 
00733   PRBool hasMoreElements = PR_FALSE;
00734   nsCAutoString fileExtension; 
00735   nsCOMPtr<nsISupports> element;
00736 
00737   if (fileExtensions)
00738   {
00739     fileExtensions->HasMoreElements(&hasMoreElements);
00740     while (hasMoreElements)
00741     { 
00742       fileExtensions->GetNext(getter_AddRefs(element));
00743       if (element)
00744       {
00745         literal = do_QueryInterface(element);
00746         if (!literal) return NS_ERROR_FAILURE;
00747 
00748         literal->GetValueConst(&stringValue);
00749         CopyUTF16toUTF8(stringValue, fileExtension);
00750         if (!fileExtension.IsEmpty())
00751           aMIMEInfo->AppendExtension(fileExtension);
00752       }
00753   
00754       fileExtensions->HasMoreElements(&hasMoreElements);
00755     } // while we have more extensions to parse....
00756   }
00757 
00758   return rv;
00759 }
00760 
00761 nsresult nsExternalHelperAppService::FillLiteralValueFromTarget(nsIRDFResource * aSource, nsIRDFResource * aProperty, const PRUnichar ** aLiteralValue)
00762 {
00763   nsresult rv = NS_OK;
00764   nsCOMPtr<nsIRDFLiteral> literal;
00765   nsCOMPtr<nsIRDFNode> target;
00766 
00767   *aLiteralValue = nsnull;
00768   rv = InitDataSource();
00769   if (NS_FAILED(rv)) return rv;
00770 
00771   mOverRideDataSource->GetTarget(aSource, aProperty, PR_TRUE, getter_AddRefs(target));
00772   if (target)
00773   {
00774     literal = do_QueryInterface(target);    
00775     if (!literal)
00776       return NS_ERROR_FAILURE;
00777     literal->GetValueConst(aLiteralValue);
00778   }
00779   else
00780     rv = NS_ERROR_FAILURE;
00781 
00782   return rv;
00783 }
00784 
00785 nsresult nsExternalHelperAppService::FillContentHandlerProperties(const char * aContentType, 
00786                                                                   nsIRDFResource * aContentTypeNodeResource, 
00787                                                                   nsIRDFService * aRDFService, 
00788                                                                   nsIMIMEInfo * aMIMEInfo)
00789 {
00790   nsCOMPtr<nsIRDFNode> target;
00791   nsCOMPtr<nsIRDFLiteral> literal;
00792   const PRUnichar * stringValue = nsnull;
00793   nsresult rv = NS_OK;
00794 
00795   rv = InitDataSource();
00796   if (NS_FAILED(rv)) return rv;
00797 
00798   nsCAutoString contentTypeHandlerNodeName(NC_CONTENT_NODE_HANDLER_PREFIX);
00799   contentTypeHandlerNodeName.Append(aContentType);
00800 
00801   nsCOMPtr<nsIRDFResource> contentTypeHandlerNodeResource;
00802   aRDFService->GetResource(contentTypeHandlerNodeName, getter_AddRefs(contentTypeHandlerNodeResource));
00803   NS_ENSURE_TRUE(contentTypeHandlerNodeResource, NS_ERROR_FAILURE); // that's not good! we have an error in the rdf file
00804 
00805   // now process the application handler information
00806   aMIMEInfo->SetPreferredAction(nsIMIMEInfo::useHelperApp);
00807 
00808   // save to disk
00809   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_SaveToDisk, &stringValue);
00810   NS_NAMED_LITERAL_STRING(trueString, "true");
00811   NS_NAMED_LITERAL_STRING(falseString, "false");
00812   if (stringValue && trueString.Equals(stringValue))
00813        aMIMEInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
00814 
00815   // use system default
00816   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_UseSystemDefault, &stringValue);
00817   if (stringValue && trueString.Equals(stringValue))
00818       aMIMEInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
00819 
00820   // handle internal
00821   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_HandleInternal, &stringValue);
00822   if (stringValue && trueString.Equals(stringValue))
00823        aMIMEInfo->SetPreferredAction(nsIMIMEInfo::handleInternally);
00824   
00825   // always ask
00826   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_AlwaysAsk, &stringValue);
00827   // Only skip asking if we are absolutely sure the user does not want
00828   // to be asked.  Any sort of bofus data should mean we ask.
00829   aMIMEInfo->SetAlwaysAskBeforeHandling(!stringValue ||
00830                                         !falseString.Equals(stringValue));
00831 
00832 
00833   // now digest the external application information
00834 
00835   nsCAutoString externalAppNodeName (NC_CONTENT_NODE_EXTERNALAPP_PREFIX);
00836   externalAppNodeName.Append(aContentType);
00837   nsCOMPtr<nsIRDFResource> externalAppNodeResource;
00838   aRDFService->GetResource(externalAppNodeName, getter_AddRefs(externalAppNodeResource));
00839 
00840   // Clear out any possibly set preferred application, to match the datasource
00841   aMIMEInfo->SetApplicationDescription(EmptyString());
00842   aMIMEInfo->SetPreferredApplicationHandler(nsnull);
00843   if (externalAppNodeResource)
00844   {
00845     FillLiteralValueFromTarget(externalAppNodeResource, kNC_PrettyName, &stringValue);
00846     if (stringValue)
00847       aMIMEInfo->SetApplicationDescription(nsDependentString(stringValue));
00848  
00849     FillLiteralValueFromTarget(externalAppNodeResource, kNC_Path, &stringValue);
00850     if (stringValue && stringValue[0])
00851     {
00852       nsCOMPtr<nsIFile> application;
00853       GetFileTokenForPath(stringValue, getter_AddRefs(application));
00854       if (application)
00855         aMIMEInfo->SetPreferredApplicationHandler(application);
00856     }
00857   }
00858 
00859   return rv;
00860 }
00861 
00862 PRBool nsExternalHelperAppService::MIMETypeIsInDataSource(const char * aContentType)
00863 {
00864   nsresult rv = InitDataSource();
00865   if (NS_FAILED(rv)) return PR_FALSE;
00866   
00867   if (mOverRideDataSource)
00868   {
00869     // Get the RDF service.
00870     nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
00871     if (NS_FAILED(rv)) return PR_FALSE;
00872     
00873     // Build uri for the mimetype resource.
00874     nsCAutoString contentTypeNodeName(NC_CONTENT_NODE_PREFIX);
00875     nsCAutoString contentType(aContentType);
00876     ToLowerCase(contentType);
00877     contentTypeNodeName.Append(contentType);
00878     
00879     // Get the mime type resource.
00880     nsCOMPtr<nsIRDFResource> contentTypeNodeResource;
00881     rv = rdf->GetResource(contentTypeNodeName, getter_AddRefs(contentTypeNodeResource));
00882     if (NS_FAILED(rv)) return PR_FALSE;
00883     
00884     // Test that there's a #value arc from the mimetype resource to the mimetype literal string.
00885     nsCOMPtr<nsIRDFLiteral> mimeLiteral;
00886     NS_ConvertUTF8toUTF16 mimeType(contentType);
00887     rv = rdf->GetLiteral( mimeType.get(), getter_AddRefs( mimeLiteral ) );
00888     if (NS_FAILED(rv)) return PR_FALSE;
00889     
00890     PRBool exists = PR_FALSE;
00891     rv = mOverRideDataSource->HasAssertion(contentTypeNodeResource, kNC_Value, mimeLiteral, PR_TRUE, &exists );
00892     
00893     if (NS_SUCCEEDED(rv) && exists) return PR_TRUE;
00894   }
00895   return PR_FALSE;
00896 }
00897 
00898 nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromDS(const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
00899 {
00900   NS_ENSURE_ARG_POINTER(aMIMEInfo);
00901   nsresult rv = InitDataSource();
00902   if (NS_FAILED(rv)) return rv;
00903 
00904   // can't do anything if we have no datasource...
00905   if (!mOverRideDataSource)
00906     return NS_ERROR_FAILURE;
00907 
00908   // Get the RDF service.
00909   nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
00910   NS_ENSURE_SUCCESS(rv, rv);
00911 
00912   
00913   // Build uri for the mimetype resource.
00914   nsCAutoString contentTypeNodeName(NC_CONTENT_NODE_PREFIX);
00915   nsCAutoString contentType(aContentType);
00916   ToLowerCase(contentType);
00917   contentTypeNodeName.Append(contentType);
00918 
00919   // Get the mime type resource.
00920   nsCOMPtr<nsIRDFResource> contentTypeNodeResource;
00921   rv = rdf->GetResource(contentTypeNodeName, getter_AddRefs(contentTypeNodeResource));
00922   NS_ENSURE_SUCCESS(rv, rv);
00923 
00924   // we need a way to determine if this content type resource is really in the graph or not...
00925   // ...Test that there's a #value arc from the mimetype resource to the mimetype literal string.
00926   nsCOMPtr<nsIRDFLiteral> mimeLiteral;
00927   NS_ConvertUTF8toUTF16 mimeType(contentType);
00928   rv = rdf->GetLiteral( mimeType.get(), getter_AddRefs( mimeLiteral ) );
00929   NS_ENSURE_SUCCESS(rv, rv);
00930   
00931   PRBool exists = PR_FALSE;
00932   rv = mOverRideDataSource->HasAssertion(contentTypeNodeResource, kNC_Value, mimeLiteral, PR_TRUE, &exists );
00933 
00934   if (NS_SUCCEEDED(rv) && exists)
00935   {
00936      // fill the mimeinfo in based on the values from the data source
00937      rv = FillTopLevelProperties(contentTypeNodeResource, rdf, aMIMEInfo);
00938      NS_ENSURE_SUCCESS(rv, rv);
00939      rv = FillContentHandlerProperties(contentType.get(), contentTypeNodeResource, rdf, aMIMEInfo);
00940 
00941   } // if we have a node in the graph for this content type
00942   // If we had success, but entry doesn't exist, we don't want to return success
00943   else if (NS_SUCCEEDED(rv)) {
00944     rv = NS_ERROR_NOT_AVAILABLE;
00945   }
00946 
00947   return rv;
00948 }
00949 
00950 nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromDS(const nsACString& aFileExtension, nsIMIMEInfo * aMIMEInfo)
00951 {
00952   nsCAutoString type;
00953   PRBool found = GetTypeFromDS(aFileExtension, type);
00954   if (!found)
00955     return NS_ERROR_NOT_AVAILABLE;
00956 
00957   return GetMIMEInfoForMimeTypeFromDS(type, aMIMEInfo);
00958 }
00959 
00960 PRBool nsExternalHelperAppService::GetTypeFromDS(const nsACString& aExtension,
00961                                                  nsACString& aType)
00962 {
00963   nsresult rv = InitDataSource();
00964   if (NS_FAILED(rv))
00965     return PR_FALSE;
00966 
00967   // Can't do anything without a datasource
00968   if (!mOverRideDataSource)
00969     return PR_FALSE;
00970 
00971   // Get the RDF service.
00972   nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
00973   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00974 
00975   NS_ConvertUTF8toUTF16 extension(aExtension);
00976   ToLowerCase(extension);
00977   nsCOMPtr<nsIRDFLiteral> extensionLiteral;
00978   rv = rdf->GetLiteral(extension.get(), getter_AddRefs(extensionLiteral));
00979   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00980 
00981   nsCOMPtr<nsIRDFResource> contentTypeNodeResource;
00982   rv = mOverRideDataSource->GetSource(kNC_FileExtensions,
00983                                       extensionLiteral,
00984                                       PR_TRUE,
00985                                       getter_AddRefs(contentTypeNodeResource));
00986   nsCAutoString contentTypeStr;
00987   if (NS_SUCCEEDED(rv) && contentTypeNodeResource)
00988   {
00989     const PRUnichar* contentType = nsnull;
00990     rv = FillLiteralValueFromTarget(contentTypeNodeResource, kNC_Value, &contentType);
00991     if (contentType) {
00992       LossyCopyUTF16toASCII(contentType, aType);
00993       return PR_TRUE;
00994     }
00995   }  // if we have a node in the graph for this extension
00996   return PR_FALSE;
00997 }
00998 
00999 nsresult nsExternalHelperAppService::GetFileTokenForPath(const PRUnichar * aPlatformAppPath,
01000                                                          nsIFile ** aFile)
01001 {
01002   nsDependentString platformAppPath(aPlatformAppPath);
01003   // First, check if we have an absolute path
01004   nsILocalFile* localFile = nsnull;
01005   nsresult rv = NS_NewLocalFile(platformAppPath, PR_TRUE, &localFile);
01006   if (NS_SUCCEEDED(rv)) {
01007     *aFile = localFile;
01008     PRBool exists;
01009     if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
01010       NS_RELEASE(*aFile);
01011       return NS_ERROR_FILE_NOT_FOUND;
01012     }
01013     return NS_OK;
01014   }
01015 
01016 
01017   // Second, check if file exists in mozilla program directory
01018   rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
01019   if (NS_SUCCEEDED(rv)) {
01020     rv = (*aFile)->Append(platformAppPath);
01021     if (NS_SUCCEEDED(rv)) {
01022       PRBool exists = PR_FALSE;
01023       rv = (*aFile)->Exists(&exists);
01024       if (NS_SUCCEEDED(rv) && exists)
01025         return NS_OK;
01026     }
01027     NS_RELEASE(*aFile);
01028   }
01029 
01030 
01031   return NS_ERROR_NOT_AVAILABLE;
01032 }
01033 
01035 // begin external protocol service default implementation...
01037 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
01038                                                                         PRBool * aHandlerExists)
01039 {
01040   // this method should only be implemented by each OS specific implementation of this service.
01041   *aHandlerExists = PR_FALSE;
01042   return NS_ERROR_NOT_IMPLEMENTED;
01043 }
01044 
01045 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, PRBool * aResult)
01046 {
01047   // by default, no protocol is exposed.  i.e., by default all link clicks must
01048   // go through the external protocol service.  most applications override this
01049   // default behavior.
01050   *aResult = PR_FALSE;
01051 
01052   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01053   if (prefs)
01054   {
01055     PRBool val;
01056     nsresult rv;
01057 
01058     // check the per protocol setting first.  it always takes precidence.
01059     // if not set, then use the global setting.
01060 
01061     nsCAutoString name;
01062     name = NS_LITERAL_CSTRING("network.protocol-handler.expose.")
01063          + nsDependentCString(aProtocolScheme);
01064     rv = prefs->GetBoolPref(name.get(), &val);
01065     if (NS_SUCCEEDED(rv))
01066     {
01067       *aResult = val;
01068     }
01069     else
01070     {
01071       rv = prefs->GetBoolPref("network.protocol-handler.expose-all", &val);
01072       if (NS_SUCCEEDED(rv) && val)
01073         *aResult = PR_TRUE;
01074     }
01075   }
01076   return NS_OK;
01077 }
01078 
01079 NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
01080 {
01081   return LoadURI(aURL, nsnull);
01082 }
01083 
01084 
01085 //  nsExternalHelperAppService::LoadURI() may now pose a confirm dialog
01086 //  that existing callers aren't expecting. We must do it on an event
01087 //  callback to make sure we don't hang someone up.
01088 
01089 struct extLoadRequest : PLEvent {
01090     nsCOMPtr<nsIURI>        uri;
01091     nsCOMPtr<nsIPrompt>     prompt;
01092 };
01093 
01094 void *PR_CALLBACK
01095 nsExternalHelperAppService::handleExternalLoadEvent(PLEvent *event)
01096 {
01097   extLoadRequest* req = NS_STATIC_CAST(extLoadRequest*, event);
01098   if (req && sSrv && sSrv->isExternalLoadOK(req->uri, req->prompt))
01099     sSrv->LoadUriInternal(req->uri);
01100 
01101   return nsnull;
01102 }
01103 
01104 static void PR_CALLBACK destroyExternalLoadEvent(PLEvent *event)
01105 {
01106   delete NS_STATIC_CAST(extLoadRequest*, event);
01107 }
01108 
01109 NS_IMETHODIMP nsExternalHelperAppService::LoadURI(nsIURI * aURL, nsIPrompt * aPrompt)
01110 {
01111   nsCAutoString spec;
01112   aURL->GetSpec(spec);
01113 
01114   spec.ReplaceSubstring("\"", "%22");
01115   spec.ReplaceSubstring("`", "%60");
01116   spec.ReplaceSubstring(" ", "%20");
01117 
01118   nsCOMPtr<nsIIOService> ios(do_GetIOService());
01119   nsCOMPtr<nsIURI> uri;
01120   nsresult rv = ios->NewURI(spec, nsnull, nsnull, getter_AddRefs(uri));
01121   NS_ENSURE_SUCCESS(rv, rv);
01122 
01123   // post external load event
01124   nsCOMPtr<nsIEventQueue> eventQ;
01125   rv = NS_GetCurrentEventQ(getter_AddRefs(eventQ));
01126   if (NS_FAILED(rv))
01127     return rv;
01128 
01129   extLoadRequest *event = new extLoadRequest;
01130   if (!event)
01131     return NS_ERROR_OUT_OF_MEMORY;
01132 
01133   event->uri    = uri;
01134   event->prompt = aPrompt;
01135   PL_InitEvent(event, nsnull, handleExternalLoadEvent, destroyExternalLoadEvent);
01136 
01137   rv = eventQ->PostEvent(event);
01138   if (NS_FAILED(rv))
01139     PL_DestroyEvent(event);
01140 
01141   return rv;
01142 }
01143 
01144 // helper routines used by LoadURI to check whether we're allowed
01145 // to load external schemes and whether or not to warn the user
01146 
01147 static const char kExternalProtocolPrefPrefix[]  = "network.protocol-handler.external.";
01148 static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
01149 static const char kExternalWarningPrefPrefix[]   = "network.protocol-handler.warn-external.";
01150 static const char kExternalWarningDefaultPref[]  = "network.protocol-handler.warn-external-default";
01151 
01152 
01153 PRBool nsExternalHelperAppService::isExternalLoadOK(nsIURI* aURL, nsIPrompt* aPrompt)
01154 {
01155   if (!aURL)
01156     return PR_FALSE;
01157 
01158   nsCAutoString scheme;
01159   aURL->GetScheme(scheme);
01160   if (scheme.IsEmpty())
01161     return PR_FALSE; // must have a scheme
01162 
01163   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
01164   if (!prefs)
01165     return PR_FALSE; // deny if we can't check prefs
01166 
01167 
01168   // Deny load if the prefs say to do so
01169   nsCAutoString externalPref(kExternalProtocolPrefPrefix);
01170   externalPref += scheme;
01171   PRBool allowLoad  = PR_FALSE;
01172   nsresult rv = prefs->GetBoolPref(externalPref.get(), &allowLoad);
01173   if (NS_FAILED(rv))
01174   {
01175     // no scheme-specific value, check the default
01176     rv = prefs->GetBoolPref(kExternalProtocolDefaultPref, &allowLoad);
01177   }
01178   if (NS_FAILED(rv) || !allowLoad)
01179     return PR_FALSE; // explicitly denied or missing default pref
01180 
01181 
01182   // allowLoad is now true. See whether we have to ask the user
01183   nsCAutoString warningPref(kExternalWarningPrefPrefix);
01184   warningPref += scheme;
01185   PRBool warn = PR_TRUE;
01186   rv = prefs->GetBoolPref(warningPref.get(), &warn);
01187   if (NS_FAILED(rv))
01188   {
01189     // no scheme-specific value, check the default
01190     rv = prefs->GetBoolPref(kExternalWarningDefaultPref, &warn);
01191   }
01192 
01193 
01194   if (NS_FAILED(rv) || warn)
01195   {
01196     // explicit "warn" setting or missing default pref:
01197     // we must ask the user before loading this type externally
01198     PRBool remember = PR_FALSE;
01199     allowLoad = promptForScheme(aURL, aPrompt, &remember);
01200 
01201     if (remember)
01202     {
01203       if (allowLoad)
01204         // suppress future warnings for this scheme
01205         prefs->SetBoolPref(warningPref.get(), PR_FALSE);
01206       else
01207         // prevent externally loading this scheme in the future
01208         prefs->SetBoolPref(externalPref.get(), PR_FALSE);
01209     }
01210   }
01211 
01212   return allowLoad;
01213 }
01214 
01215 PRBool nsExternalHelperAppService::promptForScheme(nsIURI* aURI,
01216                                                    nsIPrompt* aPrompt,
01217                                                    PRBool *aRemember)
01218 {
01219   // if no prompt passed in get one from the windowwatcher
01220   nsCOMPtr<nsIPrompt> prompt(aPrompt);
01221   if (!prompt)
01222   {
01223     nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
01224     if (wwatch)
01225       wwatch->GetNewPrompter(0, getter_AddRefs(prompt));
01226   }
01227   if (!prompt) {
01228     NS_ERROR("No prompt to warn user about external load, denying");
01229     return PR_FALSE; // told to warn but no prompt: deny
01230   }
01231 
01232   // load the strings we need
01233   nsCOMPtr<nsIStringBundleService> sbSvc(do_GetService(NS_STRINGBUNDLE_CONTRACTID));
01234   if (!sbSvc) {
01235     NS_ERROR("Couldn't load StringBundleService");
01236     return PR_FALSE;
01237   }
01238 
01239   nsCOMPtr<nsIStringBundle> appstrings;
01240   nsresult rv = sbSvc->CreateBundle("chrome://global/locale/appstrings.properties",
01241                                     getter_AddRefs(appstrings));
01242   if (NS_FAILED(rv) || !appstrings) {
01243     NS_ERROR("Failed to create appstrings.properties bundle");
01244     return PR_FALSE;
01245   }
01246 
01247   nsCAutoString spec;
01248   aURI->GetSpec(spec);
01249   NS_ConvertUTF8toUTF16 uri(spec);
01250 
01251   // The maximum amount of space the URI should take up in the dialog.
01252   const PRUint32 maxWidth = 75;
01253   const PRUint32 maxLines = 12; // (not counting ellipsis line)
01254   const PRUint32 maxLength = maxWidth * maxLines;
01255 
01256   // If the URI seems too long, insert zero-width spaces to make it wrappable
01257   // and crop it in the middle (replacing the cropped portion with an ellipsis),
01258   // so the dialog doesn't grow so wide or tall it renders buttons off-screen.
01259   if (uri.Length() > maxWidth) {
01260     PRUint32 charIdx = maxWidth;
01261     PRUint32 lineIdx = 1;
01262     
01263     PRInt32 numCharsToCrop = uri.Length() - maxLength;
01264 
01265     while (charIdx < uri.Length()) {
01266       // Don't insert characters in the middle of a surrogate pair.
01267       if (IS_LOW_SURROGATE(uri[charIdx]))
01268         --charIdx;
01269 
01270       if (numCharsToCrop > 0 && lineIdx == maxLines / 2) {
01271         NS_NAMED_LITERAL_STRING(ellipsis, "\n...\n");
01272 
01273         // Don't end the crop in the middle of a surrogate pair.
01274         if (IS_HIGH_SURROGATE(uri[charIdx + numCharsToCrop - 1]))
01275           ++numCharsToCrop;
01276 
01277         uri.Replace(charIdx, numCharsToCrop, ellipsis);
01278         charIdx += ellipsis.Length();
01279       }
01280       else {
01281         // 0x200B is the zero-width breakable space character.
01282         uri.Insert(PRUnichar(0x200B), charIdx);
01283         charIdx += 1;
01284       }
01285   
01286       charIdx += maxWidth;
01287       ++lineIdx;
01288     }
01289   }
01290 
01291   nsCAutoString asciischeme;
01292   aURI->GetScheme(asciischeme);
01293   NS_ConvertUTF8toUTF16 scheme(asciischeme);
01294 
01295   nsXPIDLString desc;
01296   GetApplicationDescription(asciischeme, desc);
01297 
01298   nsXPIDLString title;
01299   appstrings->GetStringFromName(NS_LITERAL_STRING("externalProtocolTitle").get(),
01300                                 getter_Copies(title));
01301   nsXPIDLString checkMsg;
01302   appstrings->GetStringFromName(NS_LITERAL_STRING("externalProtocolChkMsg").get(),
01303                                 getter_Copies(checkMsg));
01304   nsXPIDLString launchBtn;
01305   appstrings->GetStringFromName(NS_LITERAL_STRING("externalProtocolLaunchBtn").get(),
01306                                 getter_Copies(launchBtn));
01307 
01308   if (desc.IsEmpty())
01309     appstrings->GetStringFromName(NS_LITERAL_STRING("externalProtocolUnknown").get(),
01310                                   getter_Copies(desc));
01311 
01312   nsXPIDLString message;
01313   const PRUnichar* msgArgs[] = { scheme.get(), uri.get(), desc.get() };
01314   appstrings->FormatStringFromName(NS_LITERAL_STRING("externalProtocolPrompt").get(),
01315                                    msgArgs,
01316                                    NS_ARRAY_LENGTH(msgArgs),
01317                                    getter_Copies(message));
01318 
01319   if (scheme.IsEmpty() || uri.IsEmpty() || title.IsEmpty() ||
01320       checkMsg.IsEmpty() || launchBtn.IsEmpty() || message.IsEmpty() ||
01321       desc.IsEmpty())
01322     return PR_FALSE;
01323 
01324   // all pieces assembled, now we can pose the dialog
01325   PRInt32 choice = 1; // assume "cancel" in case of failure
01326   rv = prompt->ConfirmEx(title.get(), message.get(),
01327                          nsIPrompt::BUTTON_DELAY_ENABLE +
01328                          nsIPrompt::BUTTON_POS_1_DEFAULT +
01329                          (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
01330                          (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1),
01331                          launchBtn.get(), 0, 0, checkMsg.get(),
01332                          aRemember, &choice);
01333 
01334   if (NS_SUCCEEDED(rv) && choice == 0)
01335     return PR_TRUE;
01336 
01337   return PR_FALSE;
01338 }
01339 
01340 NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
01341 {
01342   // this method should only be implemented by each OS specific implementation of this service.
01343   return NS_ERROR_NOT_IMPLEMENTED;
01344 }
01345 
01346 
01348 // Methods related to deleting temporary files on exit
01350 
01351 NS_IMETHODIMP nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile * aTemporaryFile)
01352 {
01353   nsresult rv = NS_OK;
01354   PRBool isFile = PR_FALSE;
01355   nsCOMPtr<nsILocalFile> localFile (do_QueryInterface(aTemporaryFile, &rv));
01356   NS_ENSURE_SUCCESS(rv, rv);
01357 
01358   // as a safety measure, make sure the nsIFile is really a file and not a directory object.
01359   localFile->IsFile(&isFile);
01360   if (!isFile) return NS_OK;
01361 
01362   mTemporaryFilesList.AppendObject(localFile);
01363 
01364   return NS_OK;
01365 }
01366 
01367 void nsExternalHelperAppService::FixFilePermissions(nsILocalFile* aFile)
01368 {
01369   // This space intentionally left blank
01370 }
01371 
01372 nsresult nsExternalHelperAppService::ExpungeTemporaryFiles()
01373 {
01374   PRInt32 numEntries = mTemporaryFilesList.Count();
01375   nsILocalFile* localFile;
01376   for (PRInt32 index = 0; index < numEntries; index++)
01377   {
01378     localFile = mTemporaryFilesList[index];
01379     if (localFile)
01380       localFile->Remove(PR_FALSE);
01381   }
01382 
01383   mTemporaryFilesList.Clear();
01384 
01385   return NS_OK;
01386 }
01387 
01388 // XPCOM profile change observer
01389 NS_IMETHODIMP
01390 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData )
01391 {
01392   if (!strcmp(aTopic, "profile-before-change")) {
01393     ExpungeTemporaryFiles();
01394     nsCOMPtr <nsIRDFRemoteDataSource> flushableDataSource = do_QueryInterface(mOverRideDataSource);
01395     if (flushableDataSource)
01396       flushableDataSource->Flush();
01397     mOverRideDataSource = nsnull;
01398     mDataSourceInitialized = PR_FALSE;
01399   }
01400   return NS_OK;
01401 }
01402 
01404 // begin external app handler implementation 
01406 
01407 NS_IMPL_THREADSAFE_ADDREF(nsExternalAppHandler)
01408 NS_IMPL_THREADSAFE_RELEASE(nsExternalAppHandler)
01409 
01410 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
01411    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
01412    NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
01413    NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
01414    NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)   
01415    NS_INTERFACE_MAP_ENTRY(nsICancelable)
01416    NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
01417 NS_INTERFACE_MAP_END_THREADSAFE
01418 
01419 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
01420                                            const nsCSubstring& aTempFileExtension,
01421                                            nsIInterfaceRequestor* aWindowContext,
01422                                            const nsAString& aSuggestedFilename,
01423                                            PRUint32 aReason)
01424 : mMimeInfo(aMIMEInfo)
01425 , mWindowContext(aWindowContext)
01426 , mWindowToClose(nsnull)
01427 , mSuggestedFileName(aSuggestedFilename)
01428 , mCanceled(PR_FALSE)
01429 , mShouldCloseWindow(PR_FALSE)
01430 , mReceivedDispositionInfo(PR_FALSE)
01431 , mStopRequestIssued(PR_FALSE)
01432 , mProgressListenerInitialized(PR_FALSE)
01433 , mReason(aReason)
01434 , mContentLength(-1)
01435 , mProgress(0)
01436 , mRequest(nsnull)
01437 {
01438 
01439   // make sure the extention includes the '.'
01440   if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
01441     mTempFileExtension = PRUnichar('.');
01442   AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
01443 
01444   // replace platform specific path separator and illegal characters to avoid any confusion
01445   mSuggestedFileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
01446   mTempFileExtension.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
01447   
01448   // Make sure extension is correct.
01449   EnsureSuggestedFileName();
01450 
01451   sSrv->AddRef();
01452 }
01453 
01454 nsExternalAppHandler::~nsExternalAppHandler()
01455 {
01456   // Not using NS_RELEASE, since we don't want to set sSrv to NULL
01457   sSrv->Release();
01458 }
01459 
01460 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
01461 { 
01462   // this call back means we've succesfully brought up the 
01463   // progress window so set the appropriate flag, even though
01464   // aWebProgressListener might be null
01465   
01466   if (mReceivedDispositionInfo)
01467     mProgressListenerInitialized = PR_TRUE;
01468 
01469   // Go ahead and register the progress listener....
01470   mWebProgressListener = aWebProgressListener;
01471 
01472   // while we were bringing up the progress dialog, we actually finished processing the
01473   // url. If that's the case then mStopRequestIssued will be true. We need to execute the
01474   // operation since we are actually done now.
01475   if (mStopRequestIssued && aWebProgressListener)
01476   {
01477     return ExecuteDesiredAction();
01478   }
01479 
01480   return NS_OK;
01481 }
01482 
01483 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
01484 {
01485   if (mFinalFileDestination)
01486     *aTarget = mFinalFileDestination;
01487   else
01488     *aTarget = mTempFile;
01489 
01490   NS_IF_ADDREF(*aTarget);
01491   return NS_OK;
01492 }
01493 
01494 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
01495 {
01496   *aTime = mTimeDownloadStarted;
01497   return NS_OK;
01498 }
01499 
01500 NS_IMETHODIMP nsExternalAppHandler::CloseProgressWindow()
01501 {
01502   // release extra state...
01503   mWebProgressListener = nsnull;
01504   return NS_OK;
01505 }
01506 
01507 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
01508 {
01509   // we are going to run the downloading of the helper app in our own little docloader / load group context. 
01510   // so go ahead and force the creation of a load group and doc loader for us to use...
01511   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
01512   if (!aChannel)
01513     return;
01514 
01515   nsCOMPtr<nsILoadGroup> oldLoadGroup;
01516   aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
01517 
01518   if(oldLoadGroup)
01519      oldLoadGroup->RemoveRequest(request, nsnull, NS_BINDING_RETARGETED);
01520       
01521   aChannel->SetLoadGroup(nsnull);
01522   aChannel->SetNotificationCallbacks(nsnull);
01523 
01524   // we need to store off the original (pre redirect!) channel that initiated the load. We do
01525   // this so later on, we can pass any refresh urls associated with the original channel back to the 
01526   // window context which started the whole process. More comments about that are listed below....
01527   // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. 
01528   // ideally we should be able to just use mChannel (the channel we are extracting content from) or
01529   // the default load channel associated with the original load group. Unfortunately because
01530   // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel 
01531   // which is what we really want....
01532   nsCOMPtr<nsIDocumentLoader> origContextLoader =
01533     do_GetInterface(mWindowContext);
01534   if (origContextLoader)
01535     origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
01536 }
01537 
01538 #define SALT_SIZE 8
01539 #define TABLE_SIZE 36
01540 const PRUnichar table[] = 
01541   { 'a','b','c','d','e','f','g','h','i','j',
01542     'k','l','m','n','o','p','q','r','s','t',
01543     'u','v','w','x','y','z','0','1','2','3',
01544     '4','5','6','7','8','9'};
01545 
01559 void nsExternalAppHandler::EnsureSuggestedFileName()
01560 {
01561   // Make sure there is a mTempFileExtension (not "" or ".").
01562   // Remember that mTempFileExtension will always have the leading "."
01563   // (the check for empty is just to be safe).
01564   if (mTempFileExtension.Length() > 1)
01565   {
01566     // Get mSuggestedFileName's current extension.
01567     nsAutoString fileExt;
01568     PRInt32 pos = mSuggestedFileName.RFindChar('.');
01569     if (pos != kNotFound)
01570       mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
01571 
01572     // Now, compare fileExt to mTempFileExtension.
01573     if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
01574     {
01575       // Matches -> mTempFileExtension can be empty
01576       mTempFileExtension.Truncate();
01577     }
01578   }
01579 }
01580 
01581 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
01582 {
01583   nsresult rv;
01584 
01585 #if defined(XP_MAC) || defined (XP_MACOSX)
01586  // create a temp file for the data...and open it for writing.
01587  // use NS_MAC_DEFAULT_DOWNLOAD_DIR which gets download folder from InternetConfig
01588  // if it can't get download folder pref, then it uses desktop folder
01589   rv = NS_GetSpecialDirectory(NS_MAC_DEFAULT_DOWNLOAD_DIR,
01590                               getter_AddRefs(mTempFile));
01591 #else
01592   // create a temp file for the data...and open it for writing.
01593   rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile));
01594 #endif
01595   NS_ENSURE_SUCCESS(rv, rv);
01596 
01597   // We need to generate a name for the temp file that we are going to be streaming data to. 
01598   // We don't want this name to be predictable for security reasons so we are going to generate a 
01599   // "salted" name.....
01600   nsAutoString saltedTempLeafName;
01601   // this salting code was ripped directly from the profile manager.
01602   PRInt32 i;
01603   for (i=0;i<SALT_SIZE;i++) 
01604   {
01605     saltedTempLeafName.Append(table[rand()%TABLE_SIZE]);
01606   }
01607 
01608   // now append our extension.
01609   nsCAutoString ext;
01610   mMimeInfo->GetPrimaryExtension(ext);
01611   if (!ext.IsEmpty()) {
01612     ext.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
01613     if (ext.First() != '.')
01614       saltedTempLeafName.Append(PRUnichar('.'));
01615     AppendUTF8toUTF16(ext, saltedTempLeafName);
01616   }
01617 
01618   rv = mTempFile->Append(saltedTempLeafName); // make this file unique!!!
01619   NS_ENSURE_SUCCESS(rv, rv);
01620   rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
01621   NS_ENSURE_SUCCESS(rv, rv);
01622 
01623 #if defined(XP_MAC) || defined (XP_MACOSX)
01624   // Now that the file exists set Mac type if the file has no extension
01625   // and we can determine a type.
01626   if (ext.IsEmpty() && mMimeInfo)
01627   {
01628     nsCOMPtr<nsILocalFileMac> macfile = do_QueryInterface(mTempFile);
01629     if (macfile)
01630     {
01631       PRUint32 type;
01632       mMimeInfo->GetMacType(&type);
01633       macfile->SetFileType(type);
01634     }
01635   }
01636 #endif
01637 
01638   rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutStream), mTempFile,
01639                                    PR_WRONLY | PR_CREATE_FILE, 0600);
01640   if (NS_FAILED(rv)) {
01641     mTempFile->Remove(PR_FALSE);
01642     return rv;
01643   }
01644 
01645 #if defined(XP_MAC) || defined (XP_MACOSX)
01646     nsCAutoString contentType;
01647     mMimeInfo->GetMIMEType(contentType);
01648     if (contentType.LowerCaseEqualsLiteral(APPLICATION_APPLEFILE) ||
01649         contentType.LowerCaseEqualsLiteral(MULTIPART_APPLEDOUBLE))
01650     {
01651       nsCOMPtr<nsIAppleFileDecoder> appleFileDecoder = do_CreateInstance(NS_IAPPLEFILEDECODER_CONTRACTID, &rv);
01652       if (NS_SUCCEEDED(rv))
01653       {
01654         rv = appleFileDecoder->Initialize(mOutStream, mTempFile);
01655         if (NS_SUCCEEDED(rv))
01656           mOutStream = do_QueryInterface(appleFileDecoder, &rv);
01657       }
01658     }
01659 #endif
01660 
01661   return rv;
01662 }
01663 
01664 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
01665 {
01666   NS_PRECONDITION(request, "OnStartRequest without request?");
01667 
01668   // Set mTimeDownloadStarted here as the download has already started and
01669   // we want to record the start time before showing the filepicker.
01670   mTimeDownloadStarted = PR_Now();
01671 
01672   mRequest = request;
01673 
01674   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
01675 
01676   // Get content length
01677   nsresult rv;
01678   nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
01679   if (props) {
01680     rv = props->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
01681                                    &mContentLength.mValue);
01682   }
01683   // If that failed, ask the channel
01684   if (NS_FAILED(rv) && aChannel) {
01685     PRInt32 len;
01686     aChannel->GetContentLength(&len);
01687     mContentLength = len;
01688   }
01689 
01690   // Determine whether a new window was opened specifically for this request
01691   if (props) {
01692     PRBool tmp = PR_FALSE;
01693     props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
01694                              &tmp);
01695     mShouldCloseWindow = tmp;
01696   }
01697 
01698   // Now get the URI
01699   if (aChannel)
01700   {
01701     aChannel->GetURI(getter_AddRefs(mSourceUrl));
01702   }
01703 
01704   rv = SetUpTempFile(aChannel);
01705   if (NS_FAILED(rv)) {
01706     mCanceled = PR_TRUE;
01707     request->Cancel(rv);
01708     nsAutoString path;
01709     if (mTempFile)
01710       mTempFile->GetPath(path);
01711     SendStatusChange(kWriteError, rv, request, path);
01712     return NS_OK;
01713   }
01714 
01715   // Extract mime type for later use below.
01716   nsCAutoString MIMEType;
01717   mMimeInfo->GetMIMEType(MIMEType);
01718 
01719   // retarget all load notifications to our docloader instead of the original window's docloader...
01720   RetargetLoadNotifications(request);
01721 
01722   // Check to see if there is a refresh header on the original channel.
01723   if (mOriginalChannel) {
01724     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
01725     if (httpChannel) {
01726       nsCAutoString refreshHeader;
01727       httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
01728                                      refreshHeader);
01729       if (!refreshHeader.IsEmpty()) {
01730         mShouldCloseWindow = PR_FALSE;
01731       }
01732     }
01733   }
01734 
01735   // Close the underlying DOMWindow if there is no refresh header
01736   // and it was opened specifically for the download
01737   MaybeCloseWindow();
01738 
01739   nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
01740   if (encChannel) 
01741   {
01742     // Turn off content encoding conversions if needed
01743     PRBool applyConversion = PR_TRUE;
01744 
01745     nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
01746     if (sourceURL)
01747     {
01748       nsCAutoString extension;
01749       sourceURL->GetFileExtension(extension);
01750       if (!extension.IsEmpty())
01751       {
01752         nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
01753         encChannel->GetContentEncodings(getter_AddRefs(encEnum));
01754         if (encEnum)
01755         {
01756           PRBool hasMore;
01757           rv = encEnum->HasMore(&hasMore);
01758           if (NS_SUCCEEDED(rv) && hasMore)
01759           {
01760             nsCAutoString encType;
01761             rv = encEnum->GetNext(encType);
01762             if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
01763             {
01764               NS_ASSERTION(sSrv, "Where did the service go?");
01765               sSrv->ApplyDecodingForExtension(extension,
01766                                               encType,
01767                                               &applyConversion);
01768             }
01769           }
01770         }
01771       }    
01772     }
01773 
01774     encChannel->SetApplyConversion( applyConversion );
01775   }
01776 
01777   // now that the temp file is set up, find out if we need to invoke a dialog
01778   // asking the user what they want us to do with this content...
01779 
01780   // We can get here for three reasons: "can't handle", "sniffed type", or
01781   // "server sent content-disposition:attachment".  In the first case we want
01782   // to honor the user's "always ask" pref; in the other two cases we want to
01783   // honor it only if the default action is "save".  Opening attachments in
01784   // helper apps by default breaks some websites (especially if the attachment
01785   // is one part of a multipart document).  Opening sniffed content in helper
01786   // apps by default introduces security holes that we'd rather not have.
01787 
01788   // So let's find out whether the user wants to be prompted.  If he does not,
01789   // check mReason and the preferred action to see what we should do.
01790 
01791   PRBool alwaysAsk = PR_TRUE;
01792   mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
01793   if (alwaysAsk)
01794   {
01795     // But we *don't* ask if this mimeInfo didn't come from
01796     // our mimeTypes.rdf data source and the user has said
01797     // at some point in the distant past that they don't
01798     // want to be asked.  The latter fact would have been
01799     // stored in pref strings back in the old days.
01800     NS_ASSERTION(sSrv, "Service gone away!?");
01801     if (!sSrv->MIMETypeIsInDataSource(MIMEType.get()))
01802     {
01803       if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
01804       {
01805         // Don't need to ask after all.
01806         alwaysAsk = PR_FALSE;
01807         // Make sure action matches pref (save to disk).
01808         mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
01809       }
01810       else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get()))
01811       {
01812         // Don't need to ask after all.
01813         alwaysAsk = PR_FALSE;
01814       }
01815     }
01816   }
01817 
01818   PRInt32 action = nsIMIMEInfo::saveToDisk;
01819   mMimeInfo->GetPreferredAction( &action );
01820 
01821   // OK, now check why we're here
01822   if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
01823     // Force asking if we're not saving.  See comment back when we fetched the
01824     // alwaysAsk boolean for details.
01825     alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
01826   }
01827 
01828   if (alwaysAsk)
01829   {
01830     // do this first! make sure we don't try to take an action until the user tells us what they want to do
01831     // with it...
01832     mReceivedDispositionInfo = PR_FALSE; 
01833 
01834     // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
01835     mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
01836     NS_ENSURE_SUCCESS(rv, rv);
01837 
01838     // this will create a reference cycle (the dialog holds a reference to us as
01839     // nsIHelperAppLauncher), which will be broken in Cancel or
01840     // CreateProgressListener.
01841     rv = mDialog->Show( this, mWindowContext, mReason );
01842 
01843     // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
01844   }
01845   else
01846   {
01847     mReceivedDispositionInfo = PR_TRUE; // no need to wait for a response from the user
01848 
01849     // We need to do the save/open immediately, then.
01850 #ifdef XP_WIN
01851     /* We need to see whether the file we've got here could be
01852      * executable.  If it could, we had better not try to open it!
01853      * We can skip this check, though, if we have a setting to open in a
01854      * helper app.
01855      * This code mirrors the code in
01856      * nsExternalAppHandler::LaunchWithApplication so that what we
01857      * test here is as close as possible to what will really be
01858      * happening if we decide to execute
01859      */
01860     nsCOMPtr<nsIFile> prefApp;
01861     mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
01862     if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
01863       nsCOMPtr<nsIFile> fileToTest;
01864       GetTargetFile(getter_AddRefs(fileToTest));
01865       if (fileToTest) {
01866         PRBool isExecutable;
01867         rv = fileToTest->IsExecutable(&isExecutable);
01868         if (NS_FAILED(rv) || isExecutable) {  // checking NS_FAILED, because paranoia is good
01869           action = nsIMIMEInfo::saveToDisk;
01870         }
01871       } else {   // Paranoia is good here too, though this really should not happen
01872         NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
01873         action = nsIMIMEInfo::saveToDisk;
01874       }
01875     }
01876 
01877 #endif
01878     if (action == nsIMIMEInfo::useHelperApp ||
01879         action == nsIMIMEInfo::useSystemDefault)
01880     {
01881         rv = LaunchWithApplication(nsnull, PR_FALSE);
01882     }
01883     else // Various unknown actions go here too
01884     {
01885         rv = SaveToDisk(nsnull, PR_FALSE);
01886     }
01887   }
01888 
01889   // Now let's mark the downloaded URL as visited
01890   nsCOMPtr<nsIGlobalHistory> history(do_GetService(NS_GLOBALHISTORY_CONTRACTID));
01891   nsCAutoString spec;
01892   mSourceUrl->GetSpec(spec);
01893   if (history && !spec.IsEmpty())
01894   {
01895     PRBool visited;
01896     rv = history->IsVisited(spec.get(), &visited);
01897     if (NS_FAILED(rv))
01898         return rv;
01899     history->AddPage(spec.get());
01900     if (!visited) {
01901       nsCOMPtr<nsIObserverService> obsService =
01902           do_GetService("@mozilla.org/observer-service;1");
01903       if (obsService) {
01904         obsService->NotifyObservers(mSourceUrl, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
01905       }
01906     }
01907   }
01908 
01909   return NS_OK;
01910 }
01911 
01912 // Convert error info into proper message text and send OnStatusChange notification
01913 // to the web progress listener.
01914 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
01915 {
01916     nsAutoString msgId;
01917     switch(rv)
01918     {
01919     case NS_ERROR_OUT_OF_MEMORY:
01920         // No memory
01921         msgId.AssignLiteral("noMemory");
01922         break;
01923 
01924     case NS_ERROR_FILE_DISK_FULL:
01925     case NS_ERROR_FILE_NO_DEVICE_SPACE:
01926         // Out of space on target volume.
01927         msgId.AssignLiteral("diskFull");
01928         break;
01929 
01930     case NS_ERROR_FILE_READ_ONLY:
01931         // Attempt to write to read/only file.
01932         msgId.AssignLiteral("readOnly");
01933         break;
01934 
01935     case NS_ERROR_FILE_ACCESS_DENIED:
01936         if (type == kWriteError) {
01937           // Attempt to write without sufficient permissions.
01938           msgId.AssignLiteral("accessError");
01939         }
01940         else
01941         {
01942           msgId.AssignLiteral("launchError");
01943         }
01944         break;
01945 
01946     case NS_ERROR_FILE_NOT_FOUND:
01947     case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
01948     case NS_ERROR_FILE_UNRECOGNIZED_PATH:
01949         // Helper app not found, let's verify this happened on launch
01950         if (type == kLaunchError) {
01951           msgId.AssignLiteral("helperAppNotFound");
01952           break;
01953         }
01954         // fall through
01955 
01956     default:
01957         // Generic read/write/launch error message.
01958         switch(type)
01959         {
01960         case kReadError:
01961           msgId.AssignLiteral("readError");
01962           break;
01963         case kWriteError:
01964           msgId.AssignLiteral("writeError");
01965           break;
01966         case kLaunchError:
01967           msgId.AssignLiteral("launchError");
01968           break;
01969         }
01970         break;
01971     }
01972     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
01973         ("Error: %s, type=%i, listener=0x%p, rv=0x%08X\n",
01974          NS_LossyConvertUTF16toASCII(msgId).get(), type, mWebProgressListener.get(), rv));
01975     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
01976         ("       path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
01977 
01978     // Get properties file bundle and extract status string.
01979     nsCOMPtr<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
01980     if (s)
01981     {
01982         nsCOMPtr<nsIStringBundle> bundle;
01983         if (NS_SUCCEEDED(s->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
01984         {
01985             nsXPIDLString msgText;
01986             const PRUnichar *strings[] = { path.get() };
01987             if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
01988             {
01989               if (mWebProgressListener)
01990               {
01991                 // We have a listener, let it handle the error.
01992                 mWebProgressListener->OnStatusChange(nsnull, (type == kReadError) ? aRequest : nsnull, rv, msgText);
01993               }
01994               else
01995               {
01996                 // We don't have a listener.  Simply show the alert ourselves.
01997                 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext));
01998                 nsXPIDLString title;
01999                 bundle->FormatStringFromName(NS_LITERAL_STRING("title").get(),
02000                                              strings,
02001                                              1,
02002                                              getter_Copies(title));
02003                 if (prompter)
02004                 {
02005                   prompter->Alert(title, msgText);
02006                 }
02007               }
02008             }
02009         }
02010     }
02011 }
02012 
02013 NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
02014                                                   nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
02015 {
02016   nsresult rv = NS_OK;
02017   // first, check to see if we've been canceled....
02018   if (mCanceled) // then go cancel our underlying channel too
02019     return request->Cancel(NS_BINDING_ABORTED);
02020 
02021   // read the data out of the stream and write it to the temp file.
02022   if (mOutStream && count > 0)
02023   {
02024     PRUint32 numBytesRead = 0; 
02025     PRUint32 numBytesWritten = 0;
02026     mProgress += count;
02027     PRBool readError = PR_TRUE;
02028     while (NS_SUCCEEDED(rv) && count > 0) // while we still have bytes to copy...
02029     {
02030       readError = PR_TRUE;
02031       rv = inStr->Read(mDataBuffer, PR_MIN(count, DATA_BUFFER_SIZE - 1), &numBytesRead);
02032       if (NS_SUCCEEDED(rv))
02033       {
02034         if (count >= numBytesRead)
02035           count -= numBytesRead; // subtract off the number of bytes we just read
02036         else
02037           count = 0;
02038         readError = PR_FALSE;
02039         // Write out the data until something goes wrong, or, it is
02040         // all written.  We loop because for some errors (e.g., disk
02041         // full), we get NS_OK with some bytes written, then an error.
02042         // So, we want to write again in that case to get the actual
02043         // error code.
02044         const char *bufPtr = mDataBuffer; // Where to write from.
02045         while (NS_SUCCEEDED(rv) && numBytesRead)
02046         {
02047           numBytesWritten = 0;
02048           rv = mOutStream->Write(bufPtr, numBytesRead, &numBytesWritten);
02049           if (NS_SUCCEEDED(rv))
02050           {
02051             numBytesRead -= numBytesWritten;
02052             bufPtr += numBytesWritten;
02053             // Force an error if (for some reason) we get NS_OK but
02054             // no bytes written.
02055             if (!numBytesWritten)
02056             {
02057               rv = NS_ERROR_FAILURE;
02058             }
02059           }
02060         }
02061       }
02062     }
02063     if (NS_SUCCEEDED(rv))
02064     {
02065       // Send progress notification.
02066       if (mWebProgressListener)
02067       {
02068         mWebProgressListener->OnProgressChange64(nsnull, request, mProgress, mContentLength, mProgress, mContentLength);
02069       }
02070     }
02071     else
02072     {
02073       // An error occurred, notify listener.
02074       nsAutoString tempFilePath;
02075       if (mTempFile)
02076         mTempFile->GetPath(tempFilePath);
02077       SendStatusChange(readError ? kReadError : kWriteError, rv, request, tempFilePath);
02078 
02079       // Cancel the download.
02080       Cancel(rv);
02081     }
02082   }
02083   return rv;
02084 }
02085 
02086 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 
02087                                                   nsresult aStatus)
02088 {
02089   mStopRequestIssued = PR_TRUE;
02090   mRequest = nsnull;
02091   // Cancel if the request did not complete successfully.
02092   if (!mCanceled && NS_FAILED(aStatus))
02093   {
02094     // Send error notification.
02095     nsAutoString tempFilePath;
02096     if (mTempFile)
02097       mTempFile->GetPath(tempFilePath);
02098     SendStatusChange( kReadError, aStatus, request, tempFilePath );
02099 
02100     Cancel(aStatus);
02101   }
02102 
02103   // first, check to see if we've been canceled....
02104   if (mCanceled)
02105     return NS_OK;
02106 
02107   // close the stream...
02108   if (mOutStream)
02109   {
02110     mOutStream->Close();
02111     mOutStream = nsnull;
02112   }
02113 
02114   // Do what the user asked for
02115   ExecuteDesiredAction();
02116 
02117   // At this point, the channel should still own us. So releasing the reference
02118   // to us in the nsITransfer should be ok.
02119   // This nsITransfer object holds a reference to us (we are its observer), so
02120   // we need to release the reference to break a reference cycle (and therefore
02121   // to prevent leaking)
02122   mWebProgressListener = nsnull;
02123 
02124   return NS_OK;
02125 }
02126 
02127 nsresult nsExternalAppHandler::ExecuteDesiredAction()
02128 {
02129   nsresult rv = NS_OK;
02130   if (mProgressListenerInitialized && !mCanceled)
02131   {
02132     nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk;
02133     mMimeInfo->GetPreferredAction(&action);
02134     if (action == nsIMIMEInfo::useHelperApp ||
02135         action == nsIMIMEInfo::useSystemDefault)
02136     {
02137       // Make sure the suggested name is unique since in this case we don't
02138       // have a file name that was guaranteed to be unique by going through
02139       // the File Save dialog
02140       rv = mFinalFileDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
02141       if (NS_SUCCEEDED(rv))
02142       {
02143         // Source and dest dirs should be == so this should just do a rename
02144         rv = MoveFile(mFinalFileDestination);
02145         if (NS_SUCCEEDED(rv))
02146           rv = OpenWithApplication();
02147       }
02148     }
02149     else // Various unknown actions go here too
02150     {
02151       // XXX Put progress dialog in barber-pole mode
02152       //     and change text to say "Copying from:".
02153       rv = MoveFile(mFinalFileDestination);
02154       if (NS_SUCCEEDED(rv) && action == nsIMIMEInfo::saveToDisk)
02155       {
02156         nsCOMPtr<nsILocalFile> destfile(do_QueryInterface(mFinalFileDestination));
02157         sSrv->FixFilePermissions(destfile);
02158       }
02159     }
02160     
02161     // Notify dialog that download is complete.
02162     // By waiting till this point, it ensures that the progress dialog doesn't indicate
02163     // success until we're really done.
02164     if(mWebProgressListener)
02165     {
02166       if (!mCanceled)
02167       {
02168         mWebProgressListener->OnProgressChange64(nsnull, nsnull, mContentLength, mContentLength, mContentLength, mContentLength);
02169       }
02170       mWebProgressListener->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP, NS_OK);
02171     }
02172   }
02173   
02174   return rv;
02175 }
02176 
02177 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
02178 {
02179   *aMIMEInfo = mMimeInfo;
02180   NS_ADDREF(*aMIMEInfo);
02181   return NS_OK;
02182 }
02183 
02184 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
02185 {
02186   NS_ENSURE_ARG(aSourceURI);
02187   *aSourceURI = mSourceUrl;
02188   NS_IF_ADDREF(*aSourceURI);
02189   return NS_OK;
02190 }
02191 
02192 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
02193 {
02194   aSuggestedFileName = mSuggestedFileName;
02195   return NS_OK;
02196 }
02197 
02198 nsresult nsExternalAppHandler::InitializeDownload(nsITransfer* aTransfer)
02199 {
02200   nsresult rv;
02201   
02202   nsCOMPtr<nsIURI> target;
02203   rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
02204   if (NS_FAILED(rv)) return rv;
02205   
02206   nsCOMPtr<nsILocalFile> lf(do_QueryInterface(mTempFile));
02207   rv = aTransfer->Init(mSourceUrl, target, EmptyString(),
02208                        mMimeInfo, mTimeDownloadStarted, lf, this);
02209   if (NS_FAILED(rv)) return rv;
02210 
02211   return rv;
02212 }
02213 
02214 nsresult nsExternalAppHandler::CreateProgressListener()
02215 {
02216   // we are back from the helper app dialog (where the user chooses to save or open), but we aren't
02217   // done processing the load. in this case, throw up a progress dialog so the user can see what's going on...
02218   // Also, release our reference to mDialog. We don't need it anymore, and we
02219   // need to break the reference cycle.
02220   mDialog = nsnull;
02221   nsresult rv;
02222   
02223   nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
02224   if (NS_SUCCEEDED(rv))
02225     InitializeDownload(tr);
02226 
02227   if (tr)
02228     tr->OnStateChange(nsnull, mRequest, nsIWebProgressListener::STATE_START, NS_OK);
02229 
02230   // note we might not have a listener here if the QI() failed, or if
02231   // there is no nsITransfer object, but we still call
02232   // SetWebProgressListener() to make sure our progress state is sane
02233   // NOTE: This will set up a reference cycle (this nsITransfer has us set up as
02234   // its observer). This cycle will be broken in Cancel, CloseProgressWindow or
02235   // OnStopRequest.
02236   SetWebProgressListener(tr);
02237 
02238   return rv;
02239 }
02240 
02241 nsresult nsExternalAppHandler::PromptForSaveToFile(nsILocalFile ** aNewFile, const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
02242 {
02243   // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
02244   // Convert to use file picker? No, then embeddors could not do any sort of
02245   // "AutoDownload" w/o showing a prompt
02246   nsresult rv = NS_OK;
02247   if (!mDialog)
02248   {
02249     // Get helper app launcher dialog.
02250     mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
02251     NS_ENSURE_SUCCESS(rv, rv);
02252   }
02253 
02254   // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
02255   // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
02256 
02257   // Now, be sure to keep |this| alive, and the dialog
02258   // If we don't do this, users that close the helper app dialog while the file
02259   // picker is up would cause Cancel() to be called, and the dialog would be
02260   // released, which would release this object too, which would crash.
02261   // See Bug 249143
02262   nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
02263   nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
02264   rv = mDialog->PromptForSaveToFile(this, 
02265                                     mWindowContext,
02266                                     aDefaultFile.get(),
02267                                     aFileExtension.get(),
02268                                     aNewFile);
02269   return rv;
02270 }
02271 
02272 nsresult nsExternalAppHandler::MoveFile(nsIFile * aNewFileLocation)
02273 {
02274   nsresult rv = NS_OK;
02275   NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?");
02276  
02277   nsCOMPtr<nsILocalFile> fileToUse = do_QueryInterface(aNewFileLocation);
02278 
02279   // if the on stop request was actually issued then it's now time to actually perform the file move....
02280   if (mStopRequestIssued && fileToUse)
02281   {
02282     // Unfortunately, MoveTo will fail if a file already exists at the user specified location....
02283     // but the user has told us, this is where they want the file! (when we threw up the save to file dialog,
02284     // it told them the file already exists and do they wish to over write it. So it should be okay to delete
02285     // fileToUse if it already exists.
02286     PRBool equalToTempFile = PR_FALSE;
02287     PRBool filetoUseAlreadyExists = PR_FALSE;
02288     fileToUse->Equals(mTempFile, &equalToTempFile);
02289     fileToUse->Exists(&filetoUseAlreadyExists);
02290     if (filetoUseAlreadyExists && !equalToTempFile)
02291       fileToUse->Remove(PR_FALSE);
02292 
02293      // extract the new leaf name from the file location
02294      nsAutoString fileName;
02295      fileToUse->GetLeafName(fileName);
02296      nsCOMPtr<nsIFile> directoryLocation;
02297      rv = fileToUse->GetParent(getter_AddRefs(directoryLocation));
02298      if (directoryLocation)
02299      {
02300        rv = mTempFile->MoveTo(directoryLocation, fileName);
02301      }
02302      if (NS_FAILED(rv))
02303      {
02304        // Send error notification.        
02305        nsAutoString path;
02306        fileToUse->GetPath(path);
02307        SendStatusChange(kWriteError, rv, nsnull, path);
02308        Cancel(rv); // Cancel (and clean up temp file).
02309      }
02310 #if defined(XP_OS2)
02311      else
02312      {
02313        // tag the file with its source URI
02314        nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(fileToUse);
02315        if (localFileOS2)
02316        {
02317          nsCAutoString url;
02318          mSourceUrl->GetSpec(url);
02319          localFileOS2->SetFileSource(url);
02320        }
02321      }
02322 #endif
02323   }
02324 
02325   return rv;
02326 }
02327 
02328 // SaveToDisk should only be called by the helper app dialog which allows
02329 // the user to say launch with application or save to disk. It doesn't actually 
02330 // perform the save, it just prompts for the destination file name. The actual save
02331 // won't happen until we are done downloading the content and are sure we've 
02332 // shown a progress dialog. This was done to simplify the 
02333 // logic that was showing up in this method. Internal callers who actually want
02334 // to preform the save should call ::MoveFile
02335 
02336 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, PRBool aRememberThisPreference)
02337 {
02338   nsresult rv = NS_OK;
02339   if (mCanceled)
02340     return NS_OK;
02341 
02342   mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
02343 
02344   // The helper app dialog has told us what to do.
02345   mReceivedDispositionInfo = PR_TRUE;
02346 
02347   nsCOMPtr<nsILocalFile> fileToUse = do_QueryInterface(aNewFileLocation);
02348   if (!fileToUse)
02349   {
02350     nsAutoString leafName;
02351     mTempFile->GetLeafName(leafName);
02352     if (mSuggestedFileName.IsEmpty())
02353       rv = PromptForSaveToFile(getter_AddRefs(fileToUse), leafName, mTempFileExtension);
02354     else
02355     {
02356       nsAutoString fileExt;
02357       PRInt32 pos = mSuggestedFileName.RFindChar('.');
02358       if (pos >= 0)
02359         mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
02360       if (fileExt.IsEmpty())
02361         fileExt = mTempFileExtension;
02362 
02363       rv = PromptForSaveToFile(getter_AddRefs(fileToUse), mSuggestedFileName, fileExt);
02364     }
02365 
02366     if (NS_FAILED(rv) || !fileToUse) {
02367       Cancel(NS_BINDING_ABORTED);
02368       return NS_ERROR_FAILURE;
02369     }
02370   }
02371   
02372   mFinalFileDestination = do_QueryInterface(fileToUse);
02373 
02374   // Move what we have in the final directory, but append .part
02375   // to it, to indicate that it's unfinished.
02376   // do not do that if we're already done
02377   if (mFinalFileDestination && !mStopRequestIssued)
02378   {
02379     nsCOMPtr<nsIFile> movedFile;
02380     mFinalFileDestination->Clone(getter_AddRefs(movedFile));
02381     if (movedFile) {
02382       // Get the old leaf name and append .part to it
02383       nsAutoString name;
02384       mFinalFileDestination->GetLeafName(name);
02385       name.AppendLiteral(".part");
02386       movedFile->SetLeafName(name);
02387 
02388       nsCOMPtr<nsIFile> dir;
02389       movedFile->GetParent(getter_AddRefs(dir));
02390 
02391       mOutStream->Close();
02392 
02393       rv = mTempFile->MoveTo(dir, name);
02394       if (NS_SUCCEEDED(rv)) // if it failed, we just continue with $TEMP
02395         mTempFile = movedFile;
02396       rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutStream), mTempFile,
02397                                          PR_WRONLY | PR_APPEND, 0600);
02398       if (NS_FAILED(rv)) { // (Re-)opening the output stream failed. bad luck.
02399         nsAutoString path;
02400         mTempFile->GetPath(path);
02401         SendStatusChange(kWriteError, rv, nsnull, path);
02402         Cancel(rv);
02403         return NS_OK;
02404       }
02405     }
02406   }
02407 
02408   if (!mProgressListenerInitialized)
02409     CreateProgressListener();
02410 
02411   // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
02412   // if there is one. We don't want to do this before the save as dialog goes away because this dialog
02413   // is modal and we do bad things if you try to load a web page in the underlying window while a modal
02414   // dialog is still up. 
02415   ProcessAnyRefreshTags();
02416 
02417   return NS_OK;
02418 }
02419 
02420 
02421 nsresult nsExternalAppHandler::OpenWithApplication()
02422 {
02423   nsresult rv = NS_OK;
02424   if (mCanceled)
02425     return NS_OK;
02426   
02427   // we only should have gotten here if the on stop request had been fired already.
02428 
02429   NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?");
02430   // if a stop request was already issued then proceed with launching the application.
02431   if (mStopRequestIssued)
02432   {
02433     rv = mMimeInfo->LaunchWithFile(mFinalFileDestination);
02434     if (NS_FAILED(rv))
02435     {
02436       // Send error notification.
02437       nsAutoString path;
02438       mFinalFileDestination->GetPath(path);
02439       SendStatusChange(kLaunchError, rv, nsnull, path);
02440       Cancel(rv); // Cancel, and clean up temp file.
02441     }
02442     else
02443     {
02444       PRBool deleteTempFileOnExit;
02445       nsresult result = NS_ERROR_NOT_AVAILABLE;  // don't return this!
02446       nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
02447       if (prefs) {
02448         result = prefs->GetBoolPref("browser.helperApps.deleteTempFileOnExit",
02449                                     &deleteTempFileOnExit);
02450       }
02451       if (NS_FAILED(result)) {
02452         // No pref set; use default value
02453 #if !defined(XP_MAC) && !defined (XP_MACOSX)
02454           // Mac users have been very verbal about temp files being deleted on
02455           // app exit - they don't like it - but we'll continue to do this on
02456           // other platforms for now.
02457         deleteTempFileOnExit = PR_TRUE;
02458 #else
02459         deleteTempFileOnExit = PR_FALSE;
02460 #endif
02461       }
02462       if (deleteTempFileOnExit) {
02463         NS_ASSERTION(sSrv, "Service gone away!?");
02464         sSrv->DeleteTemporaryFileOnExit(mFinalFileDestination);
02465       }
02466     }
02467   }
02468 
02469   return rv;
02470 }
02471 
02472 // LaunchWithApplication should only be called by the helper app dialog which allows
02473 // the user to say launch with application or save to disk. It doesn't actually 
02474 // perform launch with application. That won't happen until we are done downloading
02475 // the content and are sure we've showna progress dialog. This was done to simplify the 
02476 // logic that was showing up in this method. 
02477 
02478 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, PRBool aRememberThisPreference)
02479 {
02480   if (mCanceled)
02481     return NS_OK;
02482 
02483   // user has chosen to launch using an application, fire any refresh tags now...
02484   ProcessAnyRefreshTags(); 
02485   
02486   mReceivedDispositionInfo = PR_TRUE; 
02487   if (mMimeInfo && aApplication)
02488     mMimeInfo->SetPreferredApplicationHandler(aApplication);
02489 
02490   // Now check if the file is local, in which case we won't bother with saving
02491   // it to a temporary directory and just launch it from where it is
02492   nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
02493   if (fileUrl)
02494   {
02495     Cancel(NS_BINDING_ABORTED);
02496     nsCOMPtr<nsIFile> file;
02497     nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
02498 
02499     if (NS_SUCCEEDED(rv))
02500     {
02501       rv = mMimeInfo->LaunchWithFile(file);
02502       if (NS_SUCCEEDED(rv))
02503         return NS_OK;
02504     }
02505     nsAutoString path;
02506     if (file)
02507       file->GetPath(path);
02508     // If we get here, an error happened
02509     SendStatusChange(kLaunchError, rv, nsnull, path);
02510     return rv;
02511   }
02512 
02513   // Now that the user has elected to launch the downloaded file with a helper app, we're justified in
02514   // removing the 'salted' name.  We'll rename to what was specified in mSuggestedFileName after the
02515   // download is done prior to launching the helper app.  So that any existing file of that name won't
02516   // be overwritten we call CreateUnique() before calling MoveFile().  Also note that we use the same
02517   // directory as originally downloaded to so that MoveFile() just does an in place rename.
02518    
02519   nsCOMPtr<nsIFile> fileToUse;
02520   
02521   // The directories specified here must match those specified in SetUpTempFile().  This avoids
02522   // having to do a copy of the file when it finishes downloading and the potential for errors
02523   // that would introduce
02524 #if defined(XP_MAC) || defined (XP_MACOSX)
02525   NS_GetSpecialDirectory(NS_MAC_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(fileToUse));
02526 #else
02527   NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(fileToUse));
02528 #endif
02529 
02530   if (mSuggestedFileName.IsEmpty())
02531   {
02532     // Keep using the leafname of the temp file, since we're just starting a helper
02533     mTempFile->GetLeafName(mSuggestedFileName);
02534   }
02535 
02536 #ifdef XP_WIN
02537   fileToUse->Append(mSuggestedFileName + mTempFileExtension);
02538 #else
02539   fileToUse->Append(mSuggestedFileName);  
02540 #endif
02541   
02542   // We'll make sure this results in a unique name later
02543 
02544   mFinalFileDestination = do_QueryInterface(fileToUse);
02545 
02546   // launch the progress window now that the user has picked the desired action.
02547   if (!mProgressListenerInitialized)
02548    CreateProgressListener();
02549 
02550   return NS_OK;
02551 }
02552 
02553 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
02554 {
02555   NS_ENSURE_ARG(NS_FAILED(aReason));
02556   // XXX should not ignore the reason
02557 
02558   mCanceled = PR_TRUE;
02559   // Break our reference cycle with the helper app dialog (set up in
02560   // OnStartRequest)
02561   mDialog = nsnull;
02562   // shutdown our stream to the temp file
02563   if (mOutStream)
02564   {
02565     mOutStream->Close();
02566     mOutStream = nsnull;
02567   }
02568 
02569   // clean up after ourselves and delete the temp file...
02570   // but only if we got asked to open the file. when saving,
02571   // we leave the file there - the partial file might be useful
02572   // But if we haven't received disposition info yet, then we're
02573   // here because the user cancelled the helper app dialog.
02574   // Delete the file in this case.
02575   nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk;
02576   mMimeInfo->GetPreferredAction(&action);
02577   if (mTempFile &&
02578       (!mReceivedDispositionInfo || action != nsIMIMEInfo::saveToDisk))
02579   {
02580     mTempFile->Remove(PR_FALSE);
02581     mTempFile = nsnull;
02582   }
02583 
02584   // Release the listener, to break the reference cycle with it (we are the
02585   // observer of the listener).
02586   mWebProgressListener = nsnull;
02587 
02588   return NS_OK;
02589 }
02590 
02591 void nsExternalAppHandler::ProcessAnyRefreshTags()
02592 {
02593    // one last thing, try to see if the original window context supports a refresh interface...
02594    // Sometimes, when you download content that requires an external handler, there is
02595    // a refresh header associated with the download. This refresh header points to a page
02596    // the content provider wants the user to see after they download the content. How do we
02597    // pass this refresh information back to the caller? For now, try to get the refresh URI 
02598    // interface. If the window context where the request originated came from supports this
02599    // then we can force it to process the refresh information (if there is any) from this channel.
02600    if (mWindowContext && mOriginalChannel)
02601    {
02602      nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext));
02603      if (refreshHandler) {
02604         refreshHandler->SetupRefreshURI(mOriginalChannel);
02605      }
02606      mOriginalChannel = nsnull;
02607    }
02608 }
02609 
02610 PRBool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
02611 {
02612   // Search the obsolete pref strings.
02613   nsresult rv;
02614   nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
02615   nsCOMPtr<nsIPrefBranch> prefBranch;
02616   if (prefs)
02617     rv = prefs->GetBranch(NEVER_ASK_PREF_BRANCH, getter_AddRefs(prefBranch));
02618   if (NS_SUCCEEDED(rv) && prefBranch)
02619   {
02620     nsXPIDLCString prefCString;
02621     nsCAutoString prefValue;
02622     rv = prefBranch->GetCharPref(prefName, getter_Copies(prefCString));
02623     if (NS_SUCCEEDED(rv) && !prefCString.IsEmpty())
02624     {
02625       NS_UnescapeURL(prefCString);
02626       nsACString::const_iterator start, end;
02627       prefCString.BeginReading(start);
02628       prefCString.EndReading(end);
02629       if (CaseInsensitiveFindInReadable(nsDependentCString(aContentType), start, end))
02630         return PR_FALSE;
02631     }
02632   }
02633   // Default is true, if not found in the pref string.
02634   return PR_TRUE;
02635 }
02636 
02637 nsresult nsExternalAppHandler::MaybeCloseWindow()
02638 {
02639   nsCOMPtr<nsIDOMWindow> window(do_GetInterface(mWindowContext));
02640   nsCOMPtr<nsIDOMWindowInternal> internalWindow = do_QueryInterface(window);
02641   NS_ENSURE_STATE(internalWindow);
02642 
02643   if (mShouldCloseWindow) {
02644     // Reset the window context to the opener window so that the dependent
02645     // dialogs have a parent
02646     nsCOMPtr<nsIDOMWindowInternal> opener;
02647     internalWindow->GetOpener(getter_AddRefs(opener));
02648 
02649     PRBool isClosed;
02650     if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) {
02651       mWindowContext = do_GetInterface(opener);
02652 
02653       // Now close the old window.  Do it on a timer so that we don't run
02654       // into issues trying to close the window before it has fully opened.
02655       NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
02656       mTimer = do_CreateInstance("@mozilla.org/timer;1");
02657       if (!mTimer) {
02658         return NS_ERROR_FAILURE;
02659       }
02660 
02661       mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
02662       mWindowToClose = internalWindow;
02663     }
02664   }
02665 
02666   return NS_OK;
02667 }
02668 
02669 NS_IMETHODIMP
02670 nsExternalAppHandler::Notify(nsITimer* timer)
02671 {
02672   NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
02673 
02674   mWindowToClose->Close();
02675   mWindowToClose = nsnull;
02676   mTimer = nsnull;
02677 
02678   return NS_OK;
02679 }
02681 // The following section contains our nsIMIMEService implementation and related methods.
02682 //
02684 
02685 // nsIMIMEService methods
02686 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval) 
02687 {
02688   NS_PRECONDITION(!aMIMEType.IsEmpty() ||
02689                   !aFileExt.IsEmpty(), 
02690                   "Give me something to work with");
02691   LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
02692         PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
02693 
02694   *_retval = nsnull;
02695 
02696   // OK... we need a type. Get one.
02697   nsCAutoString typeToUse(aMIMEType);
02698   if (typeToUse.IsEmpty()) {
02699     nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
02700     if (NS_FAILED(rv))
02701       return NS_ERROR_NOT_AVAILABLE;
02702   }
02703 
02704   // (1) Ask the OS for a mime info
02705   PRBool found;
02706   *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).get();
02707   LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
02708   // If we got no mimeinfo, something went wrong. Probably lack of memory.
02709   if (!*_retval)
02710     return NS_ERROR_OUT_OF_MEMORY;
02711 
02712   // (2) Now, let's see if we can find something in our datasource
02713   // This will not overwrite the OS information that interests us
02714   // (i.e. default application, default app. description)
02715   nsresult rv = GetMIMEInfoForMimeTypeFromDS(typeToUse, *_retval);
02716   found = found || NS_SUCCEEDED(rv);
02717 
02718   LOG(("Data source: Via type: retval 0x%08x\n", rv));
02719 
02720   if (!found || NS_FAILED(rv)) {
02721     // No type match, try extension match
02722     if (!aFileExt.IsEmpty()) {
02723       rv = GetMIMEInfoForExtensionFromDS(aFileExt, *_retval);
02724       LOG(("Data source: Via ext: retval 0x%08x\n", rv));
02725       found = found || NS_SUCCEEDED(rv);
02726     }
02727   }
02728 
02729   // (3) No match yet. Ask extras.
02730   if (!found) {
02731     rv = NS_ERROR_FAILURE;
02732 #ifdef XP_WIN
02733     /* XXX Gross hack to wallpaper over the most common Win32
02734      * extension issues caused by the fix for bug 116938.  See bug
02735      * 120327, comment 271 for why this is needed.  Not even sure we
02736      * want to remove this once we have fixed all this stuff to work
02737      * right; any info we get from extras on this type is pretty much
02738      * useless....
02739      */
02740     if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
02741 #endif
02742       rv = GetMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
02743     LOG(("Searched extras (by type), rv 0x%08X\n", rv));
02744     // If that didn't work out, try file extension from extras
02745     if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
02746       rv = GetMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
02747       LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
02748     }
02749   }
02750 
02751   // Finally, check if we got a file extension and if yes, if it is an
02752   // extension on the mimeinfo, in which case we want it to be the primary one
02753   if (!aFileExt.IsEmpty()) {
02754     PRBool matches = PR_FALSE;
02755     (*_retval)->ExtensionExists(aFileExt, &matches);
02756     LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
02757     if (matches)
02758       (*_retval)->SetPrimaryExtension(aFileExt);
02759   }
02760 
02761 #ifdef PR_LOGGING
02762   if (LOG_ENABLED()) {
02763     nsCAutoString type;
02764     (*_retval)->GetMIMEType(type);
02765 
02766     nsCAutoString ext;
02767     (*_retval)->GetPrimaryExtension(ext);
02768     LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
02769   }
02770 #endif
02771 
02772   return NS_OK;
02773 }
02774 
02775 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType) 
02776 {
02777   // OK. We want to try the following sources of mimetype information, in this order:
02778   // 1. defaultMimeEntries array
02779   // 2. User-set preferences (mimeTypes.rdf)
02780   // 3. OS-provided information
02781   // 4. our "extras" array
02782   // 5. Information from plugins
02783   // 6. The "ext-to-type-mapping" category
02784 
02785   nsresult rv = NS_OK;
02786   // First of all, check our default entries
02787   for (size_t i = 0; i < NS_ARRAY_LENGTH(defaultMimeEntries); i++)
02788   {
02789     if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) {
02790       aContentType = defaultMimeEntries[i].mMimeType;
02791       return rv;
02792     }
02793   }
02794 
02795   // Check RDF DS
02796   PRBool found = GetTypeFromDS(aFileExt, aContentType);
02797   if (found)
02798     return NS_OK;
02799 
02800   // Ask OS.
02801   nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
02802   if (mi && found)
02803     return mi->GetMIMEType(aContentType);
02804 
02805   // Check extras array.
02806   found = GetTypeFromExtras(aFileExt, aContentType);
02807   if (found)
02808     return NS_OK;
02809 
02810   const nsCString& flatExt = PromiseFlatCString(aFileExt);
02811   // Try the plugins
02812   const char* mimeType;
02813   nsCOMPtr<nsIPluginHost> pluginHost (do_GetService(kPluginManagerCID, &rv));
02814   if (NS_SUCCEEDED(rv)) {
02815     if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType)))
02816     {
02817       aContentType = mimeType;
02818       return NS_OK;
02819     }
02820   }
02821   
02822   rv = NS_OK;
02823   // Let's see if an extension added something
02824   nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
02825   if (catMan) {
02826     nsXPIDLCString type;
02827     rv = catMan->GetCategoryEntry("ext-to-type-mapping", flatExt.get(), getter_Copies(type));
02828     aContentType = type;
02829   }
02830   else {
02831     rv = NS_ERROR_NOT_AVAILABLE;
02832   }
02833   
02834   return rv;
02835 }
02836 
02837 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
02838 {
02839   NS_ENSURE_ARG(!aMIMEType.IsEmpty());
02840 
02841   nsCOMPtr<nsIMIMEInfo> mi;
02842   nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
02843   if (NS_FAILED(rv))
02844     return rv;
02845 
02846   return mi->GetPrimaryExtension(_retval);
02847 }
02848 
02849 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType) 
02850 {
02851   nsresult rv = NS_ERROR_NOT_AVAILABLE;
02852   aContentType.Truncate();
02853 
02854   // First look for a file to use.  If we have one, we just use that.
02855   nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
02856   if (fileUrl) {
02857     nsCOMPtr<nsIFile> file;
02858     rv = fileUrl->GetFile(getter_AddRefs(file));
02859     if (NS_SUCCEEDED(rv)) {
02860       rv = GetTypeFromFile(file, aContentType);
02861       if (NS_SUCCEEDED(rv)) {
02862         // we got something!
02863         return rv;
02864       }
02865     }
02866   }
02867 
02868   // Now try to get an nsIURL so we don't have to do our own parsing
02869   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
02870   if (url) {
02871     nsCAutoString ext;
02872     rv = url->GetFileExtension(ext);
02873     if (NS_FAILED(rv))
02874       return rv;
02875     if (ext.IsEmpty())
02876       return NS_ERROR_NOT_AVAILABLE;
02877 
02878     UnescapeFragment(ext, url, ext);
02879 
02880     return GetTypeFromExtension(ext, aContentType);
02881   }
02882     
02883   // no url, let's give the raw spec a shot
02884   nsCAutoString specStr;
02885   rv = aURI->GetSpec(specStr);
02886   if (NS_FAILED(rv))
02887     return rv;
02888   UnescapeFragment(specStr, aURI, specStr);
02889 
02890   // find the file extension (if any)
02891   PRInt32 extLoc = specStr.RFindChar('.');
02892   PRInt32 specLength = specStr.Length();
02893   if (-1 != extLoc &&
02894       extLoc != specLength - 1 &&
02895       // nothing over 20 chars long can be sanely considered an
02896       // extension.... Dat dere would be just data.
02897       specLength - extLoc < 20) 
02898   {
02899     return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
02900   }
02901 
02902   // We found no information; say so.
02903   return NS_ERROR_NOT_AVAILABLE;
02904 }
02905 
02906 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
02907 {
02908   nsresult rv;
02909   nsCOMPtr<nsIMIMEInfo> info;
02910 
02911   // Get the Extension
02912   nsAutoString fileName;
02913   rv = aFile->GetLeafName(fileName);
02914   if (NS_FAILED(rv)) return rv;
02915  
02916   nsCAutoString fileExt;
02917   if (!fileName.IsEmpty())
02918   {
02919     PRInt32 len = fileName.Length(); 
02920     for (PRInt32 i = len; i >= 0; i--) 
02921     {
02922       if (fileName[i] == PRUnichar('.'))
02923       {
02924         CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
02925         break;
02926       }
02927     }
02928   }
02929   
02930   // Handle the mac case
02931 #if defined(XP_MAC) || defined (XP_MACOSX)
02932   nsCOMPtr<nsILocalFileMac> macFile;
02933   macFile = do_QueryInterface( aFile, &rv );
02934   if ( NS_SUCCEEDED( rv ) && fileExt.IsEmpty())
02935   {
02936        PRUint32 type, creator;
02937        macFile->GetFileType( (OSType*)&type );
02938        macFile->GetFileCreator( (OSType*)&creator );   
02939        nsCOMPtr<nsIInternetConfigService> icService (do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID));
02940     if (icService)
02941     {
02942       rv = icService->GetMIMEInfoFromTypeCreator(type, creator, fileExt.get(), getter_AddRefs(info));                                                            
02943       if (NS_SUCCEEDED(rv))
02944            return info->GetMIMEType(aContentType);
02945        }
02946   }
02947 #endif
02948   // Windows, unix and mac when no type match occured.   
02949   if (fileExt.IsEmpty())
02950          return NS_ERROR_FAILURE;    
02951   return GetTypeFromExtension(fileExt, aContentType);
02952 }
02953 
02954 nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromExtras(const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo )
02955 {
02956   NS_ENSURE_ARG( aMIMEInfo );
02957 
02958   NS_ENSURE_ARG( !aContentType.IsEmpty() );
02959 
02960   // Look for default entry with matching mime type.
02961   nsCAutoString MIMEType(aContentType);
02962   ToLowerCase(MIMEType);
02963   PRInt32 numEntries = NS_ARRAY_LENGTH(extraMimeEntries);
02964   for (PRInt32 index = 0; index < numEntries; index++)
02965   {
02966       if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
02967       {
02968           // This is the one. Set attributes appropriately.
02969           aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
02970           aMIMEInfo->SetDescription(NS_ConvertASCIItoUCS2(extraMimeEntries[index].mDescription));
02971           aMIMEInfo->SetMacType(extraMimeEntries[index].mMactype);
02972           aMIMEInfo->SetMacCreator(extraMimeEntries[index].mMacCreator);
02973 
02974           return NS_OK;
02975       }
02976   }
02977 
02978   return NS_ERROR_NOT_AVAILABLE;
02979 }
02980 
02981 nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromExtras(const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
02982 {
02983   nsCAutoString type;
02984   PRBool found = GetTypeFromExtras(aExtension, type);
02985   if (!found)
02986     return NS_ERROR_NOT_AVAILABLE;
02987   return GetMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
02988 }
02989 
02990 PRBool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
02991 {
02992   NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
02993 
02994   // Look for default entry with matching extension.
02995   nsDependentCString::const_iterator start, end, iter;
02996   PRInt32 numEntries = NS_ARRAY_LENGTH(extraMimeEntries);
02997   for (PRInt32 index = 0; index < numEntries; index++)
02998   {
02999       nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
03000       extList.BeginReading(start);
03001       extList.EndReading(end);
03002       iter = start;
03003       while (start != end)
03004       {
03005           FindCharInReadable(',', iter, end);
03006           if (Substring(start, iter).Equals(aExtension,
03007                                             nsCaseInsensitiveCStringComparator()))
03008           {
03009               aMIMEType = extraMimeEntries[index].mMimeType;
03010               return PR_TRUE;
03011           }
03012           if (iter != end) {
03013             ++iter;
03014           }
03015           start = iter;
03016       }
03017   }
03018 
03019   return PR_FALSE;
03020 }