uriloader/exthandler/nsExternalHelperAppService.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2962 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * vim:expandtab:shiftwidth=2:tabstop=2:cin:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#ifdef MOZ_LOGGING
    1.11 +#define FORCE_PR_LOG
    1.12 +#endif
    1.13 +
    1.14 +#include "base/basictypes.h"
    1.15 +
    1.16 +/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
    1.17 +#include "mozilla/ArrayUtils.h"
    1.18 +#include "mozilla/Base64.h"
    1.19 +
    1.20 +#include "mozilla/dom/ContentChild.h"
    1.21 +#include "mozilla/dom/TabChild.h"
    1.22 +#include "nsXULAppAPI.h"
    1.23 +
    1.24 +#include "nsExternalHelperAppService.h"
    1.25 +#include "nsCExternalHandlerService.h"
    1.26 +#include "nsIURI.h"
    1.27 +#include "nsIURL.h"
    1.28 +#include "nsIFile.h"
    1.29 +#include "nsIFileURL.h"
    1.30 +#include "nsIChannel.h"
    1.31 +#include "nsIDirectoryService.h"
    1.32 +#include "nsAppDirectoryServiceDefs.h"
    1.33 +#include "nsICategoryManager.h"
    1.34 +#include "nsDependentSubstring.h"
    1.35 +#include "nsXPIDLString.h"
    1.36 +#include "nsUnicharUtils.h"
    1.37 +#include "nsIStringEnumerator.h"
    1.38 +#include "nsMemory.h"
    1.39 +#include "nsIStreamListener.h"
    1.40 +#include "nsIMIMEService.h"
    1.41 +#include "nsILoadGroup.h"
    1.42 +#include "nsIWebProgressListener.h"
    1.43 +#include "nsITransfer.h"
    1.44 +#include "nsReadableUtils.h"
    1.45 +#include "nsIRequest.h"
    1.46 +#include "nsDirectoryServiceDefs.h"
    1.47 +#include "nsIInterfaceRequestor.h"
    1.48 +#include "nsThreadUtils.h"
    1.49 +#include "nsAutoPtr.h"
    1.50 +#include "nsIMutableArray.h"
    1.51 +
    1.52 +// used to access our datastore of user-configured helper applications
    1.53 +#include "nsIHandlerService.h"
    1.54 +#include "nsIMIMEInfo.h"
    1.55 +#include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
    1.56 +#include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
    1.57 +#include "nsIHelperAppLauncherDialog.h"
    1.58 +#include "nsIContentDispatchChooser.h"
    1.59 +#include "nsNetUtil.h"
    1.60 +#include "nsIIOService.h"
    1.61 +#include "nsNetCID.h"
    1.62 +#include "nsChannelProperties.h"
    1.63 +
    1.64 +#include "nsMimeTypes.h"
    1.65 +// used for header disposition information.
    1.66 +#include "nsIHttpChannel.h"
    1.67 +#include "nsIHttpChannelInternal.h"
    1.68 +#include "nsIEncodedChannel.h"
    1.69 +#include "nsIMultiPartChannel.h"
    1.70 +#include "nsIFileChannel.h"
    1.71 +#include "nsIObserverService.h" // so we can be a profile change observer
    1.72 +#include "nsIPropertyBag2.h" // for the 64-bit content length
    1.73 +
    1.74 +#ifdef XP_MACOSX
    1.75 +#include "nsILocalFileMac.h"
    1.76 +#endif
    1.77 +
    1.78 +#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
    1.79 +#include "nsPluginHost.h"
    1.80 +#include "nsEscape.h"
    1.81 +
    1.82 +#include "nsIStringBundle.h" // XXX needed to localize error msgs
    1.83 +#include "nsIPrompt.h"
    1.84 +
    1.85 +#include "nsITextToSubURI.h" // to unescape the filename
    1.86 +#include "nsIMIMEHeaderParam.h"
    1.87 +
    1.88 +#include "nsIWindowWatcher.h"
    1.89 +
    1.90 +#include "nsIDownloadHistory.h" // to mark downloads as visited
    1.91 +#include "nsDocShellCID.h"
    1.92 +
    1.93 +#include "nsCRT.h"
    1.94 +#include "nsLocalHandlerApp.h"
    1.95 +
    1.96 +#include "nsIRandomGenerator.h"
    1.97 +
    1.98 +#include "ContentChild.h"
    1.99 +#include "nsXULAppAPI.h"
   1.100 +#include "nsPIDOMWindow.h"
   1.101 +#include "nsIDocShellTreeOwner.h"
   1.102 +#include "nsIDocShellTreeItem.h"
   1.103 +#include "ExternalHelperAppChild.h"
   1.104 +
   1.105 +#ifdef XP_WIN
   1.106 +#include "nsWindowsHelpers.h"
   1.107 +#endif
   1.108 +
   1.109 +#ifdef MOZ_WIDGET_ANDROID
   1.110 +#include "AndroidBridge.h"
   1.111 +#endif
   1.112 +
   1.113 +#include "mozilla/Preferences.h"
   1.114 +#include "mozilla/ipc/URIUtils.h"
   1.115 +
   1.116 +#ifdef MOZ_WIDGET_GONK
   1.117 +#include "nsDeviceStorage.h"
   1.118 +#endif
   1.119 +
   1.120 +#ifdef NECKO_PROTOCOL_rtsp
   1.121 +#include "nsIScriptSecurityManager.h"
   1.122 +#include "nsIMessageManager.h"
   1.123 +#endif
   1.124 +
   1.125 +using namespace mozilla;
   1.126 +using namespace mozilla::ipc;
   1.127 +
   1.128 +// Download Folder location constants
   1.129 +#define NS_PREF_DOWNLOAD_DIR        "browser.download.dir"
   1.130 +#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
   1.131 +enum {
   1.132 +  NS_FOLDER_VALUE_DESKTOP = 0
   1.133 +, NS_FOLDER_VALUE_DOWNLOADS = 1
   1.134 +, NS_FOLDER_VALUE_CUSTOM = 2
   1.135 +};
   1.136 +
   1.137 +#ifdef PR_LOGGING
   1.138 +PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr;
   1.139 +#endif
   1.140 +
   1.141 +// Using level 3 here because the OSHelperAppServices use a log level
   1.142 +// of PR_LOG_DEBUG (4), and we want less detailed output here
   1.143 +// Using 3 instead of PR_LOG_WARN because we don't output warnings
   1.144 +#undef LOG
   1.145 +#define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
   1.146 +#define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
   1.147 +
   1.148 +static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
   1.149 +  "browser.helperApps.neverAsk.saveToDisk";
   1.150 +static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
   1.151 +  "browser.helperApps.neverAsk.openFile";
   1.152 +
   1.153 +// Helper functions for Content-Disposition headers
   1.154 +
   1.155 +/**
   1.156 + * Given a URI fragment, unescape it
   1.157 + * @param aFragment The string to unescape
   1.158 + * @param aURI The URI from which this fragment is taken. Only its character set
   1.159 + *             will be used.
   1.160 + * @param aResult [out] Unescaped string.
   1.161 + */
   1.162 +static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
   1.163 +                                 nsAString& aResult)
   1.164 +{
   1.165 +  // First, we need a charset
   1.166 +  nsAutoCString originCharset;
   1.167 +  nsresult rv = aURI->GetOriginCharset(originCharset);
   1.168 +  NS_ENSURE_SUCCESS(rv, rv);
   1.169 +
   1.170 +  // Now, we need the unescaper
   1.171 +  nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
   1.172 +  NS_ENSURE_SUCCESS(rv, rv);
   1.173 +
   1.174 +  return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
   1.175 +}
   1.176 +
   1.177 +/**
   1.178 + * UTF-8 version of UnescapeFragment.
   1.179 + * @param aFragment The string to unescape
   1.180 + * @param aURI The URI from which this fragment is taken. Only its character set
   1.181 + *             will be used.
   1.182 + * @param aResult [out] Unescaped string, UTF-8 encoded.
   1.183 + * @note It is safe to pass the same string for aFragment and aResult.
   1.184 + * @note When this function fails, aResult will not be modified.
   1.185 + */
   1.186 +static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
   1.187 +                                 nsACString& aResult)
   1.188 +{
   1.189 +  nsAutoString result;
   1.190 +  nsresult rv = UnescapeFragment(aFragment, aURI, result);
   1.191 +  if (NS_SUCCEEDED(rv))
   1.192 +    CopyUTF16toUTF8(result, aResult);
   1.193 +  return rv;
   1.194 +}
   1.195 +
   1.196 +/**
   1.197 + * Given a channel, returns the filename and extension the channel has.
   1.198 + * This uses the URL and other sources (nsIMultiPartChannel).
   1.199 + * Also gives back whether the channel requested external handling (i.e.
   1.200 + * whether Content-Disposition: attachment was sent)
   1.201 + * @param aChannel The channel to extract the filename/extension from
   1.202 + * @param aFileName [out] Reference to the string where the filename should be
   1.203 + *        stored. Empty if it could not be retrieved.
   1.204 + *        WARNING - this filename may contain characters which the OS does not
   1.205 + *        allow as part of filenames!
   1.206 + * @param aExtension [out] Reference to the string where the extension should
   1.207 + *        be stored. Empty if it could not be retrieved. Stored in UTF-8.
   1.208 + * @param aAllowURLExtension (optional) Get the extension from the URL if no
   1.209 + *        Content-Disposition header is present. Default is true.
   1.210 + * @retval true The server sent Content-Disposition:attachment or equivalent
   1.211 + * @retval false Content-Disposition: inline or no content-disposition header
   1.212 + *         was sent.
   1.213 + */
   1.214 +static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
   1.215 +                                                 nsString& aFileName,
   1.216 +                                                 nsCString& aExtension,
   1.217 +                                                 bool aAllowURLExtension = true)
   1.218 +{
   1.219 +  aExtension.Truncate();
   1.220 +  /*
   1.221 +   * If the channel is an http or part of a multipart channel and we
   1.222 +   * have a content disposition header set, then use the file name
   1.223 +   * suggested there as the preferred file name to SUGGEST to the
   1.224 +   * user.  we shouldn't actually use that without their
   1.225 +   * permission... otherwise just use our temp file
   1.226 +   */
   1.227 +  bool handleExternally = false;
   1.228 +  uint32_t disp;
   1.229 +  nsresult rv = aChannel->GetContentDisposition(&disp);
   1.230 +  if (NS_SUCCEEDED(rv))
   1.231 +  {
   1.232 +    aChannel->GetContentDispositionFilename(aFileName);
   1.233 +    if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
   1.234 +      handleExternally = true;
   1.235 +  }
   1.236 +
   1.237 +  // If the disposition header didn't work, try the filename from nsIURL
   1.238 +  nsCOMPtr<nsIURI> uri;
   1.239 +  aChannel->GetURI(getter_AddRefs(uri));
   1.240 +  nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
   1.241 +  if (url && aFileName.IsEmpty())
   1.242 +  {
   1.243 +    if (aAllowURLExtension) {
   1.244 +      url->GetFileExtension(aExtension);
   1.245 +      UnescapeFragment(aExtension, url, aExtension);
   1.246 +
   1.247 +      // Windows ignores terminating dots. So we have to as well, so
   1.248 +      // that our security checks do "the right thing"
   1.249 +      // In case the aExtension consisted only of the dot, the code below will
   1.250 +      // extract an aExtension from the filename
   1.251 +      aExtension.Trim(".", false);
   1.252 +    }
   1.253 +
   1.254 +    // try to extract the file name from the url and use that as a first pass as the
   1.255 +    // leaf name of our temp file...
   1.256 +    nsAutoCString leafName;
   1.257 +    url->GetFileName(leafName);
   1.258 +    if (!leafName.IsEmpty())
   1.259 +    {
   1.260 +      rv = UnescapeFragment(leafName, url, aFileName);
   1.261 +      if (NS_FAILED(rv))
   1.262 +      {
   1.263 +        CopyUTF8toUTF16(leafName, aFileName); // use escaped name
   1.264 +      }
   1.265 +    }
   1.266 +  }
   1.267 +
   1.268 +  // Extract Extension, if we have a filename; otherwise,
   1.269 +  // truncate the string
   1.270 +  if (aExtension.IsEmpty()) {
   1.271 +    if (!aFileName.IsEmpty())
   1.272 +    {
   1.273 +      // Windows ignores terminating dots. So we have to as well, so
   1.274 +      // that our security checks do "the right thing"
   1.275 +      aFileName.Trim(".", false);
   1.276 +
   1.277 +      // XXX RFindCharInReadable!!
   1.278 +      nsAutoString fileNameStr(aFileName);
   1.279 +      int32_t idx = fileNameStr.RFindChar(char16_t('.'));
   1.280 +      if (idx != kNotFound)
   1.281 +        CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
   1.282 +    }
   1.283 +  }
   1.284 +
   1.285 +
   1.286 +  return handleExternally;
   1.287 +}
   1.288 +
   1.289 +/**
   1.290 + * Obtains the directory to use.  This tends to vary per platform, and
   1.291 + * needs to be consistent throughout our codepaths. For platforms where
   1.292 + * helper apps use the downloads directory, this should be kept in
   1.293 + * sync with nsDownloadManager.cpp
   1.294 + *
   1.295 + * Optionally skip availability of the directory and storage.
   1.296 + */
   1.297 +static nsresult GetDownloadDirectory(nsIFile **_directory,
   1.298 +                                     bool aSkipChecks = false)
   1.299 +{
   1.300 +  nsCOMPtr<nsIFile> dir;
   1.301 +#ifdef XP_MACOSX
   1.302 +  // On OS X, we first try to get the users download location, if it's set.
   1.303 +  switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
   1.304 +    case NS_FOLDER_VALUE_DESKTOP:
   1.305 +      (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
   1.306 +      break;
   1.307 +    case NS_FOLDER_VALUE_CUSTOM:
   1.308 +      {
   1.309 +        Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
   1.310 +                                NS_GET_IID(nsIFile),
   1.311 +                                getter_AddRefs(dir));
   1.312 +        if (!dir) break;
   1.313 +
   1.314 +        // If we're not checking for availability we're done.
   1.315 +        if (aSkipChecks) {
   1.316 +          dir.forget(_directory);
   1.317 +          return NS_OK;
   1.318 +        }
   1.319 +
   1.320 +        // We have the directory, and now we need to make sure it exists
   1.321 +        bool dirExists = false;
   1.322 +        (void) dir->Exists(&dirExists);
   1.323 +        if (dirExists) break;
   1.324 +
   1.325 +        nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
   1.326 +        if (NS_FAILED(rv)) {
   1.327 +          dir = nullptr;
   1.328 +          break;
   1.329 +        }
   1.330 +      }
   1.331 +      break;
   1.332 +    case NS_FOLDER_VALUE_DOWNLOADS:
   1.333 +      // This is just the OS default location, so fall out
   1.334 +      break;
   1.335 +  }
   1.336 +
   1.337 +  if (!dir) {
   1.338 +    // If not, we default to the OS X default download location.
   1.339 +    nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
   1.340 +                                         getter_AddRefs(dir));
   1.341 +    NS_ENSURE_SUCCESS(rv, rv);
   1.342 +  }
   1.343 +#elif defined(MOZ_WIDGET_GONK)
   1.344 +  // On Gonk, store the files on the sdcard in the downloads directory.
   1.345 +  // We need to check with the volume manager which storage point is
   1.346 +  // available.
   1.347 +
   1.348 +  // Pick the default storage in case multiple (internal and external) ones
   1.349 +  // are available.
   1.350 +  nsString storageName;
   1.351 +  nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
   1.352 +                                            storageName);
   1.353 +
   1.354 +  DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
   1.355 +                        storageName,
   1.356 +                        NS_LITERAL_STRING("downloads"));
   1.357 +  NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED);
   1.358 +
   1.359 +  // If we're not checking for availability we're done.
   1.360 +  if (aSkipChecks) {
   1.361 +    dsf.mFile.forget(_directory);
   1.362 +    return NS_OK;
   1.363 +  }
   1.364 +
   1.365 +  // Check device storage status before continuing.
   1.366 +  nsString storageStatus;
   1.367 +  dsf.GetStatus(storageStatus);
   1.368 +
   1.369 +  // If we get an "unavailable" status, it means the sd card is not present.
   1.370 +  // We'll also catch internal errors by looking for an empty string and assume
   1.371 +  // the SD card isn't present when this occurs.
   1.372 +  if (storageStatus.EqualsLiteral("unavailable") ||
   1.373 +      storageStatus.IsEmpty()) {
   1.374 +    return NS_ERROR_FILE_NOT_FOUND;
   1.375 +  }
   1.376 +
   1.377 +  // If we get a status other than 'available' here it means the card is busy
   1.378 +  // because it's mounted via USB or it is being formatted.
   1.379 +  if (!storageStatus.EqualsLiteral("available")) {
   1.380 +    return NS_ERROR_FILE_ACCESS_DENIED;
   1.381 +  }
   1.382 +
   1.383 +  bool alreadyThere;
   1.384 +  nsresult rv = dsf.mFile->Exists(&alreadyThere);
   1.385 +  NS_ENSURE_SUCCESS(rv, rv);
   1.386 +  if (!alreadyThere) {
   1.387 +    rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
   1.388 +    NS_ENSURE_SUCCESS(rv, rv);
   1.389 +  }
   1.390 +  dir = dsf.mFile;
   1.391 +#elif defined(ANDROID)
   1.392 +  // On mobile devices, we are avoiding exposing users to the file
   1.393 +  // system, and don't save downloads to temp directories
   1.394 +
   1.395 +  // On Android we only return something if we have and SD-card
   1.396 +  char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
   1.397 +  nsresult rv;
   1.398 +  if (downloadDir) {
   1.399 +    nsCOMPtr<nsIFile> ldir;
   1.400 +    rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir),
   1.401 +                               true, getter_AddRefs(ldir));
   1.402 +    NS_ENSURE_SUCCESS(rv, rv);
   1.403 +    dir = do_QueryInterface(ldir);
   1.404 +
   1.405 +    // If we're not checking for availability we're done.
   1.406 +    if (aSkipChecks) {
   1.407 +      dir.forget(_directory);
   1.408 +      return NS_OK;
   1.409 +    }
   1.410 +  }
   1.411 +  else {
   1.412 +    return NS_ERROR_FAILURE;
   1.413 +  }
   1.414 +#elif defined(XP_WIN)
   1.415 +  // On metro we want to be able to search opened files and the temp directory
   1.416 +  // is exlcuded in searches.
   1.417 +  nsresult rv;
   1.418 +  if (IsRunningInWindowsMetro()) {
   1.419 +    rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir));
   1.420 +  } else {
   1.421 +    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
   1.422 +  }
   1.423 +  NS_ENSURE_SUCCESS(rv, rv);
   1.424 +#else
   1.425 +  // On all other platforms, we default to the systems temporary directory.
   1.426 +  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
   1.427 +  NS_ENSURE_SUCCESS(rv, rv);
   1.428 +#endif
   1.429 +
   1.430 +  NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
   1.431 +  dir.forget(_directory);
   1.432 +  return NS_OK;
   1.433 +}
   1.434 +
   1.435 +/**
   1.436 + * Structure for storing extension->type mappings.
   1.437 + * @see defaultMimeEntries
   1.438 + */
   1.439 +struct nsDefaultMimeTypeEntry {
   1.440 +  const char* mMimeType;
   1.441 +  const char* mFileExtension;
   1.442 +};
   1.443 +
   1.444 +/**
   1.445 + * Default extension->mimetype mappings. These are not overridable.
   1.446 + * If you add types here, make sure they are lowercase, or you'll regret it.
   1.447 + */
   1.448 +static nsDefaultMimeTypeEntry defaultMimeEntries [] = 
   1.449 +{
   1.450 +  // The following are those extensions that we're asked about during startup,
   1.451 +  // sorted by order used
   1.452 +  { IMAGE_GIF, "gif" },
   1.453 +  { TEXT_XML, "xml" },
   1.454 +  { APPLICATION_RDF, "rdf" },
   1.455 +  { TEXT_XUL, "xul" },
   1.456 +  { IMAGE_PNG, "png" },
   1.457 +  // -- end extensions used during startup
   1.458 +  { TEXT_CSS, "css" },
   1.459 +  { IMAGE_JPEG, "jpeg" },
   1.460 +  { IMAGE_JPEG, "jpg" },
   1.461 +  { IMAGE_SVG_XML, "svg" },
   1.462 +  { TEXT_HTML, "html" },
   1.463 +  { TEXT_HTML, "htm" },
   1.464 +  { APPLICATION_XPINSTALL, "xpi" },
   1.465 +  { "application/xhtml+xml", "xhtml" },
   1.466 +  { "application/xhtml+xml", "xht" },
   1.467 +  { TEXT_PLAIN, "txt" },
   1.468 +  { VIDEO_OGG, "ogv" },
   1.469 +  { VIDEO_OGG, "ogg" },
   1.470 +  { APPLICATION_OGG, "ogg" },
   1.471 +  { AUDIO_OGG, "oga" },
   1.472 +#ifdef MOZ_OPUS
   1.473 +  { AUDIO_OGG, "opus" },
   1.474 +#endif
   1.475 +#ifdef MOZ_WEBM
   1.476 +  { VIDEO_WEBM, "webm" },
   1.477 +  { AUDIO_WEBM, "webm" },
   1.478 +#endif
   1.479 +#if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
   1.480 +  { VIDEO_MP4, "mp4" },
   1.481 +  { AUDIO_MP4, "m4a" },
   1.482 +  { AUDIO_MP3, "mp3" },
   1.483 +#endif
   1.484 +#ifdef MOZ_RAW
   1.485 +  { VIDEO_RAW, "yuv" }
   1.486 +#endif
   1.487 +};
   1.488 +
   1.489 +/**
   1.490 + * This is a small private struct used to help us initialize some
   1.491 + * default mime types.
   1.492 + */
   1.493 +struct nsExtraMimeTypeEntry {
   1.494 +  const char* mMimeType; 
   1.495 +  const char* mFileExtensions;
   1.496 +  const char* mDescription;
   1.497 +};
   1.498 +
   1.499 +#ifdef XP_MACOSX
   1.500 +#define MAC_TYPE(x) x
   1.501 +#else
   1.502 +#define MAC_TYPE(x) 0
   1.503 +#endif
   1.504 +
   1.505 +/**
   1.506 + * This table lists all of the 'extra' content types that we can deduce from particular
   1.507 + * file extensions.  These entries also ensure that we provide a good descriptive name
   1.508 + * when we encounter files with these content types and/or extensions.  These can be
   1.509 + * overridden by user helper app prefs.
   1.510 + * If you add types here, make sure they are lowercase, or you'll regret it.
   1.511 + */
   1.512 +static nsExtraMimeTypeEntry extraMimeEntries [] =
   1.513 +{
   1.514 +#if defined(VMS)
   1.515 +  { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" },
   1.516 +#elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
   1.517 +  { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
   1.518 +#else
   1.519 +  { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
   1.520 +#endif
   1.521 +  { APPLICATION_GZIP2, "gz", "gzip" },
   1.522 +  { "application/x-arj", "arj", "ARJ file" },
   1.523 +  { "application/rtf", "rtf", "Rich Text Format File" },
   1.524 +  { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
   1.525 +  { APPLICATION_PDF, "pdf", "Portable Document Format" },
   1.526 +  { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
   1.527 +  { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
   1.528 +  { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
   1.529 +#ifdef MOZ_WIDGET_ANDROID
   1.530 +  { "application/vnd.android.package-archive", "apk", "Android Package" },
   1.531 +#endif
   1.532 +  { IMAGE_ART, "art", "ART Image" },
   1.533 +  { IMAGE_BMP, "bmp", "BMP Image" },
   1.534 +  { IMAGE_GIF, "gif", "GIF Image" },
   1.535 +  { IMAGE_ICO, "ico,cur", "ICO Image" },
   1.536 +  { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
   1.537 +  { IMAGE_PNG, "png", "PNG Image" },
   1.538 +  { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
   1.539 +  { IMAGE_XBM, "xbm", "XBM Image" },
   1.540 +  { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
   1.541 +  { MESSAGE_RFC822, "eml", "RFC-822 data" },
   1.542 +  { TEXT_PLAIN, "txt,text", "Text File" },
   1.543 +  { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
   1.544 +  { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
   1.545 +  { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
   1.546 +  { APPLICATION_RDF, "rdf", "Resource Description Framework" },
   1.547 +  { TEXT_XUL, "xul", "XML-Based User Interface Language" },
   1.548 +  { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
   1.549 +  { TEXT_CSS, "css", "Style Sheet" },
   1.550 +  { TEXT_VCARD, "vcf,vcard", "Contact Information" },
   1.551 +  { VIDEO_OGG, "ogv", "Ogg Video" },
   1.552 +  { VIDEO_OGG, "ogg", "Ogg Video" },
   1.553 +  { APPLICATION_OGG, "ogg", "Ogg Video"},
   1.554 +  { AUDIO_OGG, "oga", "Ogg Audio" },
   1.555 +  { AUDIO_OGG, "opus", "Opus Audio" },
   1.556 +#ifdef MOZ_WIDGET_GONK
   1.557 +  { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
   1.558 +#endif
   1.559 +  { VIDEO_WEBM, "webm", "Web Media Video" },
   1.560 +  { AUDIO_WEBM, "webm", "Web Media Audio" },
   1.561 +  { AUDIO_MP3, "mp3", "MPEG Audio" },
   1.562 +  { VIDEO_MP4, "mp4", "MPEG-4 Video" },
   1.563 +  { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
   1.564 +  { VIDEO_RAW, "yuv", "Raw YUV Video" },
   1.565 +  { AUDIO_WAV, "wav", "Waveform Audio" },
   1.566 +  { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
   1.567 +  { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
   1.568 +};
   1.569 +
   1.570 +#undef MAC_TYPE
   1.571 +
   1.572 +/**
   1.573 + * File extensions for which decoding should be disabled.
   1.574 + * NOTE: These MUST be lower-case and ASCII.
   1.575 + */
   1.576 +static nsDefaultMimeTypeEntry nonDecodableExtensions [] = {
   1.577 +  { APPLICATION_GZIP, "gz" }, 
   1.578 +  { APPLICATION_GZIP, "tgz" },
   1.579 +  { APPLICATION_ZIP, "zip" },
   1.580 +  { APPLICATION_COMPRESS, "z" },
   1.581 +  { APPLICATION_GZIP, "svgz" }
   1.582 +};
   1.583 +
   1.584 +NS_IMPL_ISUPPORTS(
   1.585 +  nsExternalHelperAppService,
   1.586 +  nsIExternalHelperAppService,
   1.587 +  nsPIExternalAppLauncher,
   1.588 +  nsIExternalProtocolService,
   1.589 +  nsIMIMEService,
   1.590 +  nsIObserver,
   1.591 +  nsISupportsWeakReference)
   1.592 +
   1.593 +nsExternalHelperAppService::nsExternalHelperAppService()
   1.594 +{
   1.595 +}
   1.596 +nsresult nsExternalHelperAppService::Init()
   1.597 +{
   1.598 +  // Add an observer for profile change
   1.599 +  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   1.600 +  if (!obs)
   1.601 +    return NS_ERROR_FAILURE;
   1.602 +
   1.603 +#ifdef PR_LOGGING
   1.604 +  if (!mLog) {
   1.605 +    mLog = PR_NewLogModule("HelperAppService");
   1.606 +    if (!mLog)
   1.607 +      return NS_ERROR_OUT_OF_MEMORY;
   1.608 +  }
   1.609 +#endif
   1.610 +
   1.611 +  nsresult rv = obs->AddObserver(this, "profile-before-change", true);
   1.612 +  NS_ENSURE_SUCCESS(rv, rv);
   1.613 +  return obs->AddObserver(this, "last-pb-context-exited", true);
   1.614 +}
   1.615 +
   1.616 +nsExternalHelperAppService::~nsExternalHelperAppService()
   1.617 +{
   1.618 +}
   1.619 +
   1.620 +#ifdef NECKO_PROTOCOL_rtsp
   1.621 +namespace {
   1.622 +/**
   1.623 + * A stack helper to clear the currently pending exception in a JS context.
   1.624 + */
   1.625 +class AutoClearPendingException {
   1.626 +public:
   1.627 +  AutoClearPendingException(JSContext* aCx) :
   1.628 +    mCx(aCx) {
   1.629 +  }
   1.630 +  ~AutoClearPendingException() {
   1.631 +    JS_ClearPendingException(mCx);
   1.632 +  }
   1.633 +private:
   1.634 +  JSContext *mCx;
   1.635 +};
   1.636 +} // anonymous namespace
   1.637 +
   1.638 +/**
   1.639 + * This function sends a message. This 'content-handler' message is handled in
   1.640 + * b2g/chrome/content/shell.js where it starts an activity request that will
   1.641 + * open the video app.
   1.642 + */
   1.643 +void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI)
   1.644 +{
   1.645 +  bool rv;
   1.646 +
   1.647 +  // Get a system principal.
   1.648 +  nsCOMPtr<nsIScriptSecurityManager> securityManager =
   1.649 +    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
   1.650 +  NS_ENSURE_TRUE_VOID(securityManager);
   1.651 +
   1.652 +  nsCOMPtr<nsIPrincipal> principal;
   1.653 +  securityManager->GetSystemPrincipal(getter_AddRefs(principal));
   1.654 +  NS_ENSURE_TRUE_VOID(principal);
   1.655 +
   1.656 +  // Construct the message in jsVal format.
   1.657 +  AutoSafeJSContext cx;
   1.658 +  AutoClearPendingException helper(cx);
   1.659 +  JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
   1.660 +  NS_ENSURE_TRUE_VOID(msgObj);
   1.661 +  JS::Rooted<JS::Value> jsVal(cx);
   1.662 +
   1.663 +  // Set the "type" property of the message. This is a fake MIME type.
   1.664 +  {
   1.665 +    NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp");
   1.666 +    JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length());
   1.667 +    NS_ENSURE_TRUE_VOID(typeStr);
   1.668 +    jsVal.setString(typeStr);
   1.669 +    rv = JS_SetProperty(cx, msgObj, "type", jsVal);
   1.670 +    NS_ENSURE_TRUE_VOID(rv);
   1.671 +  }
   1.672 +  // Set the "url" and "title" properties of the message.
   1.673 +  // They are the same in the case of RTSP streaming.
   1.674 +  {
   1.675 +    nsAutoCString spec;
   1.676 +    aURI->GetSpec(spec);
   1.677 +    JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length());
   1.678 +    NS_ENSURE_TRUE_VOID(urlStr);
   1.679 +    jsVal.setString(urlStr);
   1.680 +    rv = JS_SetProperty(cx, msgObj, "url", jsVal);
   1.681 +    NS_ENSURE_TRUE_VOID(rv);
   1.682 +    rv = JS_SetProperty(cx, msgObj, "title", jsVal);
   1.683 +  }
   1.684 +  jsVal.setObject(*msgObj);
   1.685 +
   1.686 +  // Send the message.
   1.687 +  nsCOMPtr<nsIMessageSender> cpmm =
   1.688 +    do_GetService("@mozilla.org/childprocessmessagemanager;1");
   1.689 +  NS_ENSURE_TRUE_VOID(cpmm);
   1.690 +  cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"),
   1.691 +                         jsVal, JS::NullHandleValue, principal, cx, 2);
   1.692 +}
   1.693 +#endif
   1.694 +
   1.695 +NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
   1.696 +                                                    nsIRequest *aRequest,
   1.697 +                                                    nsIInterfaceRequestor *aWindowContext,
   1.698 +                                                    bool aForceSave,
   1.699 +                                                    nsIStreamListener ** aStreamListener)
   1.700 +{
   1.701 +  nsAutoString fileName;
   1.702 +  nsAutoCString fileExtension;
   1.703 +  uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
   1.704 +  uint32_t contentDisposition = -1;
   1.705 +
   1.706 +  nsresult rv;
   1.707 +
   1.708 +  // Get the file extension and name that we will need later
   1.709 +  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   1.710 +  nsCOMPtr<nsIURI> uri;
   1.711 +  int64_t contentLength = -1;
   1.712 +  if (channel) {
   1.713 +    channel->GetURI(getter_AddRefs(uri));
   1.714 +    channel->GetContentLength(&contentLength);
   1.715 +    channel->GetContentDisposition(&contentDisposition);
   1.716 +    channel->GetContentDispositionFilename(fileName);
   1.717 +  }
   1.718 +  
   1.719 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
   1.720 +    nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext);
   1.721 +    NS_ENSURE_STATE(window);
   1.722 +
   1.723 +    // We need to get a hold of a ContentChild so that we can begin forwarding
   1.724 +    // this data to the parent.  In the HTTP case, this is unfortunate, since
   1.725 +    // we're actually passing data from parent->child->parent wastefully, but
   1.726 +    // the Right Fix will eventually be to short-circuit those channels on the
   1.727 +    // parent side based on some sort of subscription concept.
   1.728 +    using mozilla::dom::ContentChild;
   1.729 +    using mozilla::dom::ExternalHelperAppChild;
   1.730 +    ContentChild *child = ContentChild::GetSingleton();
   1.731 +    if (!child)
   1.732 +      return NS_ERROR_FAILURE;
   1.733 +
   1.734 +    nsCString disp;
   1.735 +    if (channel) {
   1.736 +      channel->GetContentDispositionHeader(disp);
   1.737 +    }
   1.738 +
   1.739 +    nsCOMPtr<nsIURI> referrer;
   1.740 +    rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
   1.741 +
   1.742 +    OptionalURIParams uriParams, referrerParams;
   1.743 +    SerializeURI(uri, uriParams);
   1.744 +    SerializeURI(referrer, referrerParams);
   1.745 +
   1.746 +    // Now we build a protocol for forwarding our data to the parent.  The
   1.747 +    // protocol will act as a listener on the child-side and create a "real"
   1.748 +    // helperAppService listener on the parent-side, via another call to
   1.749 +    // DoContent.
   1.750 +    mozilla::dom::PExternalHelperAppChild *pc =
   1.751 +      child->SendPExternalHelperAppConstructor(uriParams,
   1.752 +                                               nsCString(aMimeContentType),
   1.753 +                                               disp, contentDisposition,
   1.754 +                                               fileName, aForceSave, 
   1.755 +                                               contentLength, referrerParams,
   1.756 +                                               mozilla::dom::TabChild::GetFrom(window));
   1.757 +    ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
   1.758 +
   1.759 +    NS_ADDREF(*aStreamListener = childListener);
   1.760 +
   1.761 +    nsRefPtr<nsExternalAppHandler> handler =
   1.762 +      new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this,
   1.763 +                               fileName,
   1.764 +                               reason, aForceSave);
   1.765 +    if (!handler)
   1.766 +      return NS_ERROR_OUT_OF_MEMORY;
   1.767 +
   1.768 +    childListener->SetHandler(handler);
   1.769 +    return NS_OK;
   1.770 +  }
   1.771 +
   1.772 +  if (channel) {
   1.773 +    // Check if we have a POST request, in which case we don't want to use
   1.774 +    // the url's extension
   1.775 +    bool allowURLExt = true;
   1.776 +    nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
   1.777 +    if (httpChan) {
   1.778 +      nsAutoCString requestMethod;
   1.779 +      httpChan->GetRequestMethod(requestMethod);
   1.780 +      allowURLExt = !requestMethod.Equals("POST");
   1.781 +    }
   1.782 +
   1.783 +    // Check if we had a query string - we don't want to check the URL
   1.784 +    // extension if a query is present in the URI
   1.785 +    // If we already know we don't want to check the URL extension, don't
   1.786 +    // bother checking the query
   1.787 +    if (uri && allowURLExt) {
   1.788 +      nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
   1.789 +
   1.790 +      if (url) {
   1.791 +        nsAutoCString query;
   1.792 +
   1.793 +        // We only care about the query for HTTP and HTTPS URLs
   1.794 +        bool isHTTP, isHTTPS;
   1.795 +        rv = uri->SchemeIs("http", &isHTTP);
   1.796 +        if (NS_FAILED(rv))
   1.797 +          isHTTP = false;
   1.798 +        rv = uri->SchemeIs("https", &isHTTPS);
   1.799 +        if (NS_FAILED(rv))
   1.800 +          isHTTPS = false;
   1.801 +
   1.802 +        if (isHTTP || isHTTPS)
   1.803 +          url->GetQuery(query);
   1.804 +
   1.805 +        // Only get the extension if the query is empty; if it isn't, then the
   1.806 +        // extension likely belongs to a cgi script and isn't helpful
   1.807 +        allowURLExt = query.IsEmpty();
   1.808 +      }
   1.809 +    }
   1.810 +    // Extract name & extension
   1.811 +    bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
   1.812 +                                                             fileExtension,
   1.813 +                                                             allowURLExt);
   1.814 +    LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
   1.815 +         fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
   1.816 +         isAttachment));
   1.817 +    if (isAttachment)
   1.818 +      reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
   1.819 +  }
   1.820 +
   1.821 +  LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
   1.822 +       PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
   1.823 +
   1.824 +  // we get the mime service here even though we're the default implementation of it,
   1.825 +  // so it's possible to override only the mime service and not need to reimplement the
   1.826 +  // whole external helper app service itself
   1.827 +  nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
   1.828 +  NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
   1.829 +
   1.830 +  // Try to find a mime object by looking at the mime type/extension
   1.831 +  nsCOMPtr<nsIMIMEInfo> mimeInfo;
   1.832 +  if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
   1.833 +    nsAutoCString mimeType;
   1.834 +    if (!fileExtension.IsEmpty()) {
   1.835 +      mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
   1.836 +      if (mimeInfo) {
   1.837 +        mimeInfo->GetMIMEType(mimeType);
   1.838 +
   1.839 +        LOG(("OS-Provided mime type '%s' for extension '%s'\n", 
   1.840 +             mimeType.get(), fileExtension.get()));
   1.841 +      }
   1.842 +    }
   1.843 +
   1.844 +    if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
   1.845 +      // Extension lookup gave us no useful match
   1.846 +      mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
   1.847 +                                       getter_AddRefs(mimeInfo));
   1.848 +      mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
   1.849 +    }
   1.850 +    if (channel)
   1.851 +      channel->SetContentType(mimeType);
   1.852 +    // Don't overwrite SERVERREQUEST
   1.853 +    if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
   1.854 +      reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
   1.855 +  } 
   1.856 +  else {
   1.857 +    mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
   1.858 +                                     getter_AddRefs(mimeInfo));
   1.859 +  } 
   1.860 +  LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
   1.861 +
   1.862 +  // No mimeinfo -> we can't continue. probably OOM.
   1.863 +  if (!mimeInfo)
   1.864 +    return NS_ERROR_OUT_OF_MEMORY;
   1.865 +
   1.866 +  *aStreamListener = nullptr;
   1.867 +  // We want the mimeInfo's primary extension to pass it to
   1.868 +  // nsExternalAppHandler
   1.869 +  nsAutoCString buf;
   1.870 +  mimeInfo->GetPrimaryExtension(buf);
   1.871 +
   1.872 +  nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
   1.873 +                                                            buf,
   1.874 +                                                            aWindowContext,
   1.875 +                                                            this,
   1.876 +                                                            fileName,
   1.877 +                                                            reason,
   1.878 +                                                            aForceSave);
   1.879 +  if (!handler)
   1.880 +    return NS_ERROR_OUT_OF_MEMORY;
   1.881 +  NS_ADDREF(*aStreamListener = handler);
   1.882 +  
   1.883 +  return NS_OK;
   1.884 +}
   1.885 +
   1.886 +NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
   1.887 +                                                                    const nsACString& aEncodingType,
   1.888 +                                                                    bool *aApplyDecoding)
   1.889 +{
   1.890 +  *aApplyDecoding = true;
   1.891 +  uint32_t i;
   1.892 +  for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
   1.893 +    if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
   1.894 +        aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
   1.895 +      *aApplyDecoding = false;
   1.896 +      break;
   1.897 +    }
   1.898 +  }
   1.899 +  return NS_OK;
   1.900 +}
   1.901 +
   1.902 +nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
   1.903 +                                                         nsIFile ** aFile)
   1.904 +{
   1.905 +  nsDependentString platformAppPath(aPlatformAppPath);
   1.906 +  // First, check if we have an absolute path
   1.907 +  nsIFile* localFile = nullptr;
   1.908 +  nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
   1.909 +  if (NS_SUCCEEDED(rv)) {
   1.910 +    *aFile = localFile;
   1.911 +    bool exists;
   1.912 +    if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
   1.913 +      NS_RELEASE(*aFile);
   1.914 +      return NS_ERROR_FILE_NOT_FOUND;
   1.915 +    }
   1.916 +    return NS_OK;
   1.917 +  }
   1.918 +
   1.919 +
   1.920 +  // Second, check if file exists in mozilla program directory
   1.921 +  rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
   1.922 +  if (NS_SUCCEEDED(rv)) {
   1.923 +    rv = (*aFile)->Append(platformAppPath);
   1.924 +    if (NS_SUCCEEDED(rv)) {
   1.925 +      bool exists = false;
   1.926 +      rv = (*aFile)->Exists(&exists);
   1.927 +      if (NS_SUCCEEDED(rv) && exists)
   1.928 +        return NS_OK;
   1.929 +    }
   1.930 +    NS_RELEASE(*aFile);
   1.931 +  }
   1.932 +
   1.933 +
   1.934 +  return NS_ERROR_NOT_AVAILABLE;
   1.935 +}
   1.936 +
   1.937 +//////////////////////////////////////////////////////////////////////////////////////////////////////
   1.938 +// begin external protocol service default implementation...
   1.939 +//////////////////////////////////////////////////////////////////////////////////////////////////////
   1.940 +NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
   1.941 +                                                                        bool * aHandlerExists)
   1.942 +{
   1.943 +  nsCOMPtr<nsIHandlerInfo> handlerInfo;
   1.944 +  nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme), 
   1.945 +                                       getter_AddRefs(handlerInfo));
   1.946 +  NS_ENSURE_SUCCESS(rv, rv);
   1.947 +
   1.948 +  // See if we have any known possible handler apps for this
   1.949 +  nsCOMPtr<nsIMutableArray> possibleHandlers;
   1.950 +  handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
   1.951 +
   1.952 +  uint32_t length;
   1.953 +  possibleHandlers->GetLength(&length);
   1.954 +  if (length) {
   1.955 +    *aHandlerExists = true;
   1.956 +    return NS_OK;
   1.957 +  }
   1.958 +
   1.959 +  // if not, fall back on an os-based handler
   1.960 +  return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
   1.961 +}
   1.962 +
   1.963 +NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
   1.964 +{
   1.965 +  // check the per protocol setting first.  it always takes precedence.
   1.966 +  // if not set, then use the global setting.
   1.967 +
   1.968 +  nsAutoCString prefName("network.protocol-handler.expose.");
   1.969 +  prefName += aProtocolScheme;
   1.970 +  bool val;
   1.971 +  if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
   1.972 +    *aResult = val;
   1.973 +    return NS_OK;
   1.974 +  }
   1.975 +
   1.976 +  // by default, no protocol is exposed.  i.e., by default all link clicks must
   1.977 +  // go through the external protocol service.  most applications override this
   1.978 +  // default behavior.
   1.979 +  *aResult =
   1.980 +    Preferences::GetBool("network.protocol-handler.expose-all", false);
   1.981 +
   1.982 +  return NS_OK;
   1.983 +}
   1.984 +
   1.985 +NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
   1.986 +{
   1.987 +  return LoadURI(aURL, nullptr);
   1.988 +}
   1.989 +
   1.990 +static const char kExternalProtocolPrefPrefix[]  = "network.protocol-handler.external.";
   1.991 +static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
   1.992 +
   1.993 +NS_IMETHODIMP 
   1.994 +nsExternalHelperAppService::LoadURI(nsIURI *aURI,
   1.995 +                                    nsIInterfaceRequestor *aWindowContext)
   1.996 +{
   1.997 +  NS_ENSURE_ARG_POINTER(aURI);
   1.998 +
   1.999 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.1000 +    URIParams uri;
  1.1001 +    SerializeURI(aURI, uri);
  1.1002 +
  1.1003 +    mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri);
  1.1004 +    return NS_OK;
  1.1005 +  }
  1.1006 +
  1.1007 +  nsAutoCString spec;
  1.1008 +  aURI->GetSpec(spec);
  1.1009 +
  1.1010 +  if (spec.Find("%00") != -1)
  1.1011 +    return NS_ERROR_MALFORMED_URI;
  1.1012 +
  1.1013 +  spec.ReplaceSubstring("\"", "%22");
  1.1014 +  spec.ReplaceSubstring("`", "%60");
  1.1015 +  
  1.1016 +  nsCOMPtr<nsIIOService> ios(do_GetIOService());
  1.1017 +  nsCOMPtr<nsIURI> uri;
  1.1018 +  nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
  1.1019 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1020 +
  1.1021 +  nsAutoCString scheme;
  1.1022 +  uri->GetScheme(scheme);
  1.1023 +  if (scheme.IsEmpty())
  1.1024 +    return NS_OK; // must have a scheme
  1.1025 +
  1.1026 +  // Deny load if the prefs say to do so
  1.1027 +  nsAutoCString externalPref(kExternalProtocolPrefPrefix);
  1.1028 +  externalPref += scheme;
  1.1029 +  bool allowLoad  = false;
  1.1030 +  if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
  1.1031 +    // no scheme-specific value, check the default
  1.1032 +    if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
  1.1033 +                                       &allowLoad))) {
  1.1034 +      return NS_OK; // missing default pref
  1.1035 +    }
  1.1036 +  }
  1.1037 +
  1.1038 +  if (!allowLoad) {
  1.1039 +    return NS_OK; // explicitly denied
  1.1040 +  }
  1.1041 +
  1.1042 +#ifdef NECKO_PROTOCOL_rtsp
  1.1043 +  // Handle rtsp protocol.
  1.1044 +  {
  1.1045 +    bool isRTSP = false;
  1.1046 +    rv = aURI->SchemeIs("rtsp", &isRTSP);
  1.1047 +    if (NS_SUCCEEDED(rv) && isRTSP) {
  1.1048 +      LaunchVideoAppForRtsp(aURI);
  1.1049 +      return NS_OK;
  1.1050 +    }
  1.1051 +  }
  1.1052 +#endif
  1.1053 +
  1.1054 +  nsCOMPtr<nsIHandlerInfo> handler;
  1.1055 +  rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
  1.1056 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1057 +
  1.1058 +  nsHandlerInfoAction preferredAction;
  1.1059 +  handler->GetPreferredAction(&preferredAction);
  1.1060 +  bool alwaysAsk = true;
  1.1061 +  handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
  1.1062 +
  1.1063 +  // if we are not supposed to ask, and the preferred action is to use
  1.1064 +  // a helper app or the system default, we just launch the URI.
  1.1065 +  if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
  1.1066 +                     preferredAction == nsIHandlerInfo::useSystemDefault))
  1.1067 +    return handler->LaunchWithURI(uri, aWindowContext);
  1.1068 +  
  1.1069 +  nsCOMPtr<nsIContentDispatchChooser> chooser =
  1.1070 +    do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
  1.1071 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1072 +  
  1.1073 +  return chooser->Ask(handler, aWindowContext, uri,
  1.1074 +                      nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
  1.1075 +}
  1.1076 +
  1.1077 +NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
  1.1078 +{
  1.1079 +  // this method should only be implemented by each OS specific implementation of this service.
  1.1080 +  return NS_ERROR_NOT_IMPLEMENTED;
  1.1081 +}
  1.1082 +
  1.1083 +
  1.1084 +//////////////////////////////////////////////////////////////////////////////////////////////////////
  1.1085 +// Methods related to deleting temporary files on exit
  1.1086 +//////////////////////////////////////////////////////////////////////////////////////////////////////
  1.1087 +
  1.1088 +/* static */
  1.1089 +nsresult
  1.1090 +nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
  1.1091 +                                                      nsCOMArray<nsIFile> &aFileList)
  1.1092 +{
  1.1093 +  bool isFile = false;
  1.1094 +
  1.1095 +  // as a safety measure, make sure the nsIFile is really a file and not a directory object.
  1.1096 +  aTemporaryFile->IsFile(&isFile);
  1.1097 +  if (!isFile) return NS_OK;
  1.1098 +
  1.1099 +  aFileList.AppendObject(aTemporaryFile);
  1.1100 +
  1.1101 +  return NS_OK;
  1.1102 +}
  1.1103 +
  1.1104 +NS_IMETHODIMP
  1.1105 +nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
  1.1106 +{
  1.1107 +  return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
  1.1108 +}
  1.1109 +
  1.1110 +NS_IMETHODIMP
  1.1111 +nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
  1.1112 +{
  1.1113 +  return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
  1.1114 +}
  1.1115 +
  1.1116 +void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
  1.1117 +{
  1.1118 +  int32_t numEntries = fileList.Count();
  1.1119 +  nsIFile* localFile;
  1.1120 +  for (int32_t index = 0; index < numEntries; index++)
  1.1121 +  {
  1.1122 +    localFile = fileList[index];
  1.1123 +    if (localFile) {
  1.1124 +      // First make the file writable, since the temp file is probably readonly.
  1.1125 +      localFile->SetPermissions(0600);
  1.1126 +      localFile->Remove(false);
  1.1127 +    }
  1.1128 +  }
  1.1129 +
  1.1130 +  fileList.Clear();
  1.1131 +}
  1.1132 +
  1.1133 +void nsExternalHelperAppService::ExpungeTemporaryFiles()
  1.1134 +{
  1.1135 +  ExpungeTemporaryFilesHelper(mTemporaryFilesList);
  1.1136 +}
  1.1137 +
  1.1138 +void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
  1.1139 +{
  1.1140 +  ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
  1.1141 +}
  1.1142 +
  1.1143 +static const char kExternalWarningPrefPrefix[] = 
  1.1144 +  "network.protocol-handler.warn-external.";
  1.1145 +static const char kExternalWarningDefaultPref[] = 
  1.1146 +  "network.protocol-handler.warn-external-default";
  1.1147 +
  1.1148 +NS_IMETHODIMP
  1.1149 +nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
  1.1150 +                                                   nsIHandlerInfo **aHandlerInfo)
  1.1151 +{
  1.1152 +  // XXX enterprise customers should be able to turn this support off with a
  1.1153 +  // single master pref (maybe use one of the "exposed" prefs here?)
  1.1154 +
  1.1155 +  bool exists;
  1.1156 +  nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
  1.1157 +  if (NS_FAILED(rv)) {
  1.1158 +    // Either it knows nothing, or we ran out of memory
  1.1159 +    return NS_ERROR_FAILURE;
  1.1160 +  }
  1.1161 +  
  1.1162 +  nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1.1163 +  if (handlerSvc) {
  1.1164 +    bool hasHandler = false;
  1.1165 +    (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
  1.1166 +    if (hasHandler) {
  1.1167 +      rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
  1.1168 +      if (NS_SUCCEEDED(rv))
  1.1169 +        return NS_OK;
  1.1170 +    }
  1.1171 +  }
  1.1172 +  
  1.1173 +  return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
  1.1174 +}
  1.1175 +
  1.1176 +NS_IMETHODIMP
  1.1177 +nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
  1.1178 +                                                         bool *found,
  1.1179 +                                                         nsIHandlerInfo **aHandlerInfo)
  1.1180 +{
  1.1181 +  // intended to be implemented by the subclass
  1.1182 +  return NS_ERROR_NOT_IMPLEMENTED;
  1.1183 +}
  1.1184 +
  1.1185 +NS_IMETHODIMP
  1.1186 +nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
  1.1187 +                                                       bool aOSHandlerExists)
  1.1188 +{
  1.1189 +  // this type isn't in our database, so we've only got an OS default handler,
  1.1190 +  // if one exists
  1.1191 +
  1.1192 +  if (aOSHandlerExists) {
  1.1193 +    // we've got a default, so use it
  1.1194 +    aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
  1.1195 +
  1.1196 +    // whether or not to ask the user depends on the warning preference
  1.1197 +    nsAutoCString scheme;
  1.1198 +    aHandlerInfo->GetType(scheme);
  1.1199 +    
  1.1200 +    nsAutoCString warningPref(kExternalWarningPrefPrefix);
  1.1201 +    warningPref += scheme;
  1.1202 +    bool warn;
  1.1203 +    if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
  1.1204 +      // no scheme-specific value, check the default
  1.1205 +      warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
  1.1206 +    }
  1.1207 +    aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
  1.1208 +  } else {
  1.1209 +    // If no OS default existed, we set the preferred action to alwaysAsk. 
  1.1210 +    // This really means not initialized (i.e. there's no available handler)
  1.1211 +    // to all the code...
  1.1212 +    aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
  1.1213 +  }
  1.1214 +
  1.1215 +  return NS_OK;
  1.1216 +}
  1.1217 + 
  1.1218 +// XPCOM profile change observer
  1.1219 +NS_IMETHODIMP
  1.1220 +nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
  1.1221 +{
  1.1222 +  if (!strcmp(aTopic, "profile-before-change")) {
  1.1223 +    ExpungeTemporaryFiles();
  1.1224 +  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
  1.1225 +    ExpungeTemporaryPrivateFiles();
  1.1226 +  }
  1.1227 +  return NS_OK;
  1.1228 +}
  1.1229 +
  1.1230 +//////////////////////////////////////////////////////////////////////////////////////////////////////
  1.1231 +// begin external app handler implementation 
  1.1232 +//////////////////////////////////////////////////////////////////////////////////////////////////////
  1.1233 +
  1.1234 +NS_IMPL_ADDREF(nsExternalAppHandler)
  1.1235 +NS_IMPL_RELEASE(nsExternalAppHandler)
  1.1236 +
  1.1237 +NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
  1.1238 +   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
  1.1239 +   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  1.1240 +   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  1.1241 +   NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
  1.1242 +   NS_INTERFACE_MAP_ENTRY(nsICancelable)
  1.1243 +   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
  1.1244 +   NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
  1.1245 +NS_INTERFACE_MAP_END_THREADSAFE
  1.1246 +
  1.1247 +nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
  1.1248 +                                           const nsCSubstring& aTempFileExtension,
  1.1249 +                                           nsIInterfaceRequestor* aWindowContext,
  1.1250 +                                           nsExternalHelperAppService *aExtProtSvc,
  1.1251 +                                           const nsAString& aSuggestedFilename,
  1.1252 +                                           uint32_t aReason, bool aForceSave)
  1.1253 +: mMimeInfo(aMIMEInfo)
  1.1254 +, mWindowContext(aWindowContext)
  1.1255 +, mWindowToClose(nullptr)
  1.1256 +, mSuggestedFileName(aSuggestedFilename)
  1.1257 +, mForceSave(aForceSave)
  1.1258 +, mCanceled(false)
  1.1259 +, mShouldCloseWindow(false)
  1.1260 +, mStopRequestIssued(false)
  1.1261 +, mReason(aReason)
  1.1262 +, mContentLength(-1)
  1.1263 +, mProgress(0)
  1.1264 +, mSaver(nullptr)
  1.1265 +, mDialogProgressListener(nullptr)
  1.1266 +, mTransfer(nullptr)
  1.1267 +, mRequest(nullptr)
  1.1268 +, mExtProtSvc(aExtProtSvc)
  1.1269 +{
  1.1270 +
  1.1271 +  // make sure the extention includes the '.'
  1.1272 +  if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
  1.1273 +    mTempFileExtension = char16_t('.');
  1.1274 +  AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
  1.1275 +
  1.1276 +  // replace platform specific path separator and illegal characters to avoid any confusion
  1.1277 +  mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1.1278 +  mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1.1279 +
  1.1280 +  // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
  1.1281 +  const char16_t unsafeBidiCharacters[] = {
  1.1282 +    char16_t(0x061c), // Arabic Letter Mark
  1.1283 +    char16_t(0x200e), // Left-to-Right Mark
  1.1284 +    char16_t(0x200f), // Right-to-Left Mark
  1.1285 +    char16_t(0x202a), // Left-to-Right Embedding
  1.1286 +    char16_t(0x202b), // Right-to-Left Embedding
  1.1287 +    char16_t(0x202c), // Pop Directional Formatting
  1.1288 +    char16_t(0x202d), // Left-to-Right Override
  1.1289 +    char16_t(0x202e), // Right-to-Left Override
  1.1290 +    char16_t(0x2066), // Left-to-Right Isolate
  1.1291 +    char16_t(0x2067), // Right-to-Left Isolate
  1.1292 +    char16_t(0x2068), // First Strong Isolate
  1.1293 +    char16_t(0x2069), // Pop Directional Isolate
  1.1294 +    char16_t(0)
  1.1295 +  };
  1.1296 +  mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
  1.1297 +  mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
  1.1298 +
  1.1299 +  // Make sure extension is correct.
  1.1300 +  EnsureSuggestedFileName();
  1.1301 +
  1.1302 +  mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
  1.1303 +}
  1.1304 +
  1.1305 +nsExternalAppHandler::~nsExternalAppHandler()
  1.1306 +{
  1.1307 +  MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
  1.1308 +}
  1.1309 +
  1.1310 +void
  1.1311 +nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
  1.1312 +{
  1.1313 +  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process");
  1.1314 +  // Remove our request from the child loadGroup
  1.1315 +  RetargetLoadNotifications(request);
  1.1316 +  MaybeCloseWindow();
  1.1317 +}
  1.1318 +
  1.1319 +NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
  1.1320 +{
  1.1321 +  // This is always called by nsHelperDlg.js. Go ahead and register the
  1.1322 +  // progress listener. At this point, we don't have mTransfer.
  1.1323 +  mDialogProgressListener = aWebProgressListener;
  1.1324 +  return NS_OK;
  1.1325 +}
  1.1326 +
  1.1327 +NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
  1.1328 +{
  1.1329 +  if (mFinalFileDestination)
  1.1330 +    *aTarget = mFinalFileDestination;
  1.1331 +  else
  1.1332 +    *aTarget = mTempFile;
  1.1333 +
  1.1334 +  NS_IF_ADDREF(*aTarget);
  1.1335 +  return NS_OK;
  1.1336 +}
  1.1337 +
  1.1338 +NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
  1.1339 +{
  1.1340 +  // Use the real target if it's been set
  1.1341 +  if (mFinalFileDestination)
  1.1342 +    return mFinalFileDestination->IsExecutable(aExec);
  1.1343 +
  1.1344 +  // Otherwise, use the stored executable-ness of the temporary
  1.1345 +  *aExec = mTempFileIsExecutable;
  1.1346 +  return NS_OK;
  1.1347 +}
  1.1348 +
  1.1349 +NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
  1.1350 +{
  1.1351 +  *aTime = mTimeDownloadStarted;
  1.1352 +  return NS_OK;
  1.1353 +}
  1.1354 +
  1.1355 +NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
  1.1356 +{
  1.1357 +  *aContentLength = mContentLength;
  1.1358 +  return NS_OK;
  1.1359 +}
  1.1360 +
  1.1361 +void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
  1.1362 +{
  1.1363 +  // we are going to run the downloading of the helper app in our own little docloader / load group context. 
  1.1364 +  // so go ahead and force the creation of a load group and doc loader for us to use...
  1.1365 +  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  1.1366 +  if (!aChannel)
  1.1367 +    return;
  1.1368 +
  1.1369 +  // we need to store off the original (pre redirect!) channel that initiated the load. We do
  1.1370 +  // this so later on, we can pass any refresh urls associated with the original channel back to the 
  1.1371 +  // window context which started the whole process. More comments about that are listed below....
  1.1372 +  // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. 
  1.1373 +  // ideally we should be able to just use mChannel (the channel we are extracting content from) or
  1.1374 +  // the default load channel associated with the original load group. Unfortunately because
  1.1375 +  // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel 
  1.1376 +  // which is what we really want....
  1.1377 +
  1.1378 +  // Note that we need to do this before removing aChannel from the loadgroup,
  1.1379 +  // since that would mess with the original channel on the loader.
  1.1380 +  nsCOMPtr<nsIDocumentLoader> origContextLoader =
  1.1381 +    do_GetInterface(mWindowContext);
  1.1382 +  if (origContextLoader)
  1.1383 +    origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
  1.1384 +
  1.1385 +  bool isPrivate = NS_UsePrivateBrowsing(aChannel);
  1.1386 +
  1.1387 +  nsCOMPtr<nsILoadGroup> oldLoadGroup;
  1.1388 +  aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
  1.1389 +
  1.1390 +  if(oldLoadGroup)
  1.1391 +     oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
  1.1392 +      
  1.1393 +  aChannel->SetLoadGroup(nullptr);
  1.1394 +  aChannel->SetNotificationCallbacks(nullptr);
  1.1395 +
  1.1396 +  nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
  1.1397 +  if (pbChannel) {
  1.1398 +    pbChannel->SetPrivate(isPrivate);
  1.1399 +  }
  1.1400 +}
  1.1401 +
  1.1402 +/**
  1.1403 + * Make mTempFileExtension contain an extension exactly when its previous value
  1.1404 + * is different from mSuggestedFileName's extension, so that it can be appended
  1.1405 + * to mSuggestedFileName and form a valid, useful leaf name.
  1.1406 + * This is required so that the (renamed) temporary file has the correct extension
  1.1407 + * after downloading to make sure the OS will launch the application corresponding
  1.1408 + * to the MIME type (which was used to calculate mTempFileExtension).  This prevents
  1.1409 + * a cgi-script named foobar.exe that returns application/zip from being named
  1.1410 + * foobar.exe and executed as an executable file. It also blocks content that
  1.1411 + * a web site might provide with a content-disposition header indicating
  1.1412 + * filename="foobar.exe" from being downloaded to a file with extension .exe
  1.1413 + * and executed.
  1.1414 + */
  1.1415 +void nsExternalAppHandler::EnsureSuggestedFileName()
  1.1416 +{
  1.1417 +  // Make sure there is a mTempFileExtension (not "" or ".").
  1.1418 +  // Remember that mTempFileExtension will always have the leading "."
  1.1419 +  // (the check for empty is just to be safe).
  1.1420 +  if (mTempFileExtension.Length() > 1)
  1.1421 +  {
  1.1422 +    // Get mSuggestedFileName's current extension.
  1.1423 +    nsAutoString fileExt;
  1.1424 +    int32_t pos = mSuggestedFileName.RFindChar('.');
  1.1425 +    if (pos != kNotFound)
  1.1426 +      mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
  1.1427 +
  1.1428 +    // Now, compare fileExt to mTempFileExtension.
  1.1429 +    if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
  1.1430 +    {
  1.1431 +      // Matches -> mTempFileExtension can be empty
  1.1432 +      mTempFileExtension.Truncate();
  1.1433 +    }
  1.1434 +  }
  1.1435 +}
  1.1436 +
  1.1437 +nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
  1.1438 +{
  1.1439 +  // First we need to try to get the destination directory for the temporary
  1.1440 +  // file.
  1.1441 +  nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
  1.1442 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1443 +
  1.1444 +  // At this point, we do not have a filename for the temp file.  For security
  1.1445 +  // purposes, this cannot be predictable, so we must use a cryptographic
  1.1446 +  // quality PRNG to generate one.
  1.1447 +  // We will request raw random bytes, and transform that to a base64 string,
  1.1448 +  // as all characters from the base64 set are acceptable for filenames.  For
  1.1449 +  // each three bytes of random data, we will get four bytes of ASCII.  Request
  1.1450 +  // a bit more, to be safe, and truncate to the length we want in the end.
  1.1451 +
  1.1452 +  const uint32_t wantedFileNameLength = 8;
  1.1453 +  const uint32_t requiredBytesLength =
  1.1454 +    static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
  1.1455 +
  1.1456 +  nsCOMPtr<nsIRandomGenerator> rg =
  1.1457 +    do_GetService("@mozilla.org/security/random-generator;1", &rv);
  1.1458 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1459 +
  1.1460 +  uint8_t *buffer;
  1.1461 +  rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
  1.1462 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1463 +
  1.1464 +  nsAutoCString tempLeafName;
  1.1465 +  nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
  1.1466 +  rv = Base64Encode(randomData, tempLeafName);
  1.1467 +  NS_Free(buffer);
  1.1468 +  buffer = nullptr;
  1.1469 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1470 +
  1.1471 +  tempLeafName.Truncate(wantedFileNameLength);
  1.1472 +
  1.1473 +  // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
  1.1474 +  // to replace illegal characters -- notably '/'
  1.1475 +  tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1.1476 +
  1.1477 +  // now append our extension.
  1.1478 +  nsAutoCString ext;
  1.1479 +  mMimeInfo->GetPrimaryExtension(ext);
  1.1480 +  if (!ext.IsEmpty()) {
  1.1481 +    ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
  1.1482 +    if (ext.First() != '.')
  1.1483 +      tempLeafName.Append('.');
  1.1484 +    tempLeafName.Append(ext);
  1.1485 +  }
  1.1486 +
  1.1487 +  // We need to temporarily create a dummy file with the correct
  1.1488 +  // file extension to determine the executable-ness, so do this before adding
  1.1489 +  // the extra .part extension.
  1.1490 +  nsCOMPtr<nsIFile> dummyFile;
  1.1491 +  rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
  1.1492 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1493 +
  1.1494 +  // Set the file name without .part
  1.1495 +  rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  1.1496 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1497 +  rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
  1.1498 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1499 +
  1.1500 +  // Store executable-ness then delete
  1.1501 +  dummyFile->IsExecutable(&mTempFileIsExecutable);
  1.1502 +  dummyFile->Remove(false);
  1.1503 +
  1.1504 +  // Add an additional .part to prevent the OS from running this file in the
  1.1505 +  // default application.
  1.1506 +  tempLeafName.Append(NS_LITERAL_CSTRING(".part"));
  1.1507 +
  1.1508 +  rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  1.1509 +  // make this file unique!!!
  1.1510 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1511 +  rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
  1.1512 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1513 +
  1.1514 +  // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
  1.1515 +  // This is a bit broken in the case when createUnique actually had to append
  1.1516 +  // some numbers, because then we now have a filename like foo.bar-1.part and
  1.1517 +  // we'll end up with foo.bar-1.bar as our final filename if we end up using
  1.1518 +  // this.  But the other options are all bad too....  Ideally we'd have a way
  1.1519 +  // to tell createUnique to put its unique marker before the extension that
  1.1520 +  // comes before ".part" or something.
  1.1521 +  rv = mTempFile->GetLeafName(mTempLeafName);
  1.1522 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1523 +
  1.1524 +  NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
  1.1525 +                 NS_ERROR_UNEXPECTED);
  1.1526 +
  1.1527 +  // Strip off the ".part" from mTempLeafName
  1.1528 +  mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
  1.1529 +
  1.1530 +  MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
  1.1531 +  mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
  1.1532 +                             &rv);
  1.1533 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1534 +
  1.1535 +  rv = mSaver->SetObserver(this);
  1.1536 +  if (NS_FAILED(rv)) {
  1.1537 +    mSaver = nullptr;
  1.1538 +    return rv;
  1.1539 +  }
  1.1540 +
  1.1541 +  rv = mSaver->EnableSha256();
  1.1542 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1543 +
  1.1544 +  rv = mSaver->EnableSignatureInfo();
  1.1545 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1546 +  LOG(("Enabled hashing and signature verification"));
  1.1547 +
  1.1548 +  rv = mSaver->SetTarget(mTempFile, false);
  1.1549 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1550 +
  1.1551 +  return rv;
  1.1552 +}
  1.1553 +
  1.1554 +NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
  1.1555 +{
  1.1556 +  NS_PRECONDITION(request, "OnStartRequest without request?");
  1.1557 +
  1.1558 +  // Set mTimeDownloadStarted here as the download has already started and
  1.1559 +  // we want to record the start time before showing the filepicker.
  1.1560 +  mTimeDownloadStarted = PR_Now();
  1.1561 +
  1.1562 +  mRequest = request;
  1.1563 +
  1.1564 +  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  1.1565 +  
  1.1566 +  nsresult rv;
  1.1567 +  
  1.1568 +  nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
  1.1569 +  mIsFileChannel = fileChan != nullptr;
  1.1570 +
  1.1571 +  // Get content length
  1.1572 +  if (aChannel) {
  1.1573 +    aChannel->GetContentLength(&mContentLength);
  1.1574 +  }
  1.1575 +
  1.1576 +  nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
  1.1577 +  // Determine whether a new window was opened specifically for this request
  1.1578 +  if (props) {
  1.1579 +    bool tmp = false;
  1.1580 +    props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
  1.1581 +                             &tmp);
  1.1582 +    mShouldCloseWindow = tmp;
  1.1583 +  }
  1.1584 +
  1.1585 +  // Now get the URI
  1.1586 +  if (aChannel)
  1.1587 +  {
  1.1588 +    aChannel->GetURI(getter_AddRefs(mSourceUrl));
  1.1589 +  }
  1.1590 +
  1.1591 +  // retarget all load notifications to our docloader instead of the original window's docloader...
  1.1592 +  RetargetLoadNotifications(request);
  1.1593 +
  1.1594 +  // Check to see if there is a refresh header on the original channel.
  1.1595 +  if (mOriginalChannel) {
  1.1596 +    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
  1.1597 +    if (httpChannel) {
  1.1598 +      nsAutoCString refreshHeader;
  1.1599 +      httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
  1.1600 +                                     refreshHeader);
  1.1601 +      if (!refreshHeader.IsEmpty()) {
  1.1602 +        mShouldCloseWindow = false;
  1.1603 +      }
  1.1604 +    }
  1.1605 +  }
  1.1606 +
  1.1607 +  // Close the underlying DOMWindow if there is no refresh header
  1.1608 +  // and it was opened specifically for the download
  1.1609 +  MaybeCloseWindow();
  1.1610 +
  1.1611 +  // In an IPC setting, we're allowing the child process, here, to make
  1.1612 +  // decisions about decoding the channel (e.g. decompression).  It will
  1.1613 +  // still forward the decoded (uncompressed) data back to the parent.
  1.1614 +  // Con: Uncompressed data means more IPC overhead.
  1.1615 +  // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
  1.1616 +  //       Parent process doesn't need to expect CPU time on decompression.
  1.1617 +  nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
  1.1618 +  if (encChannel) 
  1.1619 +  {
  1.1620 +    // Turn off content encoding conversions if needed
  1.1621 +    bool applyConversion = true;
  1.1622 +
  1.1623 +    nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
  1.1624 +    if (sourceURL)
  1.1625 +    {
  1.1626 +      nsAutoCString extension;
  1.1627 +      sourceURL->GetFileExtension(extension);
  1.1628 +      if (!extension.IsEmpty())
  1.1629 +      {
  1.1630 +        nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
  1.1631 +        encChannel->GetContentEncodings(getter_AddRefs(encEnum));
  1.1632 +        if (encEnum)
  1.1633 +        {
  1.1634 +          bool hasMore;
  1.1635 +          rv = encEnum->HasMore(&hasMore);
  1.1636 +          if (NS_SUCCEEDED(rv) && hasMore)
  1.1637 +          {
  1.1638 +            nsAutoCString encType;
  1.1639 +            rv = encEnum->GetNext(encType);
  1.1640 +            if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
  1.1641 +            {
  1.1642 +              mExtProtSvc->ApplyDecodingForExtension(extension, encType,
  1.1643 +                                                     &applyConversion);
  1.1644 +            }
  1.1645 +          }
  1.1646 +        }
  1.1647 +      }    
  1.1648 +    }
  1.1649 +
  1.1650 +    encChannel->SetApplyConversion( applyConversion );
  1.1651 +  }
  1.1652 +
  1.1653 +  // At this point, the child process has done everything it can usefully do
  1.1654 +  // for OnStartRequest.
  1.1655 +  if (XRE_GetProcessType() == GeckoProcessType_Content)
  1.1656 +     return NS_OK;
  1.1657 +
  1.1658 +  rv = SetUpTempFile(aChannel);
  1.1659 +  if (NS_FAILED(rv)) {
  1.1660 +    nsresult transferError = rv;
  1.1661 +
  1.1662 +    rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
  1.1663 +#ifdef PR_LOGGING
  1.1664 +    if (NS_FAILED(rv)) {
  1.1665 +      LOG(("Failed to create transfer to report failure."
  1.1666 +           "Will fallback to prompter!"));
  1.1667 +    }
  1.1668 +#endif
  1.1669 +
  1.1670 +    mCanceled = true;
  1.1671 +    request->Cancel(transferError);
  1.1672 +
  1.1673 +    nsAutoString path;
  1.1674 +    if (mTempFile)
  1.1675 +      mTempFile->GetPath(path);
  1.1676 +
  1.1677 +    SendStatusChange(kWriteError, transferError, request, path);
  1.1678 +
  1.1679 +    return NS_OK;
  1.1680 +  }
  1.1681 +
  1.1682 +  // Inform channel it is open on behalf of a download to prevent caching.
  1.1683 +  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
  1.1684 +  if (httpInternal) {
  1.1685 +    httpInternal->SetChannelIsForDownload(true);
  1.1686 +  }
  1.1687 +
  1.1688 +  // now that the temp file is set up, find out if we need to invoke a dialog
  1.1689 +  // asking the user what they want us to do with this content...
  1.1690 +
  1.1691 +  // We can get here for three reasons: "can't handle", "sniffed type", or
  1.1692 +  // "server sent content-disposition:attachment".  In the first case we want
  1.1693 +  // to honor the user's "always ask" pref; in the other two cases we want to
  1.1694 +  // honor it only if the default action is "save".  Opening attachments in
  1.1695 +  // helper apps by default breaks some websites (especially if the attachment
  1.1696 +  // is one part of a multipart document).  Opening sniffed content in helper
  1.1697 +  // apps by default introduces security holes that we'd rather not have.
  1.1698 +
  1.1699 +  // So let's find out whether the user wants to be prompted.  If he does not,
  1.1700 +  // check mReason and the preferred action to see what we should do.
  1.1701 +
  1.1702 +  bool alwaysAsk = true;
  1.1703 +  mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
  1.1704 +  if (alwaysAsk)
  1.1705 +  {
  1.1706 +    // But we *don't* ask if this mimeInfo didn't come from
  1.1707 +    // our user configuration datastore and the user has said
  1.1708 +    // at some point in the distant past that they don't
  1.1709 +    // want to be asked.  The latter fact would have been
  1.1710 +    // stored in pref strings back in the old days.
  1.1711 +
  1.1712 +    bool mimeTypeIsInDatastore = false;
  1.1713 +    nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1.1714 +    if (handlerSvc)
  1.1715 +      handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
  1.1716 +    if (!handlerSvc || !mimeTypeIsInDatastore)
  1.1717 +    {
  1.1718 +      nsAutoCString MIMEType;
  1.1719 +      mMimeInfo->GetMIMEType(MIMEType);
  1.1720 +
  1.1721 +      if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
  1.1722 +      {
  1.1723 +        // Don't need to ask after all.
  1.1724 +        alwaysAsk = false;
  1.1725 +        // Make sure action matches pref (save to disk).
  1.1726 +        mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
  1.1727 +      }
  1.1728 +      else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get()))
  1.1729 +      {
  1.1730 +        // Don't need to ask after all.
  1.1731 +        alwaysAsk = false;
  1.1732 +      }
  1.1733 +    }
  1.1734 +  }
  1.1735 +
  1.1736 +  int32_t action = nsIMIMEInfo::saveToDisk;
  1.1737 +  mMimeInfo->GetPreferredAction( &action );
  1.1738 +
  1.1739 +  // OK, now check why we're here
  1.1740 +  if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
  1.1741 +    // Force asking if we're not saving.  See comment back when we fetched the
  1.1742 +    // alwaysAsk boolean for details.
  1.1743 +    alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
  1.1744 +  }
  1.1745 +
  1.1746 +  // if we were told that we _must_ save to disk without asking, all the stuff
  1.1747 +  // before this is irrelevant; override it
  1.1748 +  if (mForceSave) {
  1.1749 +    alwaysAsk = false;
  1.1750 +    action = nsIMIMEInfo::saveToDisk;
  1.1751 +  }
  1.1752 +  
  1.1753 +  if (alwaysAsk)
  1.1754 +  {
  1.1755 +    // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
  1.1756 +    mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
  1.1757 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1758 +
  1.1759 +    // this will create a reference cycle (the dialog holds a reference to us as
  1.1760 +    // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
  1.1761 +    rv = mDialog->Show( this, mWindowContext, mReason );
  1.1762 +
  1.1763 +    // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
  1.1764 +  }
  1.1765 +  else
  1.1766 +  {
  1.1767 +
  1.1768 +    // We need to do the save/open immediately, then.
  1.1769 +#ifdef XP_WIN
  1.1770 +    /* We need to see whether the file we've got here could be
  1.1771 +     * executable.  If it could, we had better not try to open it!
  1.1772 +     * We can skip this check, though, if we have a setting to open in a
  1.1773 +     * helper app.
  1.1774 +     * This code mirrors the code in
  1.1775 +     * nsExternalAppHandler::LaunchWithApplication so that what we
  1.1776 +     * test here is as close as possible to what will really be
  1.1777 +     * happening if we decide to execute
  1.1778 +     */
  1.1779 +    nsCOMPtr<nsIHandlerApp> prefApp;
  1.1780 +    mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
  1.1781 +    if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
  1.1782 +      nsCOMPtr<nsIFile> fileToTest;
  1.1783 +      GetTargetFile(getter_AddRefs(fileToTest));
  1.1784 +      if (fileToTest) {
  1.1785 +        bool isExecutable;
  1.1786 +        rv = fileToTest->IsExecutable(&isExecutable);
  1.1787 +        if (NS_FAILED(rv) || isExecutable) {  // checking NS_FAILED, because paranoia is good
  1.1788 +          action = nsIMIMEInfo::saveToDisk;
  1.1789 +        }
  1.1790 +      } else {   // Paranoia is good here too, though this really should not happen
  1.1791 +        NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
  1.1792 +        action = nsIMIMEInfo::saveToDisk;
  1.1793 +      }
  1.1794 +    }
  1.1795 +
  1.1796 +#endif
  1.1797 +    if (action == nsIMIMEInfo::useHelperApp ||
  1.1798 +        action == nsIMIMEInfo::useSystemDefault)
  1.1799 +    {
  1.1800 +        rv = LaunchWithApplication(nullptr, false);
  1.1801 +    }
  1.1802 +    else // Various unknown actions go here too
  1.1803 +    {
  1.1804 +        rv = SaveToDisk(nullptr, false);
  1.1805 +    }
  1.1806 +  }
  1.1807 +
  1.1808 +  return NS_OK;
  1.1809 +}
  1.1810 +
  1.1811 +// Convert error info into proper message text and send OnStatusChange
  1.1812 +// notification to the dialog progress listener or nsITransfer implementation.
  1.1813 +void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
  1.1814 +{
  1.1815 +    nsAutoString msgId;
  1.1816 +    switch(rv)
  1.1817 +    {
  1.1818 +    case NS_ERROR_OUT_OF_MEMORY:
  1.1819 +        // No memory
  1.1820 +        msgId.AssignLiteral("noMemory");
  1.1821 +        break;
  1.1822 +
  1.1823 +    case NS_ERROR_FILE_DISK_FULL:
  1.1824 +    case NS_ERROR_FILE_NO_DEVICE_SPACE:
  1.1825 +        // Out of space on target volume.
  1.1826 +        msgId.AssignLiteral("diskFull");
  1.1827 +        break;
  1.1828 +
  1.1829 +    case NS_ERROR_FILE_READ_ONLY:
  1.1830 +        // Attempt to write to read/only file.
  1.1831 +        msgId.AssignLiteral("readOnly");
  1.1832 +        break;
  1.1833 +
  1.1834 +    case NS_ERROR_FILE_ACCESS_DENIED:
  1.1835 +        if (type == kWriteError) {
  1.1836 +          // Attempt to write without sufficient permissions.
  1.1837 +#if defined(ANDROID)
  1.1838 +          // On Android (and Gonk), this means the SD card is present but
  1.1839 +          // unavailable (read-only).
  1.1840 +          msgId.AssignLiteral("SDAccessErrorCardReadOnly");
  1.1841 +#else
  1.1842 +          msgId.AssignLiteral("accessError");
  1.1843 +#endif
  1.1844 +        }
  1.1845 +        else
  1.1846 +        {
  1.1847 +          msgId.AssignLiteral("launchError");
  1.1848 +        }
  1.1849 +        break;
  1.1850 +
  1.1851 +    case NS_ERROR_FILE_NOT_FOUND:
  1.1852 +    case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
  1.1853 +    case NS_ERROR_FILE_UNRECOGNIZED_PATH:
  1.1854 +        // Helper app not found, let's verify this happened on launch
  1.1855 +        if (type == kLaunchError) {
  1.1856 +          msgId.AssignLiteral("helperAppNotFound");
  1.1857 +          break;
  1.1858 +        }
  1.1859 +#if defined(ANDROID)
  1.1860 +        else if (type == kWriteError) {
  1.1861 +          // On Android (and Gonk), this means the SD card is missing (not in
  1.1862 +          // SD slot).
  1.1863 +          msgId.AssignLiteral("SDAccessErrorCardMissing");
  1.1864 +          break;
  1.1865 +        }
  1.1866 +#endif
  1.1867 +        // fall through
  1.1868 +
  1.1869 +    default:
  1.1870 +        // Generic read/write/launch error message.
  1.1871 +        switch(type)
  1.1872 +        {
  1.1873 +        case kReadError:
  1.1874 +          msgId.AssignLiteral("readError");
  1.1875 +          break;
  1.1876 +        case kWriteError:
  1.1877 +          msgId.AssignLiteral("writeError");
  1.1878 +          break;
  1.1879 +        case kLaunchError:
  1.1880 +          msgId.AssignLiteral("launchError");
  1.1881 +          break;
  1.1882 +        }
  1.1883 +        break;
  1.1884 +    }
  1.1885 +    PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1.1886 +        ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
  1.1887 +         NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
  1.1888 +    PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1.1889 +        ("       path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
  1.1890 +
  1.1891 +    // Get properties file bundle and extract status string.
  1.1892 +    nsCOMPtr<nsIStringBundleService> stringService =
  1.1893 +        mozilla::services::GetStringBundleService();
  1.1894 +    if (stringService)
  1.1895 +    {
  1.1896 +        nsCOMPtr<nsIStringBundle> bundle;
  1.1897 +        if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
  1.1898 +        {
  1.1899 +            nsXPIDLString msgText;
  1.1900 +            const char16_t *strings[] = { path.get() };
  1.1901 +            if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
  1.1902 +            {
  1.1903 +              if (mDialogProgressListener)
  1.1904 +              {
  1.1905 +                // We have a listener, let it handle the error.
  1.1906 +                mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
  1.1907 +              } else if (mTransfer) {
  1.1908 +                mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
  1.1909 +              }
  1.1910 +              else
  1.1911 +              if (XRE_GetProcessType() == GeckoProcessType_Default) {
  1.1912 +                // We don't have a listener.  Simply show the alert ourselves.
  1.1913 +                nsresult qiRv;
  1.1914 +                nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext, &qiRv));
  1.1915 +                nsXPIDLString title;
  1.1916 +                bundle->FormatStringFromName(MOZ_UTF16("title"),
  1.1917 +                                             strings,
  1.1918 +                                             1,
  1.1919 +                                             getter_Copies(title));
  1.1920 +
  1.1921 +                PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
  1.1922 +                       ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
  1.1923 +                       mWindowContext.get(),
  1.1924 +                       prompter.get(),
  1.1925 +                       qiRv,
  1.1926 +                       NS_ConvertUTF16toUTF8(title).get(),
  1.1927 +                       NS_ConvertUTF16toUTF8(msgText).get()));
  1.1928 +
  1.1929 +                // If we didn't have a prompter we will try and get a window
  1.1930 +                // instead, get it's docshell and use it to alert the user.
  1.1931 +                if (!prompter)
  1.1932 +                {
  1.1933 +                  nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(mWindowContext));
  1.1934 +                  if (!window || !window->GetDocShell())
  1.1935 +                  {
  1.1936 +                    return;
  1.1937 +                  }
  1.1938 +
  1.1939 +                  prompter = do_GetInterface(window->GetDocShell(), &qiRv);
  1.1940 +
  1.1941 +                  PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
  1.1942 +                         ("No prompter from mWindowContext, using DocShell, " \
  1.1943 +                          "window=0x%p, docShell=0x%p, " \
  1.1944 +                          "prompter=0x%p, qi rv=0x%08X",
  1.1945 +                          window.get(),
  1.1946 +                          window->GetDocShell(),
  1.1947 +                          prompter.get(),
  1.1948 +                          qiRv));
  1.1949 +
  1.1950 +                  // If we still don't have a prompter, there's nothing else we
  1.1951 +                  // can do so just return.
  1.1952 +                  if (!prompter)
  1.1953 +                  {
  1.1954 +                    PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
  1.1955 +                           ("No prompter from DocShell, no way to alert user"));
  1.1956 +                    return;
  1.1957 +                  }
  1.1958 +                }
  1.1959 +
  1.1960 +                // We should always have a prompter at this point.
  1.1961 +                prompter->Alert(title, msgText);
  1.1962 +              }
  1.1963 +            }
  1.1964 +        }
  1.1965 +    }
  1.1966 +}
  1.1967 +
  1.1968 +NS_IMETHODIMP
  1.1969 +nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
  1.1970 +                                      nsIInputStream * inStr,
  1.1971 +                                      uint64_t sourceOffset, uint32_t count)
  1.1972 +{
  1.1973 +  nsresult rv = NS_OK;
  1.1974 +  // first, check to see if we've been canceled....
  1.1975 +  if (mCanceled || !mSaver) // then go cancel our underlying channel too
  1.1976 +    return request->Cancel(NS_BINDING_ABORTED);
  1.1977 +
  1.1978 +  // read the data out of the stream and write it to the temp file.
  1.1979 +  if (count > 0)
  1.1980 +  {
  1.1981 +    mProgress += count;
  1.1982 +
  1.1983 +    nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
  1.1984 +    rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
  1.1985 +    if (NS_SUCCEEDED(rv))
  1.1986 +    {
  1.1987 +      // Send progress notification.
  1.1988 +      if (mTransfer) {
  1.1989 +        mTransfer->OnProgressChange64(nullptr, request, mProgress,
  1.1990 +                                      mContentLength, mProgress,
  1.1991 +                                      mContentLength);
  1.1992 +      }
  1.1993 +    }
  1.1994 +    else
  1.1995 +    {
  1.1996 +      // An error occurred, notify listener.
  1.1997 +      nsAutoString tempFilePath;
  1.1998 +      if (mTempFile)
  1.1999 +        mTempFile->GetPath(tempFilePath);
  1.2000 +      SendStatusChange(kReadError, rv, request, tempFilePath);
  1.2001 +
  1.2002 +      // Cancel the download.
  1.2003 +      Cancel(rv);
  1.2004 +    }
  1.2005 +  }
  1.2006 +  return rv;
  1.2007 +}
  1.2008 +
  1.2009 +NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
  1.2010 +                                                  nsresult aStatus)
  1.2011 +{
  1.2012 +  LOG(("nsExternalAppHandler::OnStopRequest\n"
  1.2013 +       "  mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
  1.2014 +       mCanceled, mTransfer.get(), aStatus));
  1.2015 +
  1.2016 +  mStopRequestIssued = true;
  1.2017 +
  1.2018 +  // Cancel if the request did not complete successfully.
  1.2019 +  if (!mCanceled && NS_FAILED(aStatus))
  1.2020 +  {
  1.2021 +    // Send error notification.
  1.2022 +    nsAutoString tempFilePath;
  1.2023 +    if (mTempFile)
  1.2024 +      mTempFile->GetPath(tempFilePath);
  1.2025 +    SendStatusChange( kReadError, aStatus, request, tempFilePath );
  1.2026 +
  1.2027 +    Cancel(aStatus);
  1.2028 +  }
  1.2029 +
  1.2030 +  // first, check to see if we've been canceled....
  1.2031 +  if (mCanceled || !mSaver)
  1.2032 +    return NS_OK;
  1.2033 +
  1.2034 +  return mSaver->Finish(NS_OK);
  1.2035 +}
  1.2036 +
  1.2037 +NS_IMETHODIMP
  1.2038 +nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
  1.2039 +                                     nsIFile *aTarget)
  1.2040 +{
  1.2041 +  return NS_OK;
  1.2042 +}
  1.2043 +
  1.2044 +NS_IMETHODIMP
  1.2045 +nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
  1.2046 +                                     nsresult aStatus)
  1.2047 +{
  1.2048 +  LOG(("nsExternalAppHandler::OnSaveComplete\n"
  1.2049 +       "  aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
  1.2050 +       aSaver, aStatus, mCanceled, mTransfer.get()));
  1.2051 +
  1.2052 +  if (!mCanceled) {
  1.2053 +    // Save the hash
  1.2054 +    (void)mSaver->GetSha256Hash(mHash);
  1.2055 +    (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
  1.2056 +    // Free the reference that the saver keeps on us, even if we couldn't get
  1.2057 +    // the hash.
  1.2058 +    mSaver = nullptr;
  1.2059 +  
  1.2060 +    if (NS_FAILED(aStatus)) {
  1.2061 +      nsAutoString path;
  1.2062 +      mTempFile->GetPath(path);
  1.2063 +
  1.2064 +      // It may happen when e10s is enabled that there will be no transfer
  1.2065 +      // object available to communicate status as expected by the system.
  1.2066 +      // Let's try and create a temporary transfer object to take care of this
  1.2067 +      // for us, we'll fall back to using the prompt service if we absolutely
  1.2068 +      // have to.
  1.2069 +      if (!mTransfer) {
  1.2070 +        nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  1.2071 +        // We don't care if this fails.
  1.2072 +        CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
  1.2073 +      }
  1.2074 +
  1.2075 +      SendStatusChange(kWriteError, aStatus, nullptr, path);
  1.2076 +      if (!mCanceled)
  1.2077 +        Cancel(aStatus);
  1.2078 +      return NS_OK;
  1.2079 +    }
  1.2080 +  }
  1.2081 +
  1.2082 +  // Notify the transfer object that we are done if the user has chosen an
  1.2083 +  // action. If the user hasn't chosen an action, the progress listener
  1.2084 +  // (nsITransfer) will be notified in CreateTransfer.
  1.2085 +  if (mTransfer) {
  1.2086 +    NotifyTransfer(aStatus);
  1.2087 +  }
  1.2088 +
  1.2089 +  return NS_OK;
  1.2090 +}
  1.2091 +
  1.2092 +void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
  1.2093 +{
  1.2094 +  MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
  1.2095 +  MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
  1.2096 +
  1.2097 +  LOG(("Notifying progress listener"));
  1.2098 +
  1.2099 +  if (NS_SUCCEEDED(aStatus)) {
  1.2100 +    (void)mTransfer->SetSha256Hash(mHash);
  1.2101 +    (void)mTransfer->SetSignatureInfo(mSignatureInfo);
  1.2102 +    (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
  1.2103 +      mContentLength, mProgress, mContentLength);
  1.2104 +  }
  1.2105 +
  1.2106 +  (void)mTransfer->OnStateChange(nullptr, nullptr,
  1.2107 +    nsIWebProgressListener::STATE_STOP |
  1.2108 +    nsIWebProgressListener::STATE_IS_REQUEST |
  1.2109 +    nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
  1.2110 +
  1.2111 +  // This nsITransfer object holds a reference to us (we are its observer), so
  1.2112 +  // we need to release the reference to break a reference cycle (and therefore
  1.2113 +  // to prevent leaking).  We do this even if the previous calls failed.
  1.2114 +  mTransfer = nullptr;
  1.2115 +}
  1.2116 +
  1.2117 +NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
  1.2118 +{
  1.2119 +  *aMIMEInfo = mMimeInfo;
  1.2120 +  NS_ADDREF(*aMIMEInfo);
  1.2121 +  return NS_OK;
  1.2122 +}
  1.2123 +
  1.2124 +NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
  1.2125 +{
  1.2126 +  NS_ENSURE_ARG(aSourceURI);
  1.2127 +  *aSourceURI = mSourceUrl;
  1.2128 +  NS_IF_ADDREF(*aSourceURI);
  1.2129 +  return NS_OK;
  1.2130 +}
  1.2131 +
  1.2132 +NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
  1.2133 +{
  1.2134 +  aSuggestedFileName = mSuggestedFileName;
  1.2135 +  return NS_OK;
  1.2136 +}
  1.2137 +
  1.2138 +nsresult nsExternalAppHandler::CreateTransfer()
  1.2139 +{
  1.2140 +  LOG(("nsExternalAppHandler::CreateTransfer"));
  1.2141 +
  1.2142 +  MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
  1.2143 +  // We are back from the helper app dialog (where the user chooses to save or
  1.2144 +  // open), but we aren't done processing the load. in this case, throw up a
  1.2145 +  // progress dialog so the user can see what's going on.
  1.2146 +  // Also, release our reference to mDialog. We don't need it anymore, and we
  1.2147 +  // need to break the reference cycle.
  1.2148 +  mDialog = nullptr;
  1.2149 +  if (!mDialogProgressListener) {
  1.2150 +    NS_WARNING("The dialog should nullify the dialog progress listener");
  1.2151 +  }
  1.2152 +  nsresult rv;
  1.2153 +
  1.2154 +  // We must be able to create an nsITransfer object. If not, it doesn't matter
  1.2155 +  // much that we can't launch the helper application or save to disk. Work on
  1.2156 +  // a local copy rather than mTransfer until we know we succeeded, to make it
  1.2157 +  // clearer that this function is re-entrant.
  1.2158 +  nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
  1.2159 +    NS_TRANSFER_CONTRACTID, &rv);
  1.2160 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2161 +
  1.2162 +  // Initialize the download
  1.2163 +  nsCOMPtr<nsIURI> target;
  1.2164 +  rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
  1.2165 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2166 +
  1.2167 +  nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  1.2168 +
  1.2169 +  rv = transfer->Init(mSourceUrl, target, EmptyString(),
  1.2170 +                       mMimeInfo, mTimeDownloadStarted, mTempFile, this,
  1.2171 +                       channel && NS_UsePrivateBrowsing(channel));
  1.2172 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2173 +
  1.2174 +  // Now let's add the download to history
  1.2175 +  nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
  1.2176 +  if (dh) {
  1.2177 +    nsCOMPtr<nsIURI> referrer;
  1.2178 +    nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
  1.2179 +    if (channel) {
  1.2180 +      NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
  1.2181 +    }
  1.2182 +
  1.2183 +    if (channel && !NS_UsePrivateBrowsing(channel)) {
  1.2184 +      dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
  1.2185 +    }
  1.2186 +  }
  1.2187 +
  1.2188 +  // If we were cancelled since creating the transfer, just return. It is
  1.2189 +  // always ok to return NS_OK if we are cancelled. Callers of this function
  1.2190 +  // must call Cancel if CreateTransfer fails, but there's no need to cancel
  1.2191 +  // twice.
  1.2192 +  if (mCanceled) {
  1.2193 +    return NS_OK;
  1.2194 +  }
  1.2195 +  rv = transfer->OnStateChange(nullptr, mRequest,
  1.2196 +    nsIWebProgressListener::STATE_START |
  1.2197 +    nsIWebProgressListener::STATE_IS_REQUEST |
  1.2198 +    nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
  1.2199 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2200 +
  1.2201 +  if (mCanceled) {
  1.2202 +    return NS_OK;
  1.2203 +  }
  1.2204 +
  1.2205 +  mRequest = nullptr;
  1.2206 +  // Finally, save the transfer to mTransfer.
  1.2207 +  mTransfer = transfer;
  1.2208 +  transfer = nullptr;
  1.2209 +
  1.2210 +  // While we were bringing up the progress dialog, we actually finished
  1.2211 +  // processing the url. If that's the case then mStopRequestIssued will be
  1.2212 +  // true and OnSaveComplete has been called.
  1.2213 +  if (mStopRequestIssued && !mSaver && mTransfer) {
  1.2214 +    NotifyTransfer(NS_OK);
  1.2215 +  }
  1.2216 +
  1.2217 +  return rv;
  1.2218 +}
  1.2219 +
  1.2220 +nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
  1.2221 +{
  1.2222 +  nsresult rv;
  1.2223 +  nsCOMPtr<nsITransfer> transfer =
  1.2224 +    do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
  1.2225 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2226 +
  1.2227 +  // If we don't have a download directory we're kinda screwed but it's OK
  1.2228 +  // we'll still report the error via the prompter.
  1.2229 +  nsCOMPtr<nsIFile> pseudoFile;
  1.2230 +  rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
  1.2231 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2232 +
  1.2233 +  // Append the default suggested filename. If the user restarts the transfer
  1.2234 +  // we will re-trigger a filename check anyway to ensure that it is unique.
  1.2235 +  rv = pseudoFile->Append(mSuggestedFileName);
  1.2236 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2237 +
  1.2238 +  nsCOMPtr<nsIURI> pseudoTarget;
  1.2239 +  rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
  1.2240 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2241 +
  1.2242 +  rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
  1.2243 +                      mMimeInfo, mTimeDownloadStarted, nullptr, this,
  1.2244 +                      aIsPrivateBrowsing);
  1.2245 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2246 +
  1.2247 +  // Our failed transfer is ready.
  1.2248 +  mTransfer = transfer.forget();
  1.2249 +
  1.2250 +  return NS_OK;
  1.2251 +}
  1.2252 +
  1.2253 +nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
  1.2254 +{
  1.2255 +  if (aFile)
  1.2256 +    ContinueSave(aFile);
  1.2257 +  else
  1.2258 +    Cancel(NS_BINDING_ABORTED);
  1.2259 +
  1.2260 +  return NS_OK;
  1.2261 +}
  1.2262 +
  1.2263 +void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
  1.2264 +{
  1.2265 +  // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
  1.2266 +  // Convert to use file picker? No, then embeddors could not do any sort of
  1.2267 +  // "AutoDownload" w/o showing a prompt
  1.2268 +  nsresult rv = NS_OK;
  1.2269 +  if (!mDialog)
  1.2270 +  {
  1.2271 +    // Get helper app launcher dialog.
  1.2272 +    mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
  1.2273 +    if (rv != NS_OK) {
  1.2274 +      Cancel(NS_BINDING_ABORTED);
  1.2275 +      return;
  1.2276 +    }
  1.2277 +  }
  1.2278 +
  1.2279 +  // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
  1.2280 +  // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
  1.2281 +
  1.2282 +  // Now, be sure to keep |this| alive, and the dialog
  1.2283 +  // If we don't do this, users that close the helper app dialog while the file
  1.2284 +  // picker is up would cause Cancel() to be called, and the dialog would be
  1.2285 +  // released, which would release this object too, which would crash.
  1.2286 +  // See Bug 249143
  1.2287 +  nsIFile* fileToUse;
  1.2288 +  nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
  1.2289 +  nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
  1.2290 +  rv = mDialog->PromptForSaveToFile(this,
  1.2291 +                                    mWindowContext,
  1.2292 +                                    aDefaultFile.get(),
  1.2293 +                                    aFileExtension.get(),
  1.2294 +                                    mForceSave, &fileToUse);
  1.2295 +
  1.2296 +  if (rv == NS_ERROR_NOT_AVAILABLE) {
  1.2297 +    // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
  1.2298 +    rv = mDialog->PromptForSaveToFileAsync(this, 
  1.2299 +                                           mWindowContext,
  1.2300 +                                           aDefaultFile.get(),
  1.2301 +                                           aFileExtension.get(),
  1.2302 +                                           mForceSave);
  1.2303 +  } else {
  1.2304 +    SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
  1.2305 +  }
  1.2306 +}
  1.2307 +
  1.2308 +// SaveToDisk should only be called by the helper app dialog which allows
  1.2309 +// the user to say launch with application or save to disk. It doesn't actually
  1.2310 +// perform the save, it just prompts for the destination file name.
  1.2311 +NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
  1.2312 +{
  1.2313 +  if (mCanceled)
  1.2314 +    return NS_OK;
  1.2315 +
  1.2316 +  mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
  1.2317 +
  1.2318 +  if (!aNewFileLocation) {
  1.2319 +    if (mSuggestedFileName.IsEmpty())
  1.2320 +      RequestSaveDestination(mTempLeafName, mTempFileExtension);
  1.2321 +    else
  1.2322 +    {
  1.2323 +      nsAutoString fileExt;
  1.2324 +      int32_t pos = mSuggestedFileName.RFindChar('.');
  1.2325 +      if (pos >= 0)
  1.2326 +        mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
  1.2327 +      if (fileExt.IsEmpty())
  1.2328 +        fileExt = mTempFileExtension;
  1.2329 +
  1.2330 +      RequestSaveDestination(mSuggestedFileName, fileExt);
  1.2331 +    }
  1.2332 +  } else {
  1.2333 +    ContinueSave(aNewFileLocation);
  1.2334 +  }
  1.2335 +
  1.2336 +  return NS_OK;
  1.2337 +}
  1.2338 +nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
  1.2339 +{
  1.2340 +  if (mCanceled)
  1.2341 +    return NS_OK;
  1.2342 +
  1.2343 +  NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
  1.2344 +
  1.2345 +  nsresult rv = NS_OK;
  1.2346 +  nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
  1.2347 +  mFinalFileDestination = do_QueryInterface(fileToUse);
  1.2348 +
  1.2349 +  // Move what we have in the final directory, but append .part
  1.2350 +  // to it, to indicate that it's unfinished.  Do not call SetTarget on the
  1.2351 +  // saver if we are done (Finish has been called) but OnSaverComplete has not
  1.2352 +  // been called.
  1.2353 +  if (mFinalFileDestination && mSaver && !mStopRequestIssued)
  1.2354 +  {
  1.2355 +    nsCOMPtr<nsIFile> movedFile;
  1.2356 +    mFinalFileDestination->Clone(getter_AddRefs(movedFile));
  1.2357 +    if (movedFile) {
  1.2358 +      // Get the old leaf name and append .part to it
  1.2359 +      nsAutoString name;
  1.2360 +      mFinalFileDestination->GetLeafName(name);
  1.2361 +      name.AppendLiteral(".part");
  1.2362 +      movedFile->SetLeafName(name);
  1.2363 +
  1.2364 +      rv = mSaver->SetTarget(movedFile, true);
  1.2365 +      if (NS_FAILED(rv)) {
  1.2366 +        nsAutoString path;
  1.2367 +        mTempFile->GetPath(path);
  1.2368 +        SendStatusChange(kWriteError, rv, nullptr, path);
  1.2369 +        Cancel(rv);
  1.2370 +        return NS_OK;
  1.2371 +      }
  1.2372 +
  1.2373 +      mTempFile = movedFile;
  1.2374 +    }
  1.2375 +  }
  1.2376 +
  1.2377 +  // The helper app dialog has told us what to do and we have a final file
  1.2378 +  // destination.
  1.2379 +  rv = CreateTransfer();
  1.2380 +  // If we fail to create the transfer, Cancel.
  1.2381 +  if (NS_FAILED(rv)) {
  1.2382 +    Cancel(rv);
  1.2383 +    return rv;
  1.2384 +  }
  1.2385 +
  1.2386 +  // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
  1.2387 +  // if there is one. We don't want to do this before the save as dialog goes away because this dialog
  1.2388 +  // is modal and we do bad things if you try to load a web page in the underlying window while a modal
  1.2389 +  // dialog is still up.
  1.2390 +  ProcessAnyRefreshTags();
  1.2391 +
  1.2392 +  return NS_OK;
  1.2393 +}
  1.2394 +
  1.2395 +
  1.2396 +// LaunchWithApplication should only be called by the helper app dialog which
  1.2397 +// allows the user to say launch with application or save to disk. It doesn't
  1.2398 +// actually perform launch with application.
  1.2399 +NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
  1.2400 +{
  1.2401 +  if (mCanceled)
  1.2402 +    return NS_OK;
  1.2403 +
  1.2404 +  // user has chosen to launch using an application, fire any refresh tags now...
  1.2405 +  ProcessAnyRefreshTags(); 
  1.2406 +  
  1.2407 +  if (mMimeInfo && aApplication) {
  1.2408 +    PlatformLocalHandlerApp_t *handlerApp =
  1.2409 +      new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
  1.2410 +    mMimeInfo->SetPreferredApplicationHandler(handlerApp);
  1.2411 +  }
  1.2412 +
  1.2413 +  // Now check if the file is local, in which case we won't bother with saving
  1.2414 +  // it to a temporary directory and just launch it from where it is
  1.2415 +  nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
  1.2416 +  if (fileUrl && mIsFileChannel)
  1.2417 +  {
  1.2418 +    Cancel(NS_BINDING_ABORTED);
  1.2419 +    nsCOMPtr<nsIFile> file;
  1.2420 +    nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
  1.2421 +
  1.2422 +    if (NS_SUCCEEDED(rv))
  1.2423 +    {
  1.2424 +      rv = mMimeInfo->LaunchWithFile(file);
  1.2425 +      if (NS_SUCCEEDED(rv))
  1.2426 +        return NS_OK;
  1.2427 +    }
  1.2428 +    nsAutoString path;
  1.2429 +    if (file)
  1.2430 +      file->GetPath(path);
  1.2431 +    // If we get here, an error happened
  1.2432 +    SendStatusChange(kLaunchError, rv, nullptr, path);
  1.2433 +    return rv;
  1.2434 +  }
  1.2435 +
  1.2436 +  // Now that the user has elected to launch the downloaded file with a helper
  1.2437 +  // app, we're justified in removing the 'salted' name.  We'll rename to what
  1.2438 +  // was specified in mSuggestedFileName after the download is done prior to
  1.2439 +  // launching the helper app.  So that any existing file of that name won't be
  1.2440 +  // overwritten we call CreateUnique().  Also note that we use the same
  1.2441 +  // directory as originally downloaded so nsDownload can rename in place
  1.2442 +  // later.
  1.2443 +  nsCOMPtr<nsIFile> fileToUse;
  1.2444 +  (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
  1.2445 +
  1.2446 +  if (mSuggestedFileName.IsEmpty())
  1.2447 +  {
  1.2448 +    // Keep using the leafname of the temp file, since we're just starting a helper
  1.2449 +    mSuggestedFileName = mTempLeafName;
  1.2450 +  }
  1.2451 +
  1.2452 +#ifdef XP_WIN
  1.2453 +  fileToUse->Append(mSuggestedFileName + mTempFileExtension);
  1.2454 +#else
  1.2455 +  fileToUse->Append(mSuggestedFileName);  
  1.2456 +#endif
  1.2457 +
  1.2458 +  nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
  1.2459 +  if(NS_SUCCEEDED(rv))
  1.2460 +  {
  1.2461 +    mFinalFileDestination = do_QueryInterface(fileToUse);
  1.2462 +    // launch the progress window now that the user has picked the desired action.
  1.2463 +    rv = CreateTransfer();
  1.2464 +    if (NS_FAILED(rv)) {
  1.2465 +      Cancel(rv);
  1.2466 +    }
  1.2467 +  }
  1.2468 +  else
  1.2469 +  {
  1.2470 +    // Cancel the download and report an error.  We do not want to end up in
  1.2471 +    // a state where it appears that we have a normal download that is
  1.2472 +    // pointing to a file that we did not actually create.
  1.2473 +    nsAutoString path;
  1.2474 +    mTempFile->GetPath(path);
  1.2475 +    SendStatusChange(kWriteError, rv, nullptr, path);
  1.2476 +    Cancel(rv);
  1.2477 +  }
  1.2478 +  return rv;
  1.2479 +}
  1.2480 +
  1.2481 +NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
  1.2482 +{
  1.2483 +  NS_ENSURE_ARG(NS_FAILED(aReason));
  1.2484 +
  1.2485 +  if (mCanceled) {
  1.2486 +    return NS_OK;
  1.2487 +  }
  1.2488 +  mCanceled = true;
  1.2489 +
  1.2490 +  if (mSaver) {
  1.2491 +    // We are still writing to the target file.  Give the saver a chance to
  1.2492 +    // close the target file, then notify the transfer object if necessary in
  1.2493 +    // the OnSaveComplete callback.
  1.2494 +    mSaver->Finish(aReason);
  1.2495 +    mSaver = nullptr;
  1.2496 +  } else {
  1.2497 +    if (mStopRequestIssued && mTempFile) {
  1.2498 +      // This branch can only happen when the user cancels the helper app dialog
  1.2499 +      // when the request has completed. The temp file has to be removed here,
  1.2500 +      // because mSaver has been released at that time with the temp file left.
  1.2501 +      (void)mTempFile->Remove(false);
  1.2502 +    }
  1.2503 +
  1.2504 +    // Notify the transfer object that the download has been canceled, if the
  1.2505 +    // user has already chosen an action and we didn't notify already.
  1.2506 +    if (mTransfer) {
  1.2507 +      NotifyTransfer(aReason);
  1.2508 +    }
  1.2509 +  }
  1.2510 +
  1.2511 +  // Break our reference cycle with the helper app dialog (set up in
  1.2512 +  // OnStartRequest)
  1.2513 +  mDialog = nullptr;
  1.2514 +
  1.2515 +  mRequest = nullptr;
  1.2516 +
  1.2517 +  // Release the listener, to break the reference cycle with it (we are the
  1.2518 +  // observer of the listener).
  1.2519 +  mDialogProgressListener = nullptr;
  1.2520 +
  1.2521 +  return NS_OK;
  1.2522 +}
  1.2523 +
  1.2524 +void nsExternalAppHandler::ProcessAnyRefreshTags()
  1.2525 +{
  1.2526 +   // one last thing, try to see if the original window context supports a refresh interface...
  1.2527 +   // Sometimes, when you download content that requires an external handler, there is
  1.2528 +   // a refresh header associated with the download. This refresh header points to a page
  1.2529 +   // the content provider wants the user to see after they download the content. How do we
  1.2530 +   // pass this refresh information back to the caller? For now, try to get the refresh URI
  1.2531 +   // interface. If the window context where the request originated came from supports this
  1.2532 +   // then we can force it to process the refresh information (if there is any) from this channel.
  1.2533 +   if (mWindowContext && mOriginalChannel)
  1.2534 +   {
  1.2535 +     nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext));
  1.2536 +     if (refreshHandler) {
  1.2537 +        refreshHandler->SetupRefreshURI(mOriginalChannel);
  1.2538 +     }
  1.2539 +     mOriginalChannel = nullptr;
  1.2540 +   }
  1.2541 +}
  1.2542 +
  1.2543 +bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
  1.2544 +{
  1.2545 +  // Search the obsolete pref strings.
  1.2546 +  nsAdoptingCString prefCString = Preferences::GetCString(prefName);
  1.2547 +  if (prefCString.IsEmpty()) {
  1.2548 +    // Default is true, if not found in the pref string.
  1.2549 +    return true;
  1.2550 +  }
  1.2551 +
  1.2552 +  NS_UnescapeURL(prefCString);
  1.2553 +  nsACString::const_iterator start, end;
  1.2554 +  prefCString.BeginReading(start);
  1.2555 +  prefCString.EndReading(end);
  1.2556 +  return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
  1.2557 +                                        start, end);
  1.2558 +}
  1.2559 +
  1.2560 +nsresult nsExternalAppHandler::MaybeCloseWindow()
  1.2561 +{
  1.2562 +  nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWindowContext);
  1.2563 +  NS_ENSURE_STATE(window);
  1.2564 +
  1.2565 +  if (mShouldCloseWindow) {
  1.2566 +    // Reset the window context to the opener window so that the dependent
  1.2567 +    // dialogs have a parent
  1.2568 +    nsCOMPtr<nsIDOMWindow> opener;
  1.2569 +    window->GetOpener(getter_AddRefs(opener));
  1.2570 +
  1.2571 +    bool isClosed;
  1.2572 +    if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) {
  1.2573 +      mWindowContext = do_GetInterface(opener);
  1.2574 +
  1.2575 +      // Now close the old window.  Do it on a timer so that we don't run
  1.2576 +      // into issues trying to close the window before it has fully opened.
  1.2577 +      NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
  1.2578 +      mTimer = do_CreateInstance("@mozilla.org/timer;1");
  1.2579 +      if (!mTimer) {
  1.2580 +        return NS_ERROR_FAILURE;
  1.2581 +      }
  1.2582 +
  1.2583 +      mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
  1.2584 +      mWindowToClose = window;
  1.2585 +    }
  1.2586 +  }
  1.2587 +
  1.2588 +  return NS_OK;
  1.2589 +}
  1.2590 +
  1.2591 +NS_IMETHODIMP
  1.2592 +nsExternalAppHandler::Notify(nsITimer* timer)
  1.2593 +{
  1.2594 +  NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
  1.2595 +
  1.2596 +  mWindowToClose->Close();
  1.2597 +  mWindowToClose = nullptr;
  1.2598 +  mTimer = nullptr;
  1.2599 +
  1.2600 +  return NS_OK;
  1.2601 +}
  1.2602 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1.2603 +// The following section contains our nsIMIMEService implementation and related methods.
  1.2604 +//
  1.2605 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1.2606 +
  1.2607 +// nsIMIMEService methods
  1.2608 +NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval) 
  1.2609 +{
  1.2610 +  NS_PRECONDITION(!aMIMEType.IsEmpty() ||
  1.2611 +                  !aFileExt.IsEmpty(), 
  1.2612 +                  "Give me something to work with");
  1.2613 +  LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
  1.2614 +        PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
  1.2615 +
  1.2616 +  *_retval = nullptr;
  1.2617 +
  1.2618 +  // OK... we need a type. Get one.
  1.2619 +  nsAutoCString typeToUse(aMIMEType);
  1.2620 +  if (typeToUse.IsEmpty()) {
  1.2621 +    nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
  1.2622 +    if (NS_FAILED(rv))
  1.2623 +      return NS_ERROR_NOT_AVAILABLE;
  1.2624 +  }
  1.2625 +
  1.2626 +  // We promise to only send lower case mime types to the OS
  1.2627 +  ToLowerCase(typeToUse);
  1.2628 +
  1.2629 +  // (1) Ask the OS for a mime info
  1.2630 +  bool found;
  1.2631 +  *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
  1.2632 +  LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
  1.2633 +  // If we got no mimeinfo, something went wrong. Probably lack of memory.
  1.2634 +  if (!*_retval)
  1.2635 +    return NS_ERROR_OUT_OF_MEMORY;
  1.2636 +
  1.2637 +  // (2) Now, let's see if we can find something in our datastore
  1.2638 +  // This will not overwrite the OS information that interests us
  1.2639 +  // (i.e. default application, default app. description)
  1.2640 +  nsresult rv;
  1.2641 +  nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1.2642 +  if (handlerSvc) {
  1.2643 +    bool hasHandler = false;
  1.2644 +    (void) handlerSvc->Exists(*_retval, &hasHandler);
  1.2645 +    if (hasHandler) {
  1.2646 +      rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
  1.2647 +      LOG(("Data source: Via type: retval 0x%08x\n", rv));
  1.2648 +    } else {
  1.2649 +      rv = NS_ERROR_NOT_AVAILABLE;
  1.2650 +    }
  1.2651 + 
  1.2652 +    found = found || NS_SUCCEEDED(rv);
  1.2653 +
  1.2654 +    if (!found || NS_FAILED(rv)) {
  1.2655 +      // No type match, try extension match
  1.2656 +      if (!aFileExt.IsEmpty()) {
  1.2657 +        nsAutoCString overrideType;
  1.2658 +        rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
  1.2659 +        if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
  1.2660 +          // We can't check handlerSvc->Exists() here, because we have a
  1.2661 +          // overideType. That's ok, it just results in some console noise.
  1.2662 +          // (If there's no handler for the override type, it throws)
  1.2663 +          rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
  1.2664 +          LOG(("Data source: Via ext: retval 0x%08x\n", rv));
  1.2665 +          found = found || NS_SUCCEEDED(rv);
  1.2666 +        }
  1.2667 +      }
  1.2668 +    }
  1.2669 +  }
  1.2670 +
  1.2671 +  // (3) No match yet. Ask extras.
  1.2672 +  if (!found) {
  1.2673 +    rv = NS_ERROR_FAILURE;
  1.2674 +#ifdef XP_WIN
  1.2675 +    /* XXX Gross hack to wallpaper over the most common Win32
  1.2676 +     * extension issues caused by the fix for bug 116938.  See bug
  1.2677 +     * 120327, comment 271 for why this is needed.  Not even sure we
  1.2678 +     * want to remove this once we have fixed all this stuff to work
  1.2679 +     * right; any info we get from extras on this type is pretty much
  1.2680 +     * useless....
  1.2681 +     */
  1.2682 +    if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
  1.2683 +#endif
  1.2684 +      rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
  1.2685 +    LOG(("Searched extras (by type), rv 0x%08X\n", rv));
  1.2686 +    // If that didn't work out, try file extension from extras
  1.2687 +    if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
  1.2688 +      rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
  1.2689 +      LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
  1.2690 +    }
  1.2691 +    // If that still didn't work, set the file description to "ext File"
  1.2692 +    if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
  1.2693 +      // XXXzpao This should probably be localized
  1.2694 +      nsAutoCString desc(aFileExt);
  1.2695 +      desc.Append(" File");
  1.2696 +      (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
  1.2697 +      LOG(("Falling back to 'File' file description\n"));
  1.2698 +    }
  1.2699 +  }
  1.2700 +
  1.2701 +  // Finally, check if we got a file extension and if yes, if it is an
  1.2702 +  // extension on the mimeinfo, in which case we want it to be the primary one
  1.2703 +  if (!aFileExt.IsEmpty()) {
  1.2704 +    bool matches = false;
  1.2705 +    (*_retval)->ExtensionExists(aFileExt, &matches);
  1.2706 +    LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
  1.2707 +    if (matches)
  1.2708 +      (*_retval)->SetPrimaryExtension(aFileExt);
  1.2709 +  }
  1.2710 +
  1.2711 +#ifdef PR_LOGGING
  1.2712 +  if (LOG_ENABLED()) {
  1.2713 +    nsAutoCString type;
  1.2714 +    (*_retval)->GetMIMEType(type);
  1.2715 +
  1.2716 +    nsAutoCString ext;
  1.2717 +    (*_retval)->GetPrimaryExtension(ext);
  1.2718 +    LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
  1.2719 +  }
  1.2720 +#endif
  1.2721 +
  1.2722 +  return NS_OK;
  1.2723 +}
  1.2724 +
  1.2725 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType) 
  1.2726 +{
  1.2727 +  // OK. We want to try the following sources of mimetype information, in this order:
  1.2728 +  // 1. defaultMimeEntries array
  1.2729 +  // 2. User-set preferences (managed by the handler service)
  1.2730 +  // 3. OS-provided information
  1.2731 +  // 4. our "extras" array
  1.2732 +  // 5. Information from plugins
  1.2733 +  // 6. The "ext-to-type-mapping" category
  1.2734 +
  1.2735 +  // Early return if called with an empty extension parameter
  1.2736 +  if (aFileExt.IsEmpty())
  1.2737 +    return NS_ERROR_NOT_AVAILABLE;
  1.2738 +
  1.2739 +  nsresult rv = NS_OK;
  1.2740 +  // First of all, check our default entries
  1.2741 +  for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++)
  1.2742 +  {
  1.2743 +    if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) {
  1.2744 +      aContentType = defaultMimeEntries[i].mMimeType;
  1.2745 +      return rv;
  1.2746 +    }
  1.2747 +  }
  1.2748 +
  1.2749 +  // Check user-set prefs
  1.2750 +  nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  1.2751 +  if (handlerSvc)
  1.2752 +    rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType);
  1.2753 +  if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty())
  1.2754 +    return NS_OK;
  1.2755 +
  1.2756 +  // Ask OS.
  1.2757 +  bool found = false;
  1.2758 +  nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
  1.2759 +  if (mi && found)
  1.2760 +    return mi->GetMIMEType(aContentType);
  1.2761 +
  1.2762 +  // Check extras array.
  1.2763 +  found = GetTypeFromExtras(aFileExt, aContentType);
  1.2764 +  if (found)
  1.2765 +    return NS_OK;
  1.2766 +
  1.2767 +  const nsCString& flatExt = PromiseFlatCString(aFileExt);
  1.2768 +  // Try the plugins
  1.2769 +  const char* mimeType;
  1.2770 +  nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv));
  1.2771 +  nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get());
  1.2772 +  if (NS_SUCCEEDED(rv)) {
  1.2773 +    if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) {
  1.2774 +      aContentType = mimeType;
  1.2775 +      return NS_OK;
  1.2776 +    }
  1.2777 +  }
  1.2778 +  
  1.2779 +  rv = NS_OK;
  1.2780 +  // Let's see if an extension added something
  1.2781 +  nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
  1.2782 +  if (catMan) {
  1.2783 +    // The extension in the category entry is always stored as lowercase
  1.2784 +    nsAutoCString lowercaseFileExt(aFileExt);
  1.2785 +    ToLowerCase(lowercaseFileExt);
  1.2786 +    // Read the MIME type from the category entry, if available
  1.2787 +    nsXPIDLCString type;
  1.2788 +    rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(),
  1.2789 +                                  getter_Copies(type));
  1.2790 +    aContentType = type;
  1.2791 +  }
  1.2792 +  else {
  1.2793 +    rv = NS_ERROR_NOT_AVAILABLE;
  1.2794 +  }
  1.2795 +  
  1.2796 +  return rv;
  1.2797 +}
  1.2798 +
  1.2799 +NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
  1.2800 +{
  1.2801 +  NS_ENSURE_ARG(!aMIMEType.IsEmpty());
  1.2802 +
  1.2803 +  nsCOMPtr<nsIMIMEInfo> mi;
  1.2804 +  nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
  1.2805 +  if (NS_FAILED(rv))
  1.2806 +    return rv;
  1.2807 +
  1.2808 +  return mi->GetPrimaryExtension(_retval);
  1.2809 +}
  1.2810 +
  1.2811 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType) 
  1.2812 +{
  1.2813 +  NS_ENSURE_ARG_POINTER(aURI);
  1.2814 +  nsresult rv = NS_ERROR_NOT_AVAILABLE;
  1.2815 +  aContentType.Truncate();
  1.2816 +
  1.2817 +  // First look for a file to use.  If we have one, we just use that.
  1.2818 +  nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
  1.2819 +  if (fileUrl) {
  1.2820 +    nsCOMPtr<nsIFile> file;
  1.2821 +    rv = fileUrl->GetFile(getter_AddRefs(file));
  1.2822 +    if (NS_SUCCEEDED(rv)) {
  1.2823 +      rv = GetTypeFromFile(file, aContentType);
  1.2824 +      if (NS_SUCCEEDED(rv)) {
  1.2825 +        // we got something!
  1.2826 +        return rv;
  1.2827 +      }
  1.2828 +    }
  1.2829 +  }
  1.2830 +
  1.2831 +  // Now try to get an nsIURL so we don't have to do our own parsing
  1.2832 +  nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
  1.2833 +  if (url) {
  1.2834 +    nsAutoCString ext;
  1.2835 +    rv = url->GetFileExtension(ext);
  1.2836 +    if (NS_FAILED(rv))
  1.2837 +      return rv;
  1.2838 +    if (ext.IsEmpty())
  1.2839 +      return NS_ERROR_NOT_AVAILABLE;
  1.2840 +
  1.2841 +    UnescapeFragment(ext, url, ext);
  1.2842 +
  1.2843 +    return GetTypeFromExtension(ext, aContentType);
  1.2844 +  }
  1.2845 +    
  1.2846 +  // no url, let's give the raw spec a shot
  1.2847 +  nsAutoCString specStr;
  1.2848 +  rv = aURI->GetSpec(specStr);
  1.2849 +  if (NS_FAILED(rv))
  1.2850 +    return rv;
  1.2851 +  UnescapeFragment(specStr, aURI, specStr);
  1.2852 +
  1.2853 +  // find the file extension (if any)
  1.2854 +  int32_t extLoc = specStr.RFindChar('.');
  1.2855 +  int32_t specLength = specStr.Length();
  1.2856 +  if (-1 != extLoc &&
  1.2857 +      extLoc != specLength - 1 &&
  1.2858 +      // nothing over 20 chars long can be sanely considered an
  1.2859 +      // extension.... Dat dere would be just data.
  1.2860 +      specLength - extLoc < 20) 
  1.2861 +  {
  1.2862 +    return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
  1.2863 +  }
  1.2864 +
  1.2865 +  // We found no information; say so.
  1.2866 +  return NS_ERROR_NOT_AVAILABLE;
  1.2867 +}
  1.2868 +
  1.2869 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
  1.2870 +{
  1.2871 +  NS_ENSURE_ARG_POINTER(aFile);
  1.2872 +  nsresult rv;
  1.2873 +  nsCOMPtr<nsIMIMEInfo> info;
  1.2874 +
  1.2875 +  // Get the Extension
  1.2876 +  nsAutoString fileName;
  1.2877 +  rv = aFile->GetLeafName(fileName);
  1.2878 +  if (NS_FAILED(rv)) return rv;
  1.2879 + 
  1.2880 +  nsAutoCString fileExt;
  1.2881 +  if (!fileName.IsEmpty())
  1.2882 +  {
  1.2883 +    int32_t len = fileName.Length(); 
  1.2884 +    for (int32_t i = len; i >= 0; i--) 
  1.2885 +    {
  1.2886 +      if (fileName[i] == char16_t('.'))
  1.2887 +      {
  1.2888 +        CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
  1.2889 +        break;
  1.2890 +      }
  1.2891 +    }
  1.2892 +  }
  1.2893 +
  1.2894 +  if (fileExt.IsEmpty())
  1.2895 +    return NS_ERROR_FAILURE;
  1.2896 +
  1.2897 +  return GetTypeFromExtension(fileExt, aContentType);
  1.2898 +}
  1.2899 +
  1.2900 +nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
  1.2901 +  const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
  1.2902 +{
  1.2903 +  NS_ENSURE_ARG( aMIMEInfo );
  1.2904 +
  1.2905 +  NS_ENSURE_ARG( !aContentType.IsEmpty() );
  1.2906 +
  1.2907 +  // Look for default entry with matching mime type.
  1.2908 +  nsAutoCString MIMEType(aContentType);
  1.2909 +  ToLowerCase(MIMEType);
  1.2910 +  int32_t numEntries = ArrayLength(extraMimeEntries);
  1.2911 +  for (int32_t index = 0; index < numEntries; index++)
  1.2912 +  {
  1.2913 +      if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
  1.2914 +      {
  1.2915 +          // This is the one. Set attributes appropriately.
  1.2916 +          aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
  1.2917 +          aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
  1.2918 +          return NS_OK;
  1.2919 +      }
  1.2920 +  }
  1.2921 +
  1.2922 +  return NS_ERROR_NOT_AVAILABLE;
  1.2923 +}
  1.2924 +
  1.2925 +nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
  1.2926 +  const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
  1.2927 +{
  1.2928 +  nsAutoCString type;
  1.2929 +  bool found = GetTypeFromExtras(aExtension, type);
  1.2930 +  if (!found)
  1.2931 +    return NS_ERROR_NOT_AVAILABLE;
  1.2932 +  return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
  1.2933 +}
  1.2934 +
  1.2935 +bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
  1.2936 +{
  1.2937 +  NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
  1.2938 +
  1.2939 +  // Look for default entry with matching extension.
  1.2940 +  nsDependentCString::const_iterator start, end, iter;
  1.2941 +  int32_t numEntries = ArrayLength(extraMimeEntries);
  1.2942 +  for (int32_t index = 0; index < numEntries; index++)
  1.2943 +  {
  1.2944 +      nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
  1.2945 +      extList.BeginReading(start);
  1.2946 +      extList.EndReading(end);
  1.2947 +      iter = start;
  1.2948 +      while (start != end)
  1.2949 +      {
  1.2950 +          FindCharInReadable(',', iter, end);
  1.2951 +          if (Substring(start, iter).Equals(aExtension,
  1.2952 +                                            nsCaseInsensitiveCStringComparator()))
  1.2953 +          {
  1.2954 +              aMIMEType = extraMimeEntries[index].mMimeType;
  1.2955 +              return true;
  1.2956 +          }
  1.2957 +          if (iter != end) {
  1.2958 +            ++iter;
  1.2959 +          }
  1.2960 +          start = iter;
  1.2961 +      }
  1.2962 +  }
  1.2963 +
  1.2964 +  return false;
  1.2965 +}

mercurial