uriloader/exthandler/nsExternalHelperAppService.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * vim:expandtab:shiftwidth=2:tabstop=2:cin:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #ifdef MOZ_LOGGING
     8 #define FORCE_PR_LOG
     9 #endif
    11 #include "base/basictypes.h"
    13 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
    14 #include "mozilla/ArrayUtils.h"
    15 #include "mozilla/Base64.h"
    17 #include "mozilla/dom/ContentChild.h"
    18 #include "mozilla/dom/TabChild.h"
    19 #include "nsXULAppAPI.h"
    21 #include "nsExternalHelperAppService.h"
    22 #include "nsCExternalHandlerService.h"
    23 #include "nsIURI.h"
    24 #include "nsIURL.h"
    25 #include "nsIFile.h"
    26 #include "nsIFileURL.h"
    27 #include "nsIChannel.h"
    28 #include "nsIDirectoryService.h"
    29 #include "nsAppDirectoryServiceDefs.h"
    30 #include "nsICategoryManager.h"
    31 #include "nsDependentSubstring.h"
    32 #include "nsXPIDLString.h"
    33 #include "nsUnicharUtils.h"
    34 #include "nsIStringEnumerator.h"
    35 #include "nsMemory.h"
    36 #include "nsIStreamListener.h"
    37 #include "nsIMIMEService.h"
    38 #include "nsILoadGroup.h"
    39 #include "nsIWebProgressListener.h"
    40 #include "nsITransfer.h"
    41 #include "nsReadableUtils.h"
    42 #include "nsIRequest.h"
    43 #include "nsDirectoryServiceDefs.h"
    44 #include "nsIInterfaceRequestor.h"
    45 #include "nsThreadUtils.h"
    46 #include "nsAutoPtr.h"
    47 #include "nsIMutableArray.h"
    49 // used to access our datastore of user-configured helper applications
    50 #include "nsIHandlerService.h"
    51 #include "nsIMIMEInfo.h"
    52 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
    53 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
    54 #include "nsIHelperAppLauncherDialog.h"
    55 #include "nsIContentDispatchChooser.h"
    56 #include "nsNetUtil.h"
    57 #include "nsIIOService.h"
    58 #include "nsNetCID.h"
    59 #include "nsChannelProperties.h"
    61 #include "nsMimeTypes.h"
    62 // used for header disposition information.
    63 #include "nsIHttpChannel.h"
    64 #include "nsIHttpChannelInternal.h"
    65 #include "nsIEncodedChannel.h"
    66 #include "nsIMultiPartChannel.h"
    67 #include "nsIFileChannel.h"
    68 #include "nsIObserverService.h" // so we can be a profile change observer
    69 #include "nsIPropertyBag2.h" // for the 64-bit content length
    71 #ifdef XP_MACOSX
    72 #include "nsILocalFileMac.h"
    73 #endif
    75 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
    76 #include "nsPluginHost.h"
    77 #include "nsEscape.h"
    79 #include "nsIStringBundle.h" // XXX needed to localize error msgs
    80 #include "nsIPrompt.h"
    82 #include "nsITextToSubURI.h" // to unescape the filename
    83 #include "nsIMIMEHeaderParam.h"
    85 #include "nsIWindowWatcher.h"
    87 #include "nsIDownloadHistory.h" // to mark downloads as visited
    88 #include "nsDocShellCID.h"
    90 #include "nsCRT.h"
    91 #include "nsLocalHandlerApp.h"
    93 #include "nsIRandomGenerator.h"
    95 #include "ContentChild.h"
    96 #include "nsXULAppAPI.h"
    97 #include "nsPIDOMWindow.h"
    98 #include "nsIDocShellTreeOwner.h"
    99 #include "nsIDocShellTreeItem.h"
   100 #include "ExternalHelperAppChild.h"
   102 #ifdef XP_WIN
   103 #include "nsWindowsHelpers.h"
   104 #endif
   106 #ifdef MOZ_WIDGET_ANDROID
   107 #include "AndroidBridge.h"
   108 #endif
   110 #include "mozilla/Preferences.h"
   111 #include "mozilla/ipc/URIUtils.h"
   113 #ifdef MOZ_WIDGET_GONK
   114 #include "nsDeviceStorage.h"
   115 #endif
   117 #ifdef NECKO_PROTOCOL_rtsp
   118 #include "nsIScriptSecurityManager.h"
   119 #include "nsIMessageManager.h"
   120 #endif
   122 using namespace mozilla;
   123 using namespace mozilla::ipc;
   125 // Download Folder location constants
   126 #define NS_PREF_DOWNLOAD_DIR        "browser.download.dir"
   127 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
   128 enum {
   129   NS_FOLDER_VALUE_DESKTOP = 0
   130 , NS_FOLDER_VALUE_DOWNLOADS = 1
   131 , NS_FOLDER_VALUE_CUSTOM = 2
   132 };
   134 #ifdef PR_LOGGING
   135 PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr;
   136 #endif
   138 // Using level 3 here because the OSHelperAppServices use a log level
   139 // of PR_LOG_DEBUG (4), and we want less detailed output here
   140 // Using 3 instead of PR_LOG_WARN because we don't output warnings
   141 #undef LOG
   142 #define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
   143 #define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
   145 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
   146   "browser.helperApps.neverAsk.saveToDisk";
   147 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
   148   "browser.helperApps.neverAsk.openFile";
   150 // Helper functions for Content-Disposition headers
   152 /**
   153  * Given a URI fragment, unescape it
   154  * @param aFragment The string to unescape
   155  * @param aURI The URI from which this fragment is taken. Only its character set
   156  *             will be used.
   157  * @param aResult [out] Unescaped string.
   158  */
   159 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
   160                                  nsAString& aResult)
   161 {
   162   // First, we need a charset
   163   nsAutoCString originCharset;
   164   nsresult rv = aURI->GetOriginCharset(originCharset);
   165   NS_ENSURE_SUCCESS(rv, rv);
   167   // Now, we need the unescaper
   168   nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
   169   NS_ENSURE_SUCCESS(rv, rv);
   171   return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
   172 }
   174 /**
   175  * UTF-8 version of UnescapeFragment.
   176  * @param aFragment The string to unescape
   177  * @param aURI The URI from which this fragment is taken. Only its character set
   178  *             will be used.
   179  * @param aResult [out] Unescaped string, UTF-8 encoded.
   180  * @note It is safe to pass the same string for aFragment and aResult.
   181  * @note When this function fails, aResult will not be modified.
   182  */
   183 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
   184                                  nsACString& aResult)
   185 {
   186   nsAutoString result;
   187   nsresult rv = UnescapeFragment(aFragment, aURI, result);
   188   if (NS_SUCCEEDED(rv))
   189     CopyUTF16toUTF8(result, aResult);
   190   return rv;
   191 }
   193 /**
   194  * Given a channel, returns the filename and extension the channel has.
   195  * This uses the URL and other sources (nsIMultiPartChannel).
   196  * Also gives back whether the channel requested external handling (i.e.
   197  * whether Content-Disposition: attachment was sent)
   198  * @param aChannel The channel to extract the filename/extension from
   199  * @param aFileName [out] Reference to the string where the filename should be
   200  *        stored. Empty if it could not be retrieved.
   201  *        WARNING - this filename may contain characters which the OS does not
   202  *        allow as part of filenames!
   203  * @param aExtension [out] Reference to the string where the extension should
   204  *        be stored. Empty if it could not be retrieved. Stored in UTF-8.
   205  * @param aAllowURLExtension (optional) Get the extension from the URL if no
   206  *        Content-Disposition header is present. Default is true.
   207  * @retval true The server sent Content-Disposition:attachment or equivalent
   208  * @retval false Content-Disposition: inline or no content-disposition header
   209  *         was sent.
   210  */
   211 static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
   212                                                  nsString& aFileName,
   213                                                  nsCString& aExtension,
   214                                                  bool aAllowURLExtension = true)
   215 {
   216   aExtension.Truncate();
   217   /*
   218    * If the channel is an http or part of a multipart channel and we
   219    * have a content disposition header set, then use the file name
   220    * suggested there as the preferred file name to SUGGEST to the
   221    * user.  we shouldn't actually use that without their
   222    * permission... otherwise just use our temp file
   223    */
   224   bool handleExternally = false;
   225   uint32_t disp;
   226   nsresult rv = aChannel->GetContentDisposition(&disp);
   227   if (NS_SUCCEEDED(rv))
   228   {
   229     aChannel->GetContentDispositionFilename(aFileName);
   230     if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
   231       handleExternally = true;
   232   }
   234   // If the disposition header didn't work, try the filename from nsIURL
   235   nsCOMPtr<nsIURI> uri;
   236   aChannel->GetURI(getter_AddRefs(uri));
   237   nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
   238   if (url && aFileName.IsEmpty())
   239   {
   240     if (aAllowURLExtension) {
   241       url->GetFileExtension(aExtension);
   242       UnescapeFragment(aExtension, url, aExtension);
   244       // Windows ignores terminating dots. So we have to as well, so
   245       // that our security checks do "the right thing"
   246       // In case the aExtension consisted only of the dot, the code below will
   247       // extract an aExtension from the filename
   248       aExtension.Trim(".", false);
   249     }
   251     // try to extract the file name from the url and use that as a first pass as the
   252     // leaf name of our temp file...
   253     nsAutoCString leafName;
   254     url->GetFileName(leafName);
   255     if (!leafName.IsEmpty())
   256     {
   257       rv = UnescapeFragment(leafName, url, aFileName);
   258       if (NS_FAILED(rv))
   259       {
   260         CopyUTF8toUTF16(leafName, aFileName); // use escaped name
   261       }
   262     }
   263   }
   265   // Extract Extension, if we have a filename; otherwise,
   266   // truncate the string
   267   if (aExtension.IsEmpty()) {
   268     if (!aFileName.IsEmpty())
   269     {
   270       // Windows ignores terminating dots. So we have to as well, so
   271       // that our security checks do "the right thing"
   272       aFileName.Trim(".", false);
   274       // XXX RFindCharInReadable!!
   275       nsAutoString fileNameStr(aFileName);
   276       int32_t idx = fileNameStr.RFindChar(char16_t('.'));
   277       if (idx != kNotFound)
   278         CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
   279     }
   280   }
   283   return handleExternally;
   284 }
   286 /**
   287  * Obtains the directory to use.  This tends to vary per platform, and
   288  * needs to be consistent throughout our codepaths. For platforms where
   289  * helper apps use the downloads directory, this should be kept in
   290  * sync with nsDownloadManager.cpp
   291  *
   292  * Optionally skip availability of the directory and storage.
   293  */
   294 static nsresult GetDownloadDirectory(nsIFile **_directory,
   295                                      bool aSkipChecks = false)
   296 {
   297   nsCOMPtr<nsIFile> dir;
   298 #ifdef XP_MACOSX
   299   // On OS X, we first try to get the users download location, if it's set.
   300   switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
   301     case NS_FOLDER_VALUE_DESKTOP:
   302       (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
   303       break;
   304     case NS_FOLDER_VALUE_CUSTOM:
   305       {
   306         Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
   307                                 NS_GET_IID(nsIFile),
   308                                 getter_AddRefs(dir));
   309         if (!dir) break;
   311         // If we're not checking for availability we're done.
   312         if (aSkipChecks) {
   313           dir.forget(_directory);
   314           return NS_OK;
   315         }
   317         // We have the directory, and now we need to make sure it exists
   318         bool dirExists = false;
   319         (void) dir->Exists(&dirExists);
   320         if (dirExists) break;
   322         nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
   323         if (NS_FAILED(rv)) {
   324           dir = nullptr;
   325           break;
   326         }
   327       }
   328       break;
   329     case NS_FOLDER_VALUE_DOWNLOADS:
   330       // This is just the OS default location, so fall out
   331       break;
   332   }
   334   if (!dir) {
   335     // If not, we default to the OS X default download location.
   336     nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
   337                                          getter_AddRefs(dir));
   338     NS_ENSURE_SUCCESS(rv, rv);
   339   }
   340 #elif defined(MOZ_WIDGET_GONK)
   341   // On Gonk, store the files on the sdcard in the downloads directory.
   342   // We need to check with the volume manager which storage point is
   343   // available.
   345   // Pick the default storage in case multiple (internal and external) ones
   346   // are available.
   347   nsString storageName;
   348   nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
   349                                             storageName);
   351   DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
   352                         storageName,
   353                         NS_LITERAL_STRING("downloads"));
   354   NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED);
   356   // If we're not checking for availability we're done.
   357   if (aSkipChecks) {
   358     dsf.mFile.forget(_directory);
   359     return NS_OK;
   360   }
   362   // Check device storage status before continuing.
   363   nsString storageStatus;
   364   dsf.GetStatus(storageStatus);
   366   // If we get an "unavailable" status, it means the sd card is not present.
   367   // We'll also catch internal errors by looking for an empty string and assume
   368   // the SD card isn't present when this occurs.
   369   if (storageStatus.EqualsLiteral("unavailable") ||
   370       storageStatus.IsEmpty()) {
   371     return NS_ERROR_FILE_NOT_FOUND;
   372   }
   374   // If we get a status other than 'available' here it means the card is busy
   375   // because it's mounted via USB or it is being formatted.
   376   if (!storageStatus.EqualsLiteral("available")) {
   377     return NS_ERROR_FILE_ACCESS_DENIED;
   378   }
   380   bool alreadyThere;
   381   nsresult rv = dsf.mFile->Exists(&alreadyThere);
   382   NS_ENSURE_SUCCESS(rv, rv);
   383   if (!alreadyThere) {
   384     rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
   385     NS_ENSURE_SUCCESS(rv, rv);
   386   }
   387   dir = dsf.mFile;
   388 #elif defined(ANDROID)
   389   // On mobile devices, we are avoiding exposing users to the file
   390   // system, and don't save downloads to temp directories
   392   // On Android we only return something if we have and SD-card
   393   char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
   394   nsresult rv;
   395   if (downloadDir) {
   396     nsCOMPtr<nsIFile> ldir;
   397     rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir),
   398                                true, getter_AddRefs(ldir));
   399     NS_ENSURE_SUCCESS(rv, rv);
   400     dir = do_QueryInterface(ldir);
   402     // If we're not checking for availability we're done.
   403     if (aSkipChecks) {
   404       dir.forget(_directory);
   405       return NS_OK;
   406     }
   407   }
   408   else {
   409     return NS_ERROR_FAILURE;
   410   }
   411 #elif defined(XP_WIN)
   412   // On metro we want to be able to search opened files and the temp directory
   413   // is exlcuded in searches.
   414   nsresult rv;
   415   if (IsRunningInWindowsMetro()) {
   416     rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir));
   417   } else {
   418     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
   419   }
   420   NS_ENSURE_SUCCESS(rv, rv);
   421 #else
   422   // On all other platforms, we default to the systems temporary directory.
   423   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
   424   NS_ENSURE_SUCCESS(rv, rv);
   425 #endif
   427   NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
   428   dir.forget(_directory);
   429   return NS_OK;
   430 }
   432 /**
   433  * Structure for storing extension->type mappings.
   434  * @see defaultMimeEntries
   435  */
   436 struct nsDefaultMimeTypeEntry {
   437   const char* mMimeType;
   438   const char* mFileExtension;
   439 };
   441 /**
   442  * Default extension->mimetype mappings. These are not overridable.
   443  * If you add types here, make sure they are lowercase, or you'll regret it.
   444  */
   445 static nsDefaultMimeTypeEntry defaultMimeEntries [] = 
   446 {
   447   // The following are those extensions that we're asked about during startup,
   448   // sorted by order used
   449   { IMAGE_GIF, "gif" },
   450   { TEXT_XML, "xml" },
   451   { APPLICATION_RDF, "rdf" },
   452   { TEXT_XUL, "xul" },
   453   { IMAGE_PNG, "png" },
   454   // -- end extensions used during startup
   455   { TEXT_CSS, "css" },
   456   { IMAGE_JPEG, "jpeg" },
   457   { IMAGE_JPEG, "jpg" },
   458   { IMAGE_SVG_XML, "svg" },
   459   { TEXT_HTML, "html" },
   460   { TEXT_HTML, "htm" },
   461   { APPLICATION_XPINSTALL, "xpi" },
   462   { "application/xhtml+xml", "xhtml" },
   463   { "application/xhtml+xml", "xht" },
   464   { TEXT_PLAIN, "txt" },
   465   { VIDEO_OGG, "ogv" },
   466   { VIDEO_OGG, "ogg" },
   467   { APPLICATION_OGG, "ogg" },
   468   { AUDIO_OGG, "oga" },
   469 #ifdef MOZ_OPUS
   470   { AUDIO_OGG, "opus" },
   471 #endif
   472 #ifdef MOZ_WEBM
   473   { VIDEO_WEBM, "webm" },
   474   { AUDIO_WEBM, "webm" },
   475 #endif
   476 #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
   477   { VIDEO_MP4, "mp4" },
   478   { AUDIO_MP4, "m4a" },
   479   { AUDIO_MP3, "mp3" },
   480 #endif
   481 #ifdef MOZ_RAW
   482   { VIDEO_RAW, "yuv" }
   483 #endif
   484 };
   486 /**
   487  * This is a small private struct used to help us initialize some
   488  * default mime types.
   489  */
   490 struct nsExtraMimeTypeEntry {
   491   const char* mMimeType; 
   492   const char* mFileExtensions;
   493   const char* mDescription;
   494 };
   496 #ifdef XP_MACOSX
   497 #define MAC_TYPE(x) x
   498 #else
   499 #define MAC_TYPE(x) 0
   500 #endif
   502 /**
   503  * This table lists all of the 'extra' content types that we can deduce from particular
   504  * file extensions.  These entries also ensure that we provide a good descriptive name
   505  * when we encounter files with these content types and/or extensions.  These can be
   506  * overridden by user helper app prefs.
   507  * If you add types here, make sure they are lowercase, or you'll regret it.
   508  */
   509 static nsExtraMimeTypeEntry extraMimeEntries [] =
   510 {
   511 #if defined(VMS)
   512   { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" },
   513 #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
   514   { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
   515 #else
   516   { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
   517 #endif
   518   { APPLICATION_GZIP2, "gz", "gzip" },
   519   { "application/x-arj", "arj", "ARJ file" },
   520   { "application/rtf", "rtf", "Rich Text Format File" },
   521   { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
   522   { APPLICATION_PDF, "pdf", "Portable Document Format" },
   523   { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
   524   { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
   525   { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
   526 #ifdef MOZ_WIDGET_ANDROID
   527   { "application/vnd.android.package-archive", "apk", "Android Package" },
   528 #endif
   529   { IMAGE_ART, "art", "ART Image" },
   530   { IMAGE_BMP, "bmp", "BMP Image" },
   531   { IMAGE_GIF, "gif", "GIF Image" },
   532   { IMAGE_ICO, "ico,cur", "ICO Image" },
   533   { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
   534   { IMAGE_PNG, "png", "PNG Image" },
   535   { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
   536   { IMAGE_XBM, "xbm", "XBM Image" },
   537   { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
   538   { MESSAGE_RFC822, "eml", "RFC-822 data" },
   539   { TEXT_PLAIN, "txt,text", "Text File" },
   540   { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
   541   { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
   542   { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
   543   { APPLICATION_RDF, "rdf", "Resource Description Framework" },
   544   { TEXT_XUL, "xul", "XML-Based User Interface Language" },
   545   { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
   546   { TEXT_CSS, "css", "Style Sheet" },
   547   { TEXT_VCARD, "vcf,vcard", "Contact Information" },
   548   { VIDEO_OGG, "ogv", "Ogg Video" },
   549   { VIDEO_OGG, "ogg", "Ogg Video" },
   550   { APPLICATION_OGG, "ogg", "Ogg Video"},
   551   { AUDIO_OGG, "oga", "Ogg Audio" },
   552   { AUDIO_OGG, "opus", "Opus Audio" },
   553 #ifdef MOZ_WIDGET_GONK
   554   { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
   555 #endif
   556   { VIDEO_WEBM, "webm", "Web Media Video" },
   557   { AUDIO_WEBM, "webm", "Web Media Audio" },
   558   { AUDIO_MP3, "mp3", "MPEG Audio" },
   559   { VIDEO_MP4, "mp4", "MPEG-4 Video" },
   560   { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
   561   { VIDEO_RAW, "yuv", "Raw YUV Video" },
   562   { AUDIO_WAV, "wav", "Waveform Audio" },
   563   { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
   564   { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
   565 };
   567 #undef MAC_TYPE
   569 /**
   570  * File extensions for which decoding should be disabled.
   571  * NOTE: These MUST be lower-case and ASCII.
   572  */
   573 static nsDefaultMimeTypeEntry nonDecodableExtensions [] = {
   574   { APPLICATION_GZIP, "gz" }, 
   575   { APPLICATION_GZIP, "tgz" },
   576   { APPLICATION_ZIP, "zip" },
   577   { APPLICATION_COMPRESS, "z" },
   578   { APPLICATION_GZIP, "svgz" }
   579 };
   581 NS_IMPL_ISUPPORTS(
   582   nsExternalHelperAppService,
   583   nsIExternalHelperAppService,
   584   nsPIExternalAppLauncher,
   585   nsIExternalProtocolService,
   586   nsIMIMEService,
   587   nsIObserver,
   588   nsISupportsWeakReference)
   590 nsExternalHelperAppService::nsExternalHelperAppService()
   591 {
   592 }
   593 nsresult nsExternalHelperAppService::Init()
   594 {
   595   // Add an observer for profile change
   596   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   597   if (!obs)
   598     return NS_ERROR_FAILURE;
   600 #ifdef PR_LOGGING
   601   if (!mLog) {
   602     mLog = PR_NewLogModule("HelperAppService");
   603     if (!mLog)
   604       return NS_ERROR_OUT_OF_MEMORY;
   605   }
   606 #endif
   608   nsresult rv = obs->AddObserver(this, "profile-before-change", true);
   609   NS_ENSURE_SUCCESS(rv, rv);
   610   return obs->AddObserver(this, "last-pb-context-exited", true);
   611 }
   613 nsExternalHelperAppService::~nsExternalHelperAppService()
   614 {
   615 }
   617 #ifdef NECKO_PROTOCOL_rtsp
   618 namespace {
   619 /**
   620  * A stack helper to clear the currently pending exception in a JS context.
   621  */
   622 class AutoClearPendingException {
   623 public:
   624   AutoClearPendingException(JSContext* aCx) :
   625     mCx(aCx) {
   626   }
   627   ~AutoClearPendingException() {
   628     JS_ClearPendingException(mCx);
   629   }
   630 private:
   631   JSContext *mCx;
   632 };
   633 } // anonymous namespace
   635 /**
   636  * This function sends a message. This 'content-handler' message is handled in
   637  * b2g/chrome/content/shell.js where it starts an activity request that will
   638  * open the video app.
   639  */
   640 void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI)
   641 {
   642   bool rv;
   644   // Get a system principal.
   645   nsCOMPtr<nsIScriptSecurityManager> securityManager =
   646     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
   647   NS_ENSURE_TRUE_VOID(securityManager);
   649   nsCOMPtr<nsIPrincipal> principal;
   650   securityManager->GetSystemPrincipal(getter_AddRefs(principal));
   651   NS_ENSURE_TRUE_VOID(principal);
   653   // Construct the message in jsVal format.
   654   AutoSafeJSContext cx;
   655   AutoClearPendingException helper(cx);
   656   JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
   657   NS_ENSURE_TRUE_VOID(msgObj);
   658   JS::Rooted<JS::Value> jsVal(cx);
   660   // Set the "type" property of the message. This is a fake MIME type.
   661   {
   662     NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp");
   663     JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length());
   664     NS_ENSURE_TRUE_VOID(typeStr);
   665     jsVal.setString(typeStr);
   666     rv = JS_SetProperty(cx, msgObj, "type", jsVal);
   667     NS_ENSURE_TRUE_VOID(rv);
   668   }
   669   // Set the "url" and "title" properties of the message.
   670   // They are the same in the case of RTSP streaming.
   671   {
   672     nsAutoCString spec;
   673     aURI->GetSpec(spec);
   674     JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length());
   675     NS_ENSURE_TRUE_VOID(urlStr);
   676     jsVal.setString(urlStr);
   677     rv = JS_SetProperty(cx, msgObj, "url", jsVal);
   678     NS_ENSURE_TRUE_VOID(rv);
   679     rv = JS_SetProperty(cx, msgObj, "title", jsVal);
   680   }
   681   jsVal.setObject(*msgObj);
   683   // Send the message.
   684   nsCOMPtr<nsIMessageSender> cpmm =
   685     do_GetService("@mozilla.org/childprocessmessagemanager;1");
   686   NS_ENSURE_TRUE_VOID(cpmm);
   687   cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"),
   688                          jsVal, JS::NullHandleValue, principal, cx, 2);
   689 }
   690 #endif
   692 NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
   693                                                     nsIRequest *aRequest,
   694                                                     nsIInterfaceRequestor *aWindowContext,
   695                                                     bool aForceSave,
   696                                                     nsIStreamListener ** aStreamListener)
   697 {
   698   nsAutoString fileName;
   699   nsAutoCString fileExtension;
   700   uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
   701   uint32_t contentDisposition = -1;
   703   nsresult rv;
   705   // Get the file extension and name that we will need later
   706   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   707   nsCOMPtr<nsIURI> uri;
   708   int64_t contentLength = -1;
   709   if (channel) {
   710     channel->GetURI(getter_AddRefs(uri));
   711     channel->GetContentLength(&contentLength);
   712     channel->GetContentDisposition(&contentDisposition);
   713     channel->GetContentDispositionFilename(fileName);
   714   }
   716   if (XRE_GetProcessType() == GeckoProcessType_Content) {
   717     nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext);
   718     NS_ENSURE_STATE(window);
   720     // We need to get a hold of a ContentChild so that we can begin forwarding
   721     // this data to the parent.  In the HTTP case, this is unfortunate, since
   722     // we're actually passing data from parent->child->parent wastefully, but
   723     // the Right Fix will eventually be to short-circuit those channels on the
   724     // parent side based on some sort of subscription concept.
   725     using mozilla::dom::ContentChild;
   726     using mozilla::dom::ExternalHelperAppChild;
   727     ContentChild *child = ContentChild::GetSingleton();
   728     if (!child)
   729       return NS_ERROR_FAILURE;
   731     nsCString disp;
   732     if (channel) {
   733       channel->GetContentDispositionHeader(disp);
   734     }
   736     nsCOMPtr<nsIURI> referrer;
   737     rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
   739     OptionalURIParams uriParams, referrerParams;
   740     SerializeURI(uri, uriParams);
   741     SerializeURI(referrer, referrerParams);
   743     // Now we build a protocol for forwarding our data to the parent.  The
   744     // protocol will act as a listener on the child-side and create a "real"
   745     // helperAppService listener on the parent-side, via another call to
   746     // DoContent.
   747     mozilla::dom::PExternalHelperAppChild *pc =
   748       child->SendPExternalHelperAppConstructor(uriParams,
   749                                                nsCString(aMimeContentType),
   750                                                disp, contentDisposition,
   751                                                fileName, aForceSave, 
   752                                                contentLength, referrerParams,
   753                                                mozilla::dom::TabChild::GetFrom(window));
   754     ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
   756     NS_ADDREF(*aStreamListener = childListener);
   758     nsRefPtr<nsExternalAppHandler> handler =
   759       new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this,
   760                                fileName,
   761                                reason, aForceSave);
   762     if (!handler)
   763       return NS_ERROR_OUT_OF_MEMORY;
   765     childListener->SetHandler(handler);
   766     return NS_OK;
   767   }
   769   if (channel) {
   770     // Check if we have a POST request, in which case we don't want to use
   771     // the url's extension
   772     bool allowURLExt = true;
   773     nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
   774     if (httpChan) {
   775       nsAutoCString requestMethod;
   776       httpChan->GetRequestMethod(requestMethod);
   777       allowURLExt = !requestMethod.Equals("POST");
   778     }
   780     // Check if we had a query string - we don't want to check the URL
   781     // extension if a query is present in the URI
   782     // If we already know we don't want to check the URL extension, don't
   783     // bother checking the query
   784     if (uri && allowURLExt) {
   785       nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
   787       if (url) {
   788         nsAutoCString query;
   790         // We only care about the query for HTTP and HTTPS URLs
   791         bool isHTTP, isHTTPS;
   792         rv = uri->SchemeIs("http", &isHTTP);
   793         if (NS_FAILED(rv))
   794           isHTTP = false;
   795         rv = uri->SchemeIs("https", &isHTTPS);
   796         if (NS_FAILED(rv))
   797           isHTTPS = false;
   799         if (isHTTP || isHTTPS)
   800           url->GetQuery(query);
   802         // Only get the extension if the query is empty; if it isn't, then the
   803         // extension likely belongs to a cgi script and isn't helpful
   804         allowURLExt = query.IsEmpty();
   805       }
   806     }
   807     // Extract name & extension
   808     bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
   809                                                              fileExtension,
   810                                                              allowURLExt);
   811     LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
   812          fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
   813          isAttachment));
   814     if (isAttachment)
   815       reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
   816   }
   818   LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
   819        PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
   821   // we get the mime service here even though we're the default implementation of it,
   822   // so it's possible to override only the mime service and not need to reimplement the
   823   // whole external helper app service itself
   824   nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
   825   NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
   827   // Try to find a mime object by looking at the mime type/extension
   828   nsCOMPtr<nsIMIMEInfo> mimeInfo;
   829   if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
   830     nsAutoCString mimeType;
   831     if (!fileExtension.IsEmpty()) {
   832       mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
   833       if (mimeInfo) {
   834         mimeInfo->GetMIMEType(mimeType);
   836         LOG(("OS-Provided mime type '%s' for extension '%s'\n", 
   837              mimeType.get(), fileExtension.get()));
   838       }
   839     }
   841     if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
   842       // Extension lookup gave us no useful match
   843       mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
   844                                        getter_AddRefs(mimeInfo));
   845       mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
   846     }
   847     if (channel)
   848       channel->SetContentType(mimeType);
   849     // Don't overwrite SERVERREQUEST
   850     if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
   851       reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
   852   } 
   853   else {
   854     mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
   855                                      getter_AddRefs(mimeInfo));
   856   } 
   857   LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
   859   // No mimeinfo -> we can't continue. probably OOM.
   860   if (!mimeInfo)
   861     return NS_ERROR_OUT_OF_MEMORY;
   863   *aStreamListener = nullptr;
   864   // We want the mimeInfo's primary extension to pass it to
   865   // nsExternalAppHandler
   866   nsAutoCString buf;
   867   mimeInfo->GetPrimaryExtension(buf);
   869   nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
   870                                                             buf,
   871                                                             aWindowContext,
   872                                                             this,
   873                                                             fileName,
   874                                                             reason,
   875                                                             aForceSave);
   876   if (!handler)
   877     return NS_ERROR_OUT_OF_MEMORY;
   878   NS_ADDREF(*aStreamListener = handler);
   880   return NS_OK;
   881 }
   883 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
   884                                                                     const nsACString& aEncodingType,
   885                                                                     bool *aApplyDecoding)
   886 {
   887   *aApplyDecoding = true;
   888   uint32_t i;
   889   for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
   890     if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
   891         aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
   892       *aApplyDecoding = false;
   893       break;
   894     }
   895   }
   896   return NS_OK;
   897 }
   899 nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
   900                                                          nsIFile ** aFile)
   901 {
   902   nsDependentString platformAppPath(aPlatformAppPath);
   903   // First, check if we have an absolute path
   904   nsIFile* localFile = nullptr;
   905   nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
   906   if (NS_SUCCEEDED(rv)) {
   907     *aFile = localFile;
   908     bool exists;
   909     if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
   910       NS_RELEASE(*aFile);
   911       return NS_ERROR_FILE_NOT_FOUND;
   912     }
   913     return NS_OK;
   914   }
   917   // Second, check if file exists in mozilla program directory
   918   rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
   919   if (NS_SUCCEEDED(rv)) {
   920     rv = (*aFile)->Append(platformAppPath);
   921     if (NS_SUCCEEDED(rv)) {
   922       bool exists = false;
   923       rv = (*aFile)->Exists(&exists);
   924       if (NS_SUCCEEDED(rv) && exists)
   925         return NS_OK;
   926     }
   927     NS_RELEASE(*aFile);
   928   }
   931   return NS_ERROR_NOT_AVAILABLE;
   932 }
   934 //////////////////////////////////////////////////////////////////////////////////////////////////////
   935 // begin external protocol service default implementation...
   936 //////////////////////////////////////////////////////////////////////////////////////////////////////
   937 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
   938                                                                         bool * aHandlerExists)
   939 {
   940   nsCOMPtr<nsIHandlerInfo> handlerInfo;
   941   nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme), 
   942                                        getter_AddRefs(handlerInfo));
   943   NS_ENSURE_SUCCESS(rv, rv);
   945   // See if we have any known possible handler apps for this
   946   nsCOMPtr<nsIMutableArray> possibleHandlers;
   947   handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
   949   uint32_t length;
   950   possibleHandlers->GetLength(&length);
   951   if (length) {
   952     *aHandlerExists = true;
   953     return NS_OK;
   954   }
   956   // if not, fall back on an os-based handler
   957   return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
   958 }
   960 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
   961 {
   962   // check the per protocol setting first.  it always takes precedence.
   963   // if not set, then use the global setting.
   965   nsAutoCString prefName("network.protocol-handler.expose.");
   966   prefName += aProtocolScheme;
   967   bool val;
   968   if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
   969     *aResult = val;
   970     return NS_OK;
   971   }
   973   // by default, no protocol is exposed.  i.e., by default all link clicks must
   974   // go through the external protocol service.  most applications override this
   975   // default behavior.
   976   *aResult =
   977     Preferences::GetBool("network.protocol-handler.expose-all", false);
   979   return NS_OK;
   980 }
   982 NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
   983 {
   984   return LoadURI(aURL, nullptr);
   985 }
   987 static const char kExternalProtocolPrefPrefix[]  = "network.protocol-handler.external.";
   988 static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
   990 NS_IMETHODIMP 
   991 nsExternalHelperAppService::LoadURI(nsIURI *aURI,
   992                                     nsIInterfaceRequestor *aWindowContext)
   993 {
   994   NS_ENSURE_ARG_POINTER(aURI);
   996   if (XRE_GetProcessType() == GeckoProcessType_Content) {
   997     URIParams uri;
   998     SerializeURI(aURI, uri);
  1000     mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri);
  1001     return NS_OK;
  1004   nsAutoCString spec;
  1005   aURI->GetSpec(spec);
  1007   if (spec.Find("%00") != -1)
  1008     return NS_ERROR_MALFORMED_URI;
  1010   spec.ReplaceSubstring("\"", "%22");
  1011   spec.ReplaceSubstring("`", "%60");
  1013   nsCOMPtr<nsIIOService> ios(do_GetIOService());
  1014   nsCOMPtr<nsIURI> uri;
  1015   nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
  1016   NS_ENSURE_SUCCESS(rv, rv);
  1018   nsAutoCString scheme;
  1019   uri->GetScheme(scheme);
  1020   if (scheme.IsEmpty())
  1021     return NS_OK; // must have a scheme
  1023   // Deny load if the prefs say to do so
  1024   nsAutoCString externalPref(kExternalProtocolPrefPrefix);
  1025   externalPref += scheme;
  1026   bool allowLoad  = false;
  1027   if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
  1028     // no scheme-specific value, check the default
  1029     if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
  1030                                        &allowLoad))) {
  1031       return NS_OK; // missing default pref
  1035   if (!allowLoad) {
  1036     return NS_OK; // explicitly denied
  1039 #ifdef NECKO_PROTOCOL_rtsp
  1040   // Handle rtsp protocol.
  1042     bool isRTSP = false;
  1043     rv = aURI->SchemeIs("rtsp", &isRTSP);
  1044     if (NS_SUCCEEDED(rv) && isRTSP) {
  1045       LaunchVideoAppForRtsp(aURI);
  1046       return NS_OK;
  1049 #endif
  1051   nsCOMPtr<nsIHandlerInfo> handler;
  1052   rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
  1053   NS_ENSURE_SUCCESS(rv, rv);
  1055   nsHandlerInfoAction preferredAction;
  1056   handler->GetPreferredAction(&preferredAction);
  1057   bool alwaysAsk = true;
  1058   handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
  1060   // if we are not supposed to ask, and the preferred action is to use
  1061   // a helper app or the system default, we just launch the URI.
  1062   if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
  1063                      preferredAction == nsIHandlerInfo::useSystemDefault))
  1064     return handler->LaunchWithURI(uri, aWindowContext);
  1066   nsCOMPtr<nsIContentDispatchChooser> chooser =
  1067     do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
  1068   NS_ENSURE_SUCCESS(rv, rv);
  1070   return chooser->Ask(handler, aWindowContext, uri,
  1071                       nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
  1074 NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
  1076   // this method should only be implemented by each OS specific implementation of this service.
  1077   return NS_ERROR_NOT_IMPLEMENTED;
  1081 //////////////////////////////////////////////////////////////////////////////////////////////////////
  1082 // Methods related to deleting temporary files on exit
  1083 //////////////////////////////////////////////////////////////////////////////////////////////////////
  1085 /* static */
  1086 nsresult
  1087 nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
  1088                                                       nsCOMArray<nsIFile> &aFileList)
  1090   bool isFile = false;
  1092   // as a safety measure, make sure the nsIFile is really a file and not a directory object.
  1093   aTemporaryFile->IsFile(&isFile);
  1094   if (!isFile) return NS_OK;
  1096   aFileList.AppendObject(aTemporaryFile);
  1098   return NS_OK;
  1101 NS_IMETHODIMP
  1102 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
  1104   return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
  1107 NS_IMETHODIMP
  1108 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
  1110   return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
  1113 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
  1115   int32_t numEntries = fileList.Count();
  1116   nsIFile* localFile;
  1117   for (int32_t index = 0; index < numEntries; index++)
  1119     localFile = fileList[index];
  1120     if (localFile) {
  1121       // First make the file writable, since the temp file is probably readonly.
  1122       localFile->SetPermissions(0600);
  1123       localFile->Remove(false);
  1127   fileList.Clear();
  1130 void nsExternalHelperAppService::ExpungeTemporaryFiles()
  1132   ExpungeTemporaryFilesHelper(mTemporaryFilesList);
  1135 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
  1137   ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
  1140 static const char kExternalWarningPrefPrefix[] = 
  1141   "network.protocol-handler.warn-external.";
  1142 static const char kExternalWarningDefaultPref[] = 
  1143   "network.protocol-handler.warn-external-default";
  1145 NS_IMETHODIMP
  1146 nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
  1147                                                    nsIHandlerInfo **aHandlerInfo)
  1149   // XXX enterprise customers should be able to turn this support off with a
  1150   // single master pref (maybe use one of the "exposed" prefs here?)
  1152   bool exists;
  1153   nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
  1154   if (NS_FAILED(rv)) {
  1155     // Either it knows nothing, or we ran out of memory
  1156     return NS_ERROR_FAILURE;
  1159   nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1160   if (handlerSvc) {
  1161     bool hasHandler = false;
  1162     (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
  1163     if (hasHandler) {
  1164       rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
  1165       if (NS_SUCCEEDED(rv))
  1166         return NS_OK;
  1170   return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
  1173 NS_IMETHODIMP
  1174 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
  1175                                                          bool *found,
  1176                                                          nsIHandlerInfo **aHandlerInfo)
  1178   // intended to be implemented by the subclass
  1179   return NS_ERROR_NOT_IMPLEMENTED;
  1182 NS_IMETHODIMP
  1183 nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
  1184                                                        bool aOSHandlerExists)
  1186   // this type isn't in our database, so we've only got an OS default handler,
  1187   // if one exists
  1189   if (aOSHandlerExists) {
  1190     // we've got a default, so use it
  1191     aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
  1193     // whether or not to ask the user depends on the warning preference
  1194     nsAutoCString scheme;
  1195     aHandlerInfo->GetType(scheme);
  1197     nsAutoCString warningPref(kExternalWarningPrefPrefix);
  1198     warningPref += scheme;
  1199     bool warn;
  1200     if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
  1201       // no scheme-specific value, check the default
  1202       warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
  1204     aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
  1205   } else {
  1206     // If no OS default existed, we set the preferred action to alwaysAsk. 
  1207     // This really means not initialized (i.e. there's no available handler)
  1208     // to all the code...
  1209     aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
  1212   return NS_OK;
  1215 // XPCOM profile change observer
  1216 NS_IMETHODIMP
  1217 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
  1219   if (!strcmp(aTopic, "profile-before-change")) {
  1220     ExpungeTemporaryFiles();
  1221   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
  1222     ExpungeTemporaryPrivateFiles();
  1224   return NS_OK;
  1227 //////////////////////////////////////////////////////////////////////////////////////////////////////
  1228 // begin external app handler implementation 
  1229 //////////////////////////////////////////////////////////////////////////////////////////////////////
  1231 NS_IMPL_ADDREF(nsExternalAppHandler)
  1232 NS_IMPL_RELEASE(nsExternalAppHandler)
  1234 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
  1235    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
  1236    NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  1237    NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  1238    NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
  1239    NS_INTERFACE_MAP_ENTRY(nsICancelable)
  1240    NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
  1241    NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
  1242 NS_INTERFACE_MAP_END_THREADSAFE
  1244 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
  1245                                            const nsCSubstring& aTempFileExtension,
  1246                                            nsIInterfaceRequestor* aWindowContext,
  1247                                            nsExternalHelperAppService *aExtProtSvc,
  1248                                            const nsAString& aSuggestedFilename,
  1249                                            uint32_t aReason, bool aForceSave)
  1250 : mMimeInfo(aMIMEInfo)
  1251 , mWindowContext(aWindowContext)
  1252 , mWindowToClose(nullptr)
  1253 , mSuggestedFileName(aSuggestedFilename)
  1254 , mForceSave(aForceSave)
  1255 , mCanceled(false)
  1256 , mShouldCloseWindow(false)
  1257 , mStopRequestIssued(false)
  1258 , mReason(aReason)
  1259 , mContentLength(-1)
  1260 , mProgress(0)
  1261 , mSaver(nullptr)
  1262 , mDialogProgressListener(nullptr)
  1263 , mTransfer(nullptr)
  1264 , mRequest(nullptr)
  1265 , mExtProtSvc(aExtProtSvc)
  1268   // make sure the extention includes the '.'
  1269   if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
  1270     mTempFileExtension = char16_t('.');
  1271   AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
  1273   // replace platform specific path separator and illegal characters to avoid any confusion
  1274   mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1275   mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1277   // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
  1278   const char16_t unsafeBidiCharacters[] = {
  1279     char16_t(0x061c), // Arabic Letter Mark
  1280     char16_t(0x200e), // Left-to-Right Mark
  1281     char16_t(0x200f), // Right-to-Left Mark
  1282     char16_t(0x202a), // Left-to-Right Embedding
  1283     char16_t(0x202b), // Right-to-Left Embedding
  1284     char16_t(0x202c), // Pop Directional Formatting
  1285     char16_t(0x202d), // Left-to-Right Override
  1286     char16_t(0x202e), // Right-to-Left Override
  1287     char16_t(0x2066), // Left-to-Right Isolate
  1288     char16_t(0x2067), // Right-to-Left Isolate
  1289     char16_t(0x2068), // First Strong Isolate
  1290     char16_t(0x2069), // Pop Directional Isolate
  1291     char16_t(0)
  1292   };
  1293   mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
  1294   mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
  1296   // Make sure extension is correct.
  1297   EnsureSuggestedFileName();
  1299   mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
  1302 nsExternalAppHandler::~nsExternalAppHandler()
  1304   MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
  1307 void
  1308 nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
  1310   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process");
  1311   // Remove our request from the child loadGroup
  1312   RetargetLoadNotifications(request);
  1313   MaybeCloseWindow();
  1316 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
  1318   // This is always called by nsHelperDlg.js. Go ahead and register the
  1319   // progress listener. At this point, we don't have mTransfer.
  1320   mDialogProgressListener = aWebProgressListener;
  1321   return NS_OK;
  1324 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
  1326   if (mFinalFileDestination)
  1327     *aTarget = mFinalFileDestination;
  1328   else
  1329     *aTarget = mTempFile;
  1331   NS_IF_ADDREF(*aTarget);
  1332   return NS_OK;
  1335 NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
  1337   // Use the real target if it's been set
  1338   if (mFinalFileDestination)
  1339     return mFinalFileDestination->IsExecutable(aExec);
  1341   // Otherwise, use the stored executable-ness of the temporary
  1342   *aExec = mTempFileIsExecutable;
  1343   return NS_OK;
  1346 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
  1348   *aTime = mTimeDownloadStarted;
  1349   return NS_OK;
  1352 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
  1354   *aContentLength = mContentLength;
  1355   return NS_OK;
  1358 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
  1360   // we are going to run the downloading of the helper app in our own little docloader / load group context. 
  1361   // so go ahead and force the creation of a load group and doc loader for us to use...
  1362   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  1363   if (!aChannel)
  1364     return;
  1366   // we need to store off the original (pre redirect!) channel that initiated the load. We do
  1367   // this so later on, we can pass any refresh urls associated with the original channel back to the 
  1368   // window context which started the whole process. More comments about that are listed below....
  1369   // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. 
  1370   // ideally we should be able to just use mChannel (the channel we are extracting content from) or
  1371   // the default load channel associated with the original load group. Unfortunately because
  1372   // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel 
  1373   // which is what we really want....
  1375   // Note that we need to do this before removing aChannel from the loadgroup,
  1376   // since that would mess with the original channel on the loader.
  1377   nsCOMPtr<nsIDocumentLoader> origContextLoader =
  1378     do_GetInterface(mWindowContext);
  1379   if (origContextLoader)
  1380     origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
  1382   bool isPrivate = NS_UsePrivateBrowsing(aChannel);
  1384   nsCOMPtr<nsILoadGroup> oldLoadGroup;
  1385   aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
  1387   if(oldLoadGroup)
  1388      oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
  1390   aChannel->SetLoadGroup(nullptr);
  1391   aChannel->SetNotificationCallbacks(nullptr);
  1393   nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
  1394   if (pbChannel) {
  1395     pbChannel->SetPrivate(isPrivate);
  1399 /**
  1400  * Make mTempFileExtension contain an extension exactly when its previous value
  1401  * is different from mSuggestedFileName's extension, so that it can be appended
  1402  * to mSuggestedFileName and form a valid, useful leaf name.
  1403  * This is required so that the (renamed) temporary file has the correct extension
  1404  * after downloading to make sure the OS will launch the application corresponding
  1405  * to the MIME type (which was used to calculate mTempFileExtension).  This prevents
  1406  * a cgi-script named foobar.exe that returns application/zip from being named
  1407  * foobar.exe and executed as an executable file. It also blocks content that
  1408  * a web site might provide with a content-disposition header indicating
  1409  * filename="foobar.exe" from being downloaded to a file with extension .exe
  1410  * and executed.
  1411  */
  1412 void nsExternalAppHandler::EnsureSuggestedFileName()
  1414   // Make sure there is a mTempFileExtension (not "" or ".").
  1415   // Remember that mTempFileExtension will always have the leading "."
  1416   // (the check for empty is just to be safe).
  1417   if (mTempFileExtension.Length() > 1)
  1419     // Get mSuggestedFileName's current extension.
  1420     nsAutoString fileExt;
  1421     int32_t pos = mSuggestedFileName.RFindChar('.');
  1422     if (pos != kNotFound)
  1423       mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
  1425     // Now, compare fileExt to mTempFileExtension.
  1426     if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
  1428       // Matches -> mTempFileExtension can be empty
  1429       mTempFileExtension.Truncate();
  1434 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
  1436   // First we need to try to get the destination directory for the temporary
  1437   // file.
  1438   nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
  1439   NS_ENSURE_SUCCESS(rv, rv);
  1441   // At this point, we do not have a filename for the temp file.  For security
  1442   // purposes, this cannot be predictable, so we must use a cryptographic
  1443   // quality PRNG to generate one.
  1444   // We will request raw random bytes, and transform that to a base64 string,
  1445   // as all characters from the base64 set are acceptable for filenames.  For
  1446   // each three bytes of random data, we will get four bytes of ASCII.  Request
  1447   // a bit more, to be safe, and truncate to the length we want in the end.
  1449   const uint32_t wantedFileNameLength = 8;
  1450   const uint32_t requiredBytesLength =
  1451     static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
  1453   nsCOMPtr<nsIRandomGenerator> rg =
  1454     do_GetService("@mozilla.org/security/random-generator;1", &rv);
  1455   NS_ENSURE_SUCCESS(rv, rv);
  1457   uint8_t *buffer;
  1458   rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
  1459   NS_ENSURE_SUCCESS(rv, rv);
  1461   nsAutoCString tempLeafName;
  1462   nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
  1463   rv = Base64Encode(randomData, tempLeafName);
  1464   NS_Free(buffer);
  1465   buffer = nullptr;
  1466   NS_ENSURE_SUCCESS(rv, rv);
  1468   tempLeafName.Truncate(wantedFileNameLength);
  1470   // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
  1471   // to replace illegal characters -- notably '/'
  1472   tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1474   // now append our extension.
  1475   nsAutoCString ext;
  1476   mMimeInfo->GetPrimaryExtension(ext);
  1477   if (!ext.IsEmpty()) {
  1478     ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1479     if (ext.First() != '.')
  1480       tempLeafName.Append('.');
  1481     tempLeafName.Append(ext);
  1484   // We need to temporarily create a dummy file with the correct
  1485   // file extension to determine the executable-ness, so do this before adding
  1486   // the extra .part extension.
  1487   nsCOMPtr<nsIFile> dummyFile;
  1488   rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
  1489   NS_ENSURE_SUCCESS(rv, rv);
  1491   // Set the file name without .part
  1492   rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  1493   NS_ENSURE_SUCCESS(rv, rv);
  1494   rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
  1495   NS_ENSURE_SUCCESS(rv, rv);
  1497   // Store executable-ness then delete
  1498   dummyFile->IsExecutable(&mTempFileIsExecutable);
  1499   dummyFile->Remove(false);
  1501   // Add an additional .part to prevent the OS from running this file in the
  1502   // default application.
  1503   tempLeafName.Append(NS_LITERAL_CSTRING(".part"));
  1505   rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  1506   // make this file unique!!!
  1507   NS_ENSURE_SUCCESS(rv, rv);
  1508   rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
  1509   NS_ENSURE_SUCCESS(rv, rv);
  1511   // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
  1512   // This is a bit broken in the case when createUnique actually had to append
  1513   // some numbers, because then we now have a filename like foo.bar-1.part and
  1514   // we'll end up with foo.bar-1.bar as our final filename if we end up using
  1515   // this.  But the other options are all bad too....  Ideally we'd have a way
  1516   // to tell createUnique to put its unique marker before the extension that
  1517   // comes before ".part" or something.
  1518   rv = mTempFile->GetLeafName(mTempLeafName);
  1519   NS_ENSURE_SUCCESS(rv, rv);
  1521   NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
  1522                  NS_ERROR_UNEXPECTED);
  1524   // Strip off the ".part" from mTempLeafName
  1525   mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
  1527   MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
  1528   mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
  1529                              &rv);
  1530   NS_ENSURE_SUCCESS(rv, rv);
  1532   rv = mSaver->SetObserver(this);
  1533   if (NS_FAILED(rv)) {
  1534     mSaver = nullptr;
  1535     return rv;
  1538   rv = mSaver->EnableSha256();
  1539   NS_ENSURE_SUCCESS(rv, rv);
  1541   rv = mSaver->EnableSignatureInfo();
  1542   NS_ENSURE_SUCCESS(rv, rv);
  1543   LOG(("Enabled hashing and signature verification"));
  1545   rv = mSaver->SetTarget(mTempFile, false);
  1546   NS_ENSURE_SUCCESS(rv, rv);
  1548   return rv;
  1551 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
  1553   NS_PRECONDITION(request, "OnStartRequest without request?");
  1555   // Set mTimeDownloadStarted here as the download has already started and
  1556   // we want to record the start time before showing the filepicker.
  1557   mTimeDownloadStarted = PR_Now();
  1559   mRequest = request;
  1561   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  1563   nsresult rv;
  1565   nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
  1566   mIsFileChannel = fileChan != nullptr;
  1568   // Get content length
  1569   if (aChannel) {
  1570     aChannel->GetContentLength(&mContentLength);
  1573   nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
  1574   // Determine whether a new window was opened specifically for this request
  1575   if (props) {
  1576     bool tmp = false;
  1577     props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
  1578                              &tmp);
  1579     mShouldCloseWindow = tmp;
  1582   // Now get the URI
  1583   if (aChannel)
  1585     aChannel->GetURI(getter_AddRefs(mSourceUrl));
  1588   // retarget all load notifications to our docloader instead of the original window's docloader...
  1589   RetargetLoadNotifications(request);
  1591   // Check to see if there is a refresh header on the original channel.
  1592   if (mOriginalChannel) {
  1593     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
  1594     if (httpChannel) {
  1595       nsAutoCString refreshHeader;
  1596       httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
  1597                                      refreshHeader);
  1598       if (!refreshHeader.IsEmpty()) {
  1599         mShouldCloseWindow = false;
  1604   // Close the underlying DOMWindow if there is no refresh header
  1605   // and it was opened specifically for the download
  1606   MaybeCloseWindow();
  1608   // In an IPC setting, we're allowing the child process, here, to make
  1609   // decisions about decoding the channel (e.g. decompression).  It will
  1610   // still forward the decoded (uncompressed) data back to the parent.
  1611   // Con: Uncompressed data means more IPC overhead.
  1612   // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
  1613   //       Parent process doesn't need to expect CPU time on decompression.
  1614   nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
  1615   if (encChannel) 
  1617     // Turn off content encoding conversions if needed
  1618     bool applyConversion = true;
  1620     nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
  1621     if (sourceURL)
  1623       nsAutoCString extension;
  1624       sourceURL->GetFileExtension(extension);
  1625       if (!extension.IsEmpty())
  1627         nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
  1628         encChannel->GetContentEncodings(getter_AddRefs(encEnum));
  1629         if (encEnum)
  1631           bool hasMore;
  1632           rv = encEnum->HasMore(&hasMore);
  1633           if (NS_SUCCEEDED(rv) && hasMore)
  1635             nsAutoCString encType;
  1636             rv = encEnum->GetNext(encType);
  1637             if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
  1639               mExtProtSvc->ApplyDecodingForExtension(extension, encType,
  1640                                                      &applyConversion);
  1647     encChannel->SetApplyConversion( applyConversion );
  1650   // At this point, the child process has done everything it can usefully do
  1651   // for OnStartRequest.
  1652   if (XRE_GetProcessType() == GeckoProcessType_Content)
  1653      return NS_OK;
  1655   rv = SetUpTempFile(aChannel);
  1656   if (NS_FAILED(rv)) {
  1657     nsresult transferError = rv;
  1659     rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
  1660 #ifdef PR_LOGGING
  1661     if (NS_FAILED(rv)) {
  1662       LOG(("Failed to create transfer to report failure."
  1663            "Will fallback to prompter!"));
  1665 #endif
  1667     mCanceled = true;
  1668     request->Cancel(transferError);
  1670     nsAutoString path;
  1671     if (mTempFile)
  1672       mTempFile->GetPath(path);
  1674     SendStatusChange(kWriteError, transferError, request, path);
  1676     return NS_OK;
  1679   // Inform channel it is open on behalf of a download to prevent caching.
  1680   nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
  1681   if (httpInternal) {
  1682     httpInternal->SetChannelIsForDownload(true);
  1685   // now that the temp file is set up, find out if we need to invoke a dialog
  1686   // asking the user what they want us to do with this content...
  1688   // We can get here for three reasons: "can't handle", "sniffed type", or
  1689   // "server sent content-disposition:attachment".  In the first case we want
  1690   // to honor the user's "always ask" pref; in the other two cases we want to
  1691   // honor it only if the default action is "save".  Opening attachments in
  1692   // helper apps by default breaks some websites (especially if the attachment
  1693   // is one part of a multipart document).  Opening sniffed content in helper
  1694   // apps by default introduces security holes that we'd rather not have.
  1696   // So let's find out whether the user wants to be prompted.  If he does not,
  1697   // check mReason and the preferred action to see what we should do.
  1699   bool alwaysAsk = true;
  1700   mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
  1701   if (alwaysAsk)
  1703     // But we *don't* ask if this mimeInfo didn't come from
  1704     // our user configuration datastore and the user has said
  1705     // at some point in the distant past that they don't
  1706     // want to be asked.  The latter fact would have been
  1707     // stored in pref strings back in the old days.
  1709     bool mimeTypeIsInDatastore = false;
  1710     nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1711     if (handlerSvc)
  1712       handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
  1713     if (!handlerSvc || !mimeTypeIsInDatastore)
  1715       nsAutoCString MIMEType;
  1716       mMimeInfo->GetMIMEType(MIMEType);
  1718       if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
  1720         // Don't need to ask after all.
  1721         alwaysAsk = false;
  1722         // Make sure action matches pref (save to disk).
  1723         mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
  1725       else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get()))
  1727         // Don't need to ask after all.
  1728         alwaysAsk = false;
  1733   int32_t action = nsIMIMEInfo::saveToDisk;
  1734   mMimeInfo->GetPreferredAction( &action );
  1736   // OK, now check why we're here
  1737   if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
  1738     // Force asking if we're not saving.  See comment back when we fetched the
  1739     // alwaysAsk boolean for details.
  1740     alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
  1743   // if we were told that we _must_ save to disk without asking, all the stuff
  1744   // before this is irrelevant; override it
  1745   if (mForceSave) {
  1746     alwaysAsk = false;
  1747     action = nsIMIMEInfo::saveToDisk;
  1750   if (alwaysAsk)
  1752     // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
  1753     mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
  1754     NS_ENSURE_SUCCESS(rv, rv);
  1756     // this will create a reference cycle (the dialog holds a reference to us as
  1757     // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
  1758     rv = mDialog->Show( this, mWindowContext, mReason );
  1760     // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
  1762   else
  1765     // We need to do the save/open immediately, then.
  1766 #ifdef XP_WIN
  1767     /* We need to see whether the file we've got here could be
  1768      * executable.  If it could, we had better not try to open it!
  1769      * We can skip this check, though, if we have a setting to open in a
  1770      * helper app.
  1771      * This code mirrors the code in
  1772      * nsExternalAppHandler::LaunchWithApplication so that what we
  1773      * test here is as close as possible to what will really be
  1774      * happening if we decide to execute
  1775      */
  1776     nsCOMPtr<nsIHandlerApp> prefApp;
  1777     mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
  1778     if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
  1779       nsCOMPtr<nsIFile> fileToTest;
  1780       GetTargetFile(getter_AddRefs(fileToTest));
  1781       if (fileToTest) {
  1782         bool isExecutable;
  1783         rv = fileToTest->IsExecutable(&isExecutable);
  1784         if (NS_FAILED(rv) || isExecutable) {  // checking NS_FAILED, because paranoia is good
  1785           action = nsIMIMEInfo::saveToDisk;
  1787       } else {   // Paranoia is good here too, though this really should not happen
  1788         NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
  1789         action = nsIMIMEInfo::saveToDisk;
  1793 #endif
  1794     if (action == nsIMIMEInfo::useHelperApp ||
  1795         action == nsIMIMEInfo::useSystemDefault)
  1797         rv = LaunchWithApplication(nullptr, false);
  1799     else // Various unknown actions go here too
  1801         rv = SaveToDisk(nullptr, false);
  1805   return NS_OK;
  1808 // Convert error info into proper message text and send OnStatusChange
  1809 // notification to the dialog progress listener or nsITransfer implementation.
  1810 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
  1812     nsAutoString msgId;
  1813     switch(rv)
  1815     case NS_ERROR_OUT_OF_MEMORY:
  1816         // No memory
  1817         msgId.AssignLiteral("noMemory");
  1818         break;
  1820     case NS_ERROR_FILE_DISK_FULL:
  1821     case NS_ERROR_FILE_NO_DEVICE_SPACE:
  1822         // Out of space on target volume.
  1823         msgId.AssignLiteral("diskFull");
  1824         break;
  1826     case NS_ERROR_FILE_READ_ONLY:
  1827         // Attempt to write to read/only file.
  1828         msgId.AssignLiteral("readOnly");
  1829         break;
  1831     case NS_ERROR_FILE_ACCESS_DENIED:
  1832         if (type == kWriteError) {
  1833           // Attempt to write without sufficient permissions.
  1834 #if defined(ANDROID)
  1835           // On Android (and Gonk), this means the SD card is present but
  1836           // unavailable (read-only).
  1837           msgId.AssignLiteral("SDAccessErrorCardReadOnly");
  1838 #else
  1839           msgId.AssignLiteral("accessError");
  1840 #endif
  1842         else
  1844           msgId.AssignLiteral("launchError");
  1846         break;
  1848     case NS_ERROR_FILE_NOT_FOUND:
  1849     case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
  1850     case NS_ERROR_FILE_UNRECOGNIZED_PATH:
  1851         // Helper app not found, let's verify this happened on launch
  1852         if (type == kLaunchError) {
  1853           msgId.AssignLiteral("helperAppNotFound");
  1854           break;
  1856 #if defined(ANDROID)
  1857         else if (type == kWriteError) {
  1858           // On Android (and Gonk), this means the SD card is missing (not in
  1859           // SD slot).
  1860           msgId.AssignLiteral("SDAccessErrorCardMissing");
  1861           break;
  1863 #endif
  1864         // fall through
  1866     default:
  1867         // Generic read/write/launch error message.
  1868         switch(type)
  1870         case kReadError:
  1871           msgId.AssignLiteral("readError");
  1872           break;
  1873         case kWriteError:
  1874           msgId.AssignLiteral("writeError");
  1875           break;
  1876         case kLaunchError:
  1877           msgId.AssignLiteral("launchError");
  1878           break;
  1880         break;
  1882     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1883         ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
  1884          NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
  1885     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1886         ("       path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
  1888     // Get properties file bundle and extract status string.
  1889     nsCOMPtr<nsIStringBundleService> stringService =
  1890         mozilla::services::GetStringBundleService();
  1891     if (stringService)
  1893         nsCOMPtr<nsIStringBundle> bundle;
  1894         if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
  1896             nsXPIDLString msgText;
  1897             const char16_t *strings[] = { path.get() };
  1898             if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
  1900               if (mDialogProgressListener)
  1902                 // We have a listener, let it handle the error.
  1903                 mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
  1904               } else if (mTransfer) {
  1905                 mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
  1907               else
  1908               if (XRE_GetProcessType() == GeckoProcessType_Default) {
  1909                 // We don't have a listener.  Simply show the alert ourselves.
  1910                 nsresult qiRv;
  1911                 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext, &qiRv));
  1912                 nsXPIDLString title;
  1913                 bundle->FormatStringFromName(MOZ_UTF16("title"),
  1914                                              strings,
  1915                                              1,
  1916                                              getter_Copies(title));
  1918                 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
  1919                        ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
  1920                        mWindowContext.get(),
  1921                        prompter.get(),
  1922                        qiRv,
  1923                        NS_ConvertUTF16toUTF8(title).get(),
  1924                        NS_ConvertUTF16toUTF8(msgText).get()));
  1926                 // If we didn't have a prompter we will try and get a window
  1927                 // instead, get it's docshell and use it to alert the user.
  1928                 if (!prompter)
  1930                   nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(mWindowContext));
  1931                   if (!window || !window->GetDocShell())
  1933                     return;
  1936                   prompter = do_GetInterface(window->GetDocShell(), &qiRv);
  1938                   PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
  1939                          ("No prompter from mWindowContext, using DocShell, " \
  1940                           "window=0x%p, docShell=0x%p, " \
  1941                           "prompter=0x%p, qi rv=0x%08X",
  1942                           window.get(),
  1943                           window->GetDocShell(),
  1944                           prompter.get(),
  1945                           qiRv));
  1947                   // If we still don't have a prompter, there's nothing else we
  1948                   // can do so just return.
  1949                   if (!prompter)
  1951                     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1952                            ("No prompter from DocShell, no way to alert user"));
  1953                     return;
  1957                 // We should always have a prompter at this point.
  1958                 prompter->Alert(title, msgText);
  1965 NS_IMETHODIMP
  1966 nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
  1967                                       nsIInputStream * inStr,
  1968                                       uint64_t sourceOffset, uint32_t count)
  1970   nsresult rv = NS_OK;
  1971   // first, check to see if we've been canceled....
  1972   if (mCanceled || !mSaver) // then go cancel our underlying channel too
  1973     return request->Cancel(NS_BINDING_ABORTED);
  1975   // read the data out of the stream and write it to the temp file.
  1976   if (count > 0)
  1978     mProgress += count;
  1980     nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
  1981     rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
  1982     if (NS_SUCCEEDED(rv))
  1984       // Send progress notification.
  1985       if (mTransfer) {
  1986         mTransfer->OnProgressChange64(nullptr, request, mProgress,
  1987                                       mContentLength, mProgress,
  1988                                       mContentLength);
  1991     else
  1993       // An error occurred, notify listener.
  1994       nsAutoString tempFilePath;
  1995       if (mTempFile)
  1996         mTempFile->GetPath(tempFilePath);
  1997       SendStatusChange(kReadError, rv, request, tempFilePath);
  1999       // Cancel the download.
  2000       Cancel(rv);
  2003   return rv;
  2006 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
  2007                                                   nsresult aStatus)
  2009   LOG(("nsExternalAppHandler::OnStopRequest\n"
  2010        "  mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
  2011        mCanceled, mTransfer.get(), aStatus));
  2013   mStopRequestIssued = true;
  2015   // Cancel if the request did not complete successfully.
  2016   if (!mCanceled && NS_FAILED(aStatus))
  2018     // Send error notification.
  2019     nsAutoString tempFilePath;
  2020     if (mTempFile)
  2021       mTempFile->GetPath(tempFilePath);
  2022     SendStatusChange( kReadError, aStatus, request, tempFilePath );
  2024     Cancel(aStatus);
  2027   // first, check to see if we've been canceled....
  2028   if (mCanceled || !mSaver)
  2029     return NS_OK;
  2031   return mSaver->Finish(NS_OK);
  2034 NS_IMETHODIMP
  2035 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
  2036                                      nsIFile *aTarget)
  2038   return NS_OK;
  2041 NS_IMETHODIMP
  2042 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
  2043                                      nsresult aStatus)
  2045   LOG(("nsExternalAppHandler::OnSaveComplete\n"
  2046        "  aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
  2047        aSaver, aStatus, mCanceled, mTransfer.get()));
  2049   if (!mCanceled) {
  2050     // Save the hash
  2051     (void)mSaver->GetSha256Hash(mHash);
  2052     (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
  2053     // Free the reference that the saver keeps on us, even if we couldn't get
  2054     // the hash.
  2055     mSaver = nullptr;
  2057     if (NS_FAILED(aStatus)) {
  2058       nsAutoString path;
  2059       mTempFile->GetPath(path);
  2061       // It may happen when e10s is enabled that there will be no transfer
  2062       // object available to communicate status as expected by the system.
  2063       // Let's try and create a temporary transfer object to take care of this
  2064       // for us, we'll fall back to using the prompt service if we absolutely
  2065       // have to.
  2066       if (!mTransfer) {
  2067         nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  2068         // We don't care if this fails.
  2069         CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
  2072       SendStatusChange(kWriteError, aStatus, nullptr, path);
  2073       if (!mCanceled)
  2074         Cancel(aStatus);
  2075       return NS_OK;
  2079   // Notify the transfer object that we are done if the user has chosen an
  2080   // action. If the user hasn't chosen an action, the progress listener
  2081   // (nsITransfer) will be notified in CreateTransfer.
  2082   if (mTransfer) {
  2083     NotifyTransfer(aStatus);
  2086   return NS_OK;
  2089 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
  2091   MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
  2092   MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
  2094   LOG(("Notifying progress listener"));
  2096   if (NS_SUCCEEDED(aStatus)) {
  2097     (void)mTransfer->SetSha256Hash(mHash);
  2098     (void)mTransfer->SetSignatureInfo(mSignatureInfo);
  2099     (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
  2100       mContentLength, mProgress, mContentLength);
  2103   (void)mTransfer->OnStateChange(nullptr, nullptr,
  2104     nsIWebProgressListener::STATE_STOP |
  2105     nsIWebProgressListener::STATE_IS_REQUEST |
  2106     nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
  2108   // This nsITransfer object holds a reference to us (we are its observer), so
  2109   // we need to release the reference to break a reference cycle (and therefore
  2110   // to prevent leaking).  We do this even if the previous calls failed.
  2111   mTransfer = nullptr;
  2114 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
  2116   *aMIMEInfo = mMimeInfo;
  2117   NS_ADDREF(*aMIMEInfo);
  2118   return NS_OK;
  2121 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
  2123   NS_ENSURE_ARG(aSourceURI);
  2124   *aSourceURI = mSourceUrl;
  2125   NS_IF_ADDREF(*aSourceURI);
  2126   return NS_OK;
  2129 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
  2131   aSuggestedFileName = mSuggestedFileName;
  2132   return NS_OK;
  2135 nsresult nsExternalAppHandler::CreateTransfer()
  2137   LOG(("nsExternalAppHandler::CreateTransfer"));
  2139   MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
  2140   // We are back from the helper app dialog (where the user chooses to save or
  2141   // open), but we aren't done processing the load. in this case, throw up a
  2142   // progress dialog so the user can see what's going on.
  2143   // Also, release our reference to mDialog. We don't need it anymore, and we
  2144   // need to break the reference cycle.
  2145   mDialog = nullptr;
  2146   if (!mDialogProgressListener) {
  2147     NS_WARNING("The dialog should nullify the dialog progress listener");
  2149   nsresult rv;
  2151   // We must be able to create an nsITransfer object. If not, it doesn't matter
  2152   // much that we can't launch the helper application or save to disk. Work on
  2153   // a local copy rather than mTransfer until we know we succeeded, to make it
  2154   // clearer that this function is re-entrant.
  2155   nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
  2156     NS_TRANSFER_CONTRACTID, &rv);
  2157   NS_ENSURE_SUCCESS(rv, rv);
  2159   // Initialize the download
  2160   nsCOMPtr<nsIURI> target;
  2161   rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
  2162   NS_ENSURE_SUCCESS(rv, rv);
  2164   nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  2166   rv = transfer->Init(mSourceUrl, target, EmptyString(),
  2167                        mMimeInfo, mTimeDownloadStarted, mTempFile, this,
  2168                        channel && NS_UsePrivateBrowsing(channel));
  2169   NS_ENSURE_SUCCESS(rv, rv);
  2171   // Now let's add the download to history
  2172   nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
  2173   if (dh) {
  2174     nsCOMPtr<nsIURI> referrer;
  2175     nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  2176     if (channel) {
  2177       NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
  2180     if (channel && !NS_UsePrivateBrowsing(channel)) {
  2181       dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
  2185   // If we were cancelled since creating the transfer, just return. It is
  2186   // always ok to return NS_OK if we are cancelled. Callers of this function
  2187   // must call Cancel if CreateTransfer fails, but there's no need to cancel
  2188   // twice.
  2189   if (mCanceled) {
  2190     return NS_OK;
  2192   rv = transfer->OnStateChange(nullptr, mRequest,
  2193     nsIWebProgressListener::STATE_START |
  2194     nsIWebProgressListener::STATE_IS_REQUEST |
  2195     nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
  2196   NS_ENSURE_SUCCESS(rv, rv);
  2198   if (mCanceled) {
  2199     return NS_OK;
  2202   mRequest = nullptr;
  2203   // Finally, save the transfer to mTransfer.
  2204   mTransfer = transfer;
  2205   transfer = nullptr;
  2207   // While we were bringing up the progress dialog, we actually finished
  2208   // processing the url. If that's the case then mStopRequestIssued will be
  2209   // true and OnSaveComplete has been called.
  2210   if (mStopRequestIssued && !mSaver && mTransfer) {
  2211     NotifyTransfer(NS_OK);
  2214   return rv;
  2217 nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
  2219   nsresult rv;
  2220   nsCOMPtr<nsITransfer> transfer =
  2221     do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
  2222   NS_ENSURE_SUCCESS(rv, rv);
  2224   // If we don't have a download directory we're kinda screwed but it's OK
  2225   // we'll still report the error via the prompter.
  2226   nsCOMPtr<nsIFile> pseudoFile;
  2227   rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
  2228   NS_ENSURE_SUCCESS(rv, rv);
  2230   // Append the default suggested filename. If the user restarts the transfer
  2231   // we will re-trigger a filename check anyway to ensure that it is unique.
  2232   rv = pseudoFile->Append(mSuggestedFileName);
  2233   NS_ENSURE_SUCCESS(rv, rv);
  2235   nsCOMPtr<nsIURI> pseudoTarget;
  2236   rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
  2237   NS_ENSURE_SUCCESS(rv, rv);
  2239   rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
  2240                       mMimeInfo, mTimeDownloadStarted, nullptr, this,
  2241                       aIsPrivateBrowsing);
  2242   NS_ENSURE_SUCCESS(rv, rv);
  2244   // Our failed transfer is ready.
  2245   mTransfer = transfer.forget();
  2247   return NS_OK;
  2250 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
  2252   if (aFile)
  2253     ContinueSave(aFile);
  2254   else
  2255     Cancel(NS_BINDING_ABORTED);
  2257   return NS_OK;
  2260 void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
  2262   // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
  2263   // Convert to use file picker? No, then embeddors could not do any sort of
  2264   // "AutoDownload" w/o showing a prompt
  2265   nsresult rv = NS_OK;
  2266   if (!mDialog)
  2268     // Get helper app launcher dialog.
  2269     mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
  2270     if (rv != NS_OK) {
  2271       Cancel(NS_BINDING_ABORTED);
  2272       return;
  2276   // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
  2277   // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
  2279   // Now, be sure to keep |this| alive, and the dialog
  2280   // If we don't do this, users that close the helper app dialog while the file
  2281   // picker is up would cause Cancel() to be called, and the dialog would be
  2282   // released, which would release this object too, which would crash.
  2283   // See Bug 249143
  2284   nsIFile* fileToUse;
  2285   nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
  2286   nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
  2287   rv = mDialog->PromptForSaveToFile(this,
  2288                                     mWindowContext,
  2289                                     aDefaultFile.get(),
  2290                                     aFileExtension.get(),
  2291                                     mForceSave, &fileToUse);
  2293   if (rv == NS_ERROR_NOT_AVAILABLE) {
  2294     // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
  2295     rv = mDialog->PromptForSaveToFileAsync(this, 
  2296                                            mWindowContext,
  2297                                            aDefaultFile.get(),
  2298                                            aFileExtension.get(),
  2299                                            mForceSave);
  2300   } else {
  2301     SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
  2305 // SaveToDisk should only be called by the helper app dialog which allows
  2306 // the user to say launch with application or save to disk. It doesn't actually
  2307 // perform the save, it just prompts for the destination file name.
  2308 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
  2310   if (mCanceled)
  2311     return NS_OK;
  2313   mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
  2315   if (!aNewFileLocation) {
  2316     if (mSuggestedFileName.IsEmpty())
  2317       RequestSaveDestination(mTempLeafName, mTempFileExtension);
  2318     else
  2320       nsAutoString fileExt;
  2321       int32_t pos = mSuggestedFileName.RFindChar('.');
  2322       if (pos >= 0)
  2323         mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
  2324       if (fileExt.IsEmpty())
  2325         fileExt = mTempFileExtension;
  2327       RequestSaveDestination(mSuggestedFileName, fileExt);
  2329   } else {
  2330     ContinueSave(aNewFileLocation);
  2333   return NS_OK;
  2335 nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
  2337   if (mCanceled)
  2338     return NS_OK;
  2340   NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
  2342   nsresult rv = NS_OK;
  2343   nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
  2344   mFinalFileDestination = do_QueryInterface(fileToUse);
  2346   // Move what we have in the final directory, but append .part
  2347   // to it, to indicate that it's unfinished.  Do not call SetTarget on the
  2348   // saver if we are done (Finish has been called) but OnSaverComplete has not
  2349   // been called.
  2350   if (mFinalFileDestination && mSaver && !mStopRequestIssued)
  2352     nsCOMPtr<nsIFile> movedFile;
  2353     mFinalFileDestination->Clone(getter_AddRefs(movedFile));
  2354     if (movedFile) {
  2355       // Get the old leaf name and append .part to it
  2356       nsAutoString name;
  2357       mFinalFileDestination->GetLeafName(name);
  2358       name.AppendLiteral(".part");
  2359       movedFile->SetLeafName(name);
  2361       rv = mSaver->SetTarget(movedFile, true);
  2362       if (NS_FAILED(rv)) {
  2363         nsAutoString path;
  2364         mTempFile->GetPath(path);
  2365         SendStatusChange(kWriteError, rv, nullptr, path);
  2366         Cancel(rv);
  2367         return NS_OK;
  2370       mTempFile = movedFile;
  2374   // The helper app dialog has told us what to do and we have a final file
  2375   // destination.
  2376   rv = CreateTransfer();
  2377   // If we fail to create the transfer, Cancel.
  2378   if (NS_FAILED(rv)) {
  2379     Cancel(rv);
  2380     return rv;
  2383   // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
  2384   // if there is one. We don't want to do this before the save as dialog goes away because this dialog
  2385   // is modal and we do bad things if you try to load a web page in the underlying window while a modal
  2386   // dialog is still up.
  2387   ProcessAnyRefreshTags();
  2389   return NS_OK;
  2393 // LaunchWithApplication should only be called by the helper app dialog which
  2394 // allows the user to say launch with application or save to disk. It doesn't
  2395 // actually perform launch with application.
  2396 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
  2398   if (mCanceled)
  2399     return NS_OK;
  2401   // user has chosen to launch using an application, fire any refresh tags now...
  2402   ProcessAnyRefreshTags(); 
  2404   if (mMimeInfo && aApplication) {
  2405     PlatformLocalHandlerApp_t *handlerApp =
  2406       new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
  2407     mMimeInfo->SetPreferredApplicationHandler(handlerApp);
  2410   // Now check if the file is local, in which case we won't bother with saving
  2411   // it to a temporary directory and just launch it from where it is
  2412   nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
  2413   if (fileUrl && mIsFileChannel)
  2415     Cancel(NS_BINDING_ABORTED);
  2416     nsCOMPtr<nsIFile> file;
  2417     nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
  2419     if (NS_SUCCEEDED(rv))
  2421       rv = mMimeInfo->LaunchWithFile(file);
  2422       if (NS_SUCCEEDED(rv))
  2423         return NS_OK;
  2425     nsAutoString path;
  2426     if (file)
  2427       file->GetPath(path);
  2428     // If we get here, an error happened
  2429     SendStatusChange(kLaunchError, rv, nullptr, path);
  2430     return rv;
  2433   // Now that the user has elected to launch the downloaded file with a helper
  2434   // app, we're justified in removing the 'salted' name.  We'll rename to what
  2435   // was specified in mSuggestedFileName after the download is done prior to
  2436   // launching the helper app.  So that any existing file of that name won't be
  2437   // overwritten we call CreateUnique().  Also note that we use the same
  2438   // directory as originally downloaded so nsDownload can rename in place
  2439   // later.
  2440   nsCOMPtr<nsIFile> fileToUse;
  2441   (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
  2443   if (mSuggestedFileName.IsEmpty())
  2445     // Keep using the leafname of the temp file, since we're just starting a helper
  2446     mSuggestedFileName = mTempLeafName;
  2449 #ifdef XP_WIN
  2450   fileToUse->Append(mSuggestedFileName + mTempFileExtension);
  2451 #else
  2452   fileToUse->Append(mSuggestedFileName);  
  2453 #endif
  2455   nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
  2456   if(NS_SUCCEEDED(rv))
  2458     mFinalFileDestination = do_QueryInterface(fileToUse);
  2459     // launch the progress window now that the user has picked the desired action.
  2460     rv = CreateTransfer();
  2461     if (NS_FAILED(rv)) {
  2462       Cancel(rv);
  2465   else
  2467     // Cancel the download and report an error.  We do not want to end up in
  2468     // a state where it appears that we have a normal download that is
  2469     // pointing to a file that we did not actually create.
  2470     nsAutoString path;
  2471     mTempFile->GetPath(path);
  2472     SendStatusChange(kWriteError, rv, nullptr, path);
  2473     Cancel(rv);
  2475   return rv;
  2478 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
  2480   NS_ENSURE_ARG(NS_FAILED(aReason));
  2482   if (mCanceled) {
  2483     return NS_OK;
  2485   mCanceled = true;
  2487   if (mSaver) {
  2488     // We are still writing to the target file.  Give the saver a chance to
  2489     // close the target file, then notify the transfer object if necessary in
  2490     // the OnSaveComplete callback.
  2491     mSaver->Finish(aReason);
  2492     mSaver = nullptr;
  2493   } else {
  2494     if (mStopRequestIssued && mTempFile) {
  2495       // This branch can only happen when the user cancels the helper app dialog
  2496       // when the request has completed. The temp file has to be removed here,
  2497       // because mSaver has been released at that time with the temp file left.
  2498       (void)mTempFile->Remove(false);
  2501     // Notify the transfer object that the download has been canceled, if the
  2502     // user has already chosen an action and we didn't notify already.
  2503     if (mTransfer) {
  2504       NotifyTransfer(aReason);
  2508   // Break our reference cycle with the helper app dialog (set up in
  2509   // OnStartRequest)
  2510   mDialog = nullptr;
  2512   mRequest = nullptr;
  2514   // Release the listener, to break the reference cycle with it (we are the
  2515   // observer of the listener).
  2516   mDialogProgressListener = nullptr;
  2518   return NS_OK;
  2521 void nsExternalAppHandler::ProcessAnyRefreshTags()
  2523    // one last thing, try to see if the original window context supports a refresh interface...
  2524    // Sometimes, when you download content that requires an external handler, there is
  2525    // a refresh header associated with the download. This refresh header points to a page
  2526    // the content provider wants the user to see after they download the content. How do we
  2527    // pass this refresh information back to the caller? For now, try to get the refresh URI
  2528    // interface. If the window context where the request originated came from supports this
  2529    // then we can force it to process the refresh information (if there is any) from this channel.
  2530    if (mWindowContext && mOriginalChannel)
  2532      nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext));
  2533      if (refreshHandler) {
  2534         refreshHandler->SetupRefreshURI(mOriginalChannel);
  2536      mOriginalChannel = nullptr;
  2540 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
  2542   // Search the obsolete pref strings.
  2543   nsAdoptingCString prefCString = Preferences::GetCString(prefName);
  2544   if (prefCString.IsEmpty()) {
  2545     // Default is true, if not found in the pref string.
  2546     return true;
  2549   NS_UnescapeURL(prefCString);
  2550   nsACString::const_iterator start, end;
  2551   prefCString.BeginReading(start);
  2552   prefCString.EndReading(end);
  2553   return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
  2554                                         start, end);
  2557 nsresult nsExternalAppHandler::MaybeCloseWindow()
  2559   nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWindowContext);
  2560   NS_ENSURE_STATE(window);
  2562   if (mShouldCloseWindow) {
  2563     // Reset the window context to the opener window so that the dependent
  2564     // dialogs have a parent
  2565     nsCOMPtr<nsIDOMWindow> opener;
  2566     window->GetOpener(getter_AddRefs(opener));
  2568     bool isClosed;
  2569     if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) {
  2570       mWindowContext = do_GetInterface(opener);
  2572       // Now close the old window.  Do it on a timer so that we don't run
  2573       // into issues trying to close the window before it has fully opened.
  2574       NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
  2575       mTimer = do_CreateInstance("@mozilla.org/timer;1");
  2576       if (!mTimer) {
  2577         return NS_ERROR_FAILURE;
  2580       mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
  2581       mWindowToClose = window;
  2585   return NS_OK;
  2588 NS_IMETHODIMP
  2589 nsExternalAppHandler::Notify(nsITimer* timer)
  2591   NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
  2593   mWindowToClose->Close();
  2594   mWindowToClose = nullptr;
  2595   mTimer = nullptr;
  2597   return NS_OK;
  2599 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2600 // The following section contains our nsIMIMEService implementation and related methods.
  2601 //
  2602 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2604 // nsIMIMEService methods
  2605 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval) 
  2607   NS_PRECONDITION(!aMIMEType.IsEmpty() ||
  2608                   !aFileExt.IsEmpty(), 
  2609                   "Give me something to work with");
  2610   LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
  2611         PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
  2613   *_retval = nullptr;
  2615   // OK... we need a type. Get one.
  2616   nsAutoCString typeToUse(aMIMEType);
  2617   if (typeToUse.IsEmpty()) {
  2618     nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
  2619     if (NS_FAILED(rv))
  2620       return NS_ERROR_NOT_AVAILABLE;
  2623   // We promise to only send lower case mime types to the OS
  2624   ToLowerCase(typeToUse);
  2626   // (1) Ask the OS for a mime info
  2627   bool found;
  2628   *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
  2629   LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
  2630   // If we got no mimeinfo, something went wrong. Probably lack of memory.
  2631   if (!*_retval)
  2632     return NS_ERROR_OUT_OF_MEMORY;
  2634   // (2) Now, let's see if we can find something in our datastore
  2635   // This will not overwrite the OS information that interests us
  2636   // (i.e. default application, default app. description)
  2637   nsresult rv;
  2638   nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  2639   if (handlerSvc) {
  2640     bool hasHandler = false;
  2641     (void) handlerSvc->Exists(*_retval, &hasHandler);
  2642     if (hasHandler) {
  2643       rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
  2644       LOG(("Data source: Via type: retval 0x%08x\n", rv));
  2645     } else {
  2646       rv = NS_ERROR_NOT_AVAILABLE;
  2649     found = found || NS_SUCCEEDED(rv);
  2651     if (!found || NS_FAILED(rv)) {
  2652       // No type match, try extension match
  2653       if (!aFileExt.IsEmpty()) {
  2654         nsAutoCString overrideType;
  2655         rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
  2656         if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
  2657           // We can't check handlerSvc->Exists() here, because we have a
  2658           // overideType. That's ok, it just results in some console noise.
  2659           // (If there's no handler for the override type, it throws)
  2660           rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
  2661           LOG(("Data source: Via ext: retval 0x%08x\n", rv));
  2662           found = found || NS_SUCCEEDED(rv);
  2668   // (3) No match yet. Ask extras.
  2669   if (!found) {
  2670     rv = NS_ERROR_FAILURE;
  2671 #ifdef XP_WIN
  2672     /* XXX Gross hack to wallpaper over the most common Win32
  2673      * extension issues caused by the fix for bug 116938.  See bug
  2674      * 120327, comment 271 for why this is needed.  Not even sure we
  2675      * want to remove this once we have fixed all this stuff to work
  2676      * right; any info we get from extras on this type is pretty much
  2677      * useless....
  2678      */
  2679     if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
  2680 #endif
  2681       rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
  2682     LOG(("Searched extras (by type), rv 0x%08X\n", rv));
  2683     // If that didn't work out, try file extension from extras
  2684     if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
  2685       rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
  2686       LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
  2688     // If that still didn't work, set the file description to "ext File"
  2689     if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
  2690       // XXXzpao This should probably be localized
  2691       nsAutoCString desc(aFileExt);
  2692       desc.Append(" File");
  2693       (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
  2694       LOG(("Falling back to 'File' file description\n"));
  2698   // Finally, check if we got a file extension and if yes, if it is an
  2699   // extension on the mimeinfo, in which case we want it to be the primary one
  2700   if (!aFileExt.IsEmpty()) {
  2701     bool matches = false;
  2702     (*_retval)->ExtensionExists(aFileExt, &matches);
  2703     LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
  2704     if (matches)
  2705       (*_retval)->SetPrimaryExtension(aFileExt);
  2708 #ifdef PR_LOGGING
  2709   if (LOG_ENABLED()) {
  2710     nsAutoCString type;
  2711     (*_retval)->GetMIMEType(type);
  2713     nsAutoCString ext;
  2714     (*_retval)->GetPrimaryExtension(ext);
  2715     LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
  2717 #endif
  2719   return NS_OK;
  2722 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType) 
  2724   // OK. We want to try the following sources of mimetype information, in this order:
  2725   // 1. defaultMimeEntries array
  2726   // 2. User-set preferences (managed by the handler service)
  2727   // 3. OS-provided information
  2728   // 4. our "extras" array
  2729   // 5. Information from plugins
  2730   // 6. The "ext-to-type-mapping" category
  2732   // Early return if called with an empty extension parameter
  2733   if (aFileExt.IsEmpty())
  2734     return NS_ERROR_NOT_AVAILABLE;
  2736   nsresult rv = NS_OK;
  2737   // First of all, check our default entries
  2738   for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++)
  2740     if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) {
  2741       aContentType = defaultMimeEntries[i].mMimeType;
  2742       return rv;
  2746   // Check user-set prefs
  2747   nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  2748   if (handlerSvc)
  2749     rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType);
  2750   if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty())
  2751     return NS_OK;
  2753   // Ask OS.
  2754   bool found = false;
  2755   nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
  2756   if (mi && found)
  2757     return mi->GetMIMEType(aContentType);
  2759   // Check extras array.
  2760   found = GetTypeFromExtras(aFileExt, aContentType);
  2761   if (found)
  2762     return NS_OK;
  2764   const nsCString& flatExt = PromiseFlatCString(aFileExt);
  2765   // Try the plugins
  2766   const char* mimeType;
  2767   nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv));
  2768   nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get());
  2769   if (NS_SUCCEEDED(rv)) {
  2770     if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) {
  2771       aContentType = mimeType;
  2772       return NS_OK;
  2776   rv = NS_OK;
  2777   // Let's see if an extension added something
  2778   nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
  2779   if (catMan) {
  2780     // The extension in the category entry is always stored as lowercase
  2781     nsAutoCString lowercaseFileExt(aFileExt);
  2782     ToLowerCase(lowercaseFileExt);
  2783     // Read the MIME type from the category entry, if available
  2784     nsXPIDLCString type;
  2785     rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(),
  2786                                   getter_Copies(type));
  2787     aContentType = type;
  2789   else {
  2790     rv = NS_ERROR_NOT_AVAILABLE;
  2793   return rv;
  2796 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
  2798   NS_ENSURE_ARG(!aMIMEType.IsEmpty());
  2800   nsCOMPtr<nsIMIMEInfo> mi;
  2801   nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
  2802   if (NS_FAILED(rv))
  2803     return rv;
  2805   return mi->GetPrimaryExtension(_retval);
  2808 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType) 
  2810   NS_ENSURE_ARG_POINTER(aURI);
  2811   nsresult rv = NS_ERROR_NOT_AVAILABLE;
  2812   aContentType.Truncate();
  2814   // First look for a file to use.  If we have one, we just use that.
  2815   nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
  2816   if (fileUrl) {
  2817     nsCOMPtr<nsIFile> file;
  2818     rv = fileUrl->GetFile(getter_AddRefs(file));
  2819     if (NS_SUCCEEDED(rv)) {
  2820       rv = GetTypeFromFile(file, aContentType);
  2821       if (NS_SUCCEEDED(rv)) {
  2822         // we got something!
  2823         return rv;
  2828   // Now try to get an nsIURL so we don't have to do our own parsing
  2829   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
  2830   if (url) {
  2831     nsAutoCString ext;
  2832     rv = url->GetFileExtension(ext);
  2833     if (NS_FAILED(rv))
  2834       return rv;
  2835     if (ext.IsEmpty())
  2836       return NS_ERROR_NOT_AVAILABLE;
  2838     UnescapeFragment(ext, url, ext);
  2840     return GetTypeFromExtension(ext, aContentType);
  2843   // no url, let's give the raw spec a shot
  2844   nsAutoCString specStr;
  2845   rv = aURI->GetSpec(specStr);
  2846   if (NS_FAILED(rv))
  2847     return rv;
  2848   UnescapeFragment(specStr, aURI, specStr);
  2850   // find the file extension (if any)
  2851   int32_t extLoc = specStr.RFindChar('.');
  2852   int32_t specLength = specStr.Length();
  2853   if (-1 != extLoc &&
  2854       extLoc != specLength - 1 &&
  2855       // nothing over 20 chars long can be sanely considered an
  2856       // extension.... Dat dere would be just data.
  2857       specLength - extLoc < 20) 
  2859     return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
  2862   // We found no information; say so.
  2863   return NS_ERROR_NOT_AVAILABLE;
  2866 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
  2868   NS_ENSURE_ARG_POINTER(aFile);
  2869   nsresult rv;
  2870   nsCOMPtr<nsIMIMEInfo> info;
  2872   // Get the Extension
  2873   nsAutoString fileName;
  2874   rv = aFile->GetLeafName(fileName);
  2875   if (NS_FAILED(rv)) return rv;
  2877   nsAutoCString fileExt;
  2878   if (!fileName.IsEmpty())
  2880     int32_t len = fileName.Length(); 
  2881     for (int32_t i = len; i >= 0; i--) 
  2883       if (fileName[i] == char16_t('.'))
  2885         CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
  2886         break;
  2891   if (fileExt.IsEmpty())
  2892     return NS_ERROR_FAILURE;
  2894   return GetTypeFromExtension(fileExt, aContentType);
  2897 nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
  2898   const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
  2900   NS_ENSURE_ARG( aMIMEInfo );
  2902   NS_ENSURE_ARG( !aContentType.IsEmpty() );
  2904   // Look for default entry with matching mime type.
  2905   nsAutoCString MIMEType(aContentType);
  2906   ToLowerCase(MIMEType);
  2907   int32_t numEntries = ArrayLength(extraMimeEntries);
  2908   for (int32_t index = 0; index < numEntries; index++)
  2910       if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
  2912           // This is the one. Set attributes appropriately.
  2913           aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
  2914           aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
  2915           return NS_OK;
  2919   return NS_ERROR_NOT_AVAILABLE;
  2922 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
  2923   const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
  2925   nsAutoCString type;
  2926   bool found = GetTypeFromExtras(aExtension, type);
  2927   if (!found)
  2928     return NS_ERROR_NOT_AVAILABLE;
  2929   return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
  2932 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
  2934   NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
  2936   // Look for default entry with matching extension.
  2937   nsDependentCString::const_iterator start, end, iter;
  2938   int32_t numEntries = ArrayLength(extraMimeEntries);
  2939   for (int32_t index = 0; index < numEntries; index++)
  2941       nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
  2942       extList.BeginReading(start);
  2943       extList.EndReading(end);
  2944       iter = start;
  2945       while (start != end)
  2947           FindCharInReadable(',', iter, end);
  2948           if (Substring(start, iter).Equals(aExtension,
  2949                                             nsCaseInsensitiveCStringComparator()))
  2951               aMIMEType = extraMimeEntries[index].mMimeType;
  2952               return true;
  2954           if (iter != end) {
  2955             ++iter;
  2957           start = iter;
  2961   return false;

mercurial