Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/uriloader/exthandler/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 132 kB image not shown  

Quelle  nsExternalHelperAppService.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim:expandtab:shiftwidth=2:tabstop=2:cin:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "base/basictypes.h"

/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
#include "mozilla/ArrayUtils.h"
#include "mozilla/Base64.h"
#include "mozilla/ResultExtensions.h"

#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/RandomNum.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StaticPtr.h"
#include "nsXULAppAPI.h"

#include "ExternalHelperAppParent.h"
#include "nsExternalHelperAppService.h"
#include "nsCExternalHandlerService.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIChannel.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsICategoryManager.h"
#include "nsDependentSubstring.h"
#include "nsSandboxFlags.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsIStringEnumerator.h"
#include "nsIStreamListener.h"
#include "nsIMIMEService.h"
#include "nsILoadGroup.h"
#include "nsIWebProgressListener.h"
#include "nsITransfer.h"
#include "nsReadableUtils.h"
#include "nsIRequest.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIInterfaceRequestor.h"
#include "nsThreadUtils.h"
#include "nsIMutableArray.h"
#include "nsIRedirectHistoryEntry.h"
#include "nsOSHelperAppService.h"
#include "nsOSHelperAppServiceChild.h"
#include "nsContentSecurityUtils.h"
#include "nsUTF8Utils.h"
#include "nsUnicodeProperties.h"

// used to access our datastore of user-configured helper applications
#include "nsIHandlerService.h"
#include "nsIMIMEInfo.h"
#include "nsIHelperAppLauncherDialog.h"
#include "nsIContentDispatchChooser.h"
#include "nsNetUtil.h"
#include "nsIPrivateBrowsingChannel.h"
#include "nsIIOService.h"
#include "nsNetCID.h"

#include "nsIApplicationReputation.h"

#include "nsDSURIContentListener.h"
#include "nsMimeTypes.h"
#include "nsMIMEInfoImpl.h"
// used for header disposition information.
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIEncodedChannel.h"
#include "nsIMultiPartChannel.h"
#include "nsIFileChannel.h"
#include "nsIObserverService.h"  // so we can be a profile change observer
#include "nsIPropertyBag2.h"     // for the 64-bit content length

#ifdef XP_MACOSX
#  include "nsILocalFileMac.h"
#endif

#include "nsEscape.h"

#include "nsIStringBundle.h"  // XXX needed to localize error msgs
#include "nsIPrompt.h"

#include "nsITextToSubURI.h"  // to unescape the filename

#include "nsDocShellCID.h"

#include "nsCRT.h"
#include "nsLocalHandlerApp.h"

#include "nsIRandomGenerator.h"

#include "ContentChild.h"
#include "nsXULAppAPI.h"
#include "nsPIDOMWindow.h"
#include "ExternalHelperAppChild.h"

#include "mozilla/dom/nsHTTPSOnlyUtils.h"

#ifdef XP_WIN
#  include "nsWindowsHelpers.h"
#  include "nsLocalFile.h"
#endif

#include "mozilla/Components.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/ipc/URIUtils.h"

using namespace mozilla;
using namespace mozilla::ipc;
using namespace mozilla::dom;

#define kDefaultMaxFileNameLength 254

// Download Folder location constants
#define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
enum {
  NS_FOLDER_VALUE_DESKTOP = 0,
  NS_FOLDER_VALUE_DOWNLOADS = 1,
  NS_FOLDER_VALUE_CUSTOM = 2
};

LazyLogModule nsExternalHelperAppService::sLog("HelperAppService");

// Using level 3 here because the OSHelperAppServices use a log level
// of LogLevel::Debug (4), and we want less detailed output here
// Using 3 instead of LogLevel::Warning because we don't output warnings
#undef LOG
#define LOG(...)                                                     \
  MOZ_LOG(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info, \
          (__VA_ARGS__))
#define LOG_ENABLED() \
  MOZ_LOG_TEST(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info)

static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
    "browser.helperApps.neverAsk.saveToDisk";
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
    "browser.helperApps.neverAsk.openFile";

StaticRefPtr<nsIFile> sFallbackDownloadDir;

// Helper functions for Content-Disposition headers

/**
 * Given a URI fragment, unescape it
 * @param aFragment The string to unescape
 * @param aURI The URI from which this fragment is taken. Only its character set
 *             will be used.
 * @param aResult [out] Unescaped string.
 */

static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
                                 nsAString& aResult) {
  // We need the unescaper
  nsresult rv;
  nsCOMPtr<nsITextToSubURI> textToSubURI =
      do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return textToSubURI->UnEscapeURIForUI(aFragment, /* aDontEscape = */ true,
                                        aResult);
}

/**
 * UTF-8 version of UnescapeFragment.
 * @param aFragment The string to unescape
 * @param aURI The URI from which this fragment is taken. Only its character set
 *             will be used.
 * @param aResult [out] Unescaped string, UTF-8 encoded.
 * @note It is safe to pass the same string for aFragment and aResult.
 * @note When this function fails, aResult will not be modified.
 */

static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
                                 nsACString& aResult) {
  nsAutoString result;
  nsresult rv = UnescapeFragment(aFragment, aURI, result);
  if (NS_SUCCEEDED(rv)) CopyUTF16toUTF8(result, aResult);
  return rv;
}

static Result<nsCOMPtr<nsIFile>, nsresult> GetOsTmpDownloadDirectory() {
  nsCOMPtr<nsIFile> dir;
  MOZ_TRY(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir)));

#if !defined(XP_MACOSX) && defined(XP_UNIX)
  // Ensuring that only the current user can read the file names we end up
  // creating. Note that creating directories with a specified permission is
  // only supported on Unix platform right now. That's why the above check
  // exists.

  uint32_t permissions;
  MOZ_TRY(dir->GetPermissions(&permissions));

  if (permissions != PR_IRWXU) {
    const char* userName = PR_GetEnv("USERNAME");
    if (!userName || !*userName) {
      userName = PR_GetEnv("USER");
    }
    if (!userName || !*userName) {
      userName = PR_GetEnv("LOGNAME");
    }
    if (!userName || !*userName) {
      userName = "mozillaUser";
    }

    nsAutoString userDir;
    userDir.AssignLiteral("mozilla_");
    userDir.AppendASCII(userName);
    userDir.ReplaceChar(u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');

    int counter = 0;
    bool pathExists;
    nsCOMPtr<nsIFile> finalPath;

    while (true) {
      nsAutoString countedUserDir(userDir);
      countedUserDir.AppendInt(counter, 10);
      dir->Clone(getter_AddRefs(finalPath));
      finalPath->Append(countedUserDir);

      MOZ_TRY(finalPath->Exists(&pathExists));

      if (pathExists) {
        // If this path has the right permissions, use it.
        MOZ_TRY(finalPath->GetPermissions(&permissions));

        // Ensuring the path is writable by the current user.
        bool isWritable;
        MOZ_TRY(finalPath->IsWritable(&isWritable));

        if (permissions == PR_IRWXU && isWritable) {
          dir = finalPath;
          break;
        }
      }

      nsresult const rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
      if (NS_SUCCEEDED(rv)) {
        dir = finalPath;
        break;
      }
      if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
        // Unexpected error.
        return Err(rv);
      }
      counter++;
    }
  }

#endif
  NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
  return dir;
}

/**
 * Given an alleged download directory, either create it, or confirm that it
 * already exists and is usable.
 */

static nsresult EnsureDirectoryExists(nsIFile* aDir) {
  nsresult const rv = aDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
  if (rv == NS_ERROR_FILE_ALREADY_EXISTS || NS_SUCCEEDED(rv)) {
    return NS_OK;
  }
  return rv;
};

/**
 * Obtains the final directory to save downloads to. This tends to vary per
 * platform, and needs to be consistent throughout our codepaths. For platforms
 * where helper apps use the downloads directory, this should be kept in sync
 * with the function of the same name in DownloadIntegration.sys.mjs.
 *
 * Optionally skip availability of the directory and storage.
 */

static Result<nsCOMPtr<nsIFile>, nsresult> GetPreferredDownloadsDirectory(
    bool aSkipChecks = false) {
#if defined(ANDROID)
  return Err(NS_ERROR_FAILURE);
#endif

  nsresult rv;
  // Try to get the users download location, if it's set.
  switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
    case NS_FOLDER_VALUE_DESKTOP: {
      nsCOMPtr<nsIFile> dir;
      if (NS_SUCCEEDED(
              NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir)))) {
        return dir;
      }
    } break;

    case NS_FOLDER_VALUE_CUSTOM: {
      nsCOMPtr<nsIFile> dir;
      Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, NS_GET_IID(nsIFile),
                              getter_AddRefs(dir));
      if (!dir) break;

      // Check for availability if requested.
      if (!aSkipChecks && NS_FAILED(EnsureDirectoryExists(dir))) {
        break;
      }

      return dir;
    } break;

    default:
    case NS_FOLDER_VALUE_DOWNLOADS:
      // This is just the OS default location, so fall out
      break;
  }

  // Fallthrough: get OS default directory

  nsCOMPtr<nsIFile> dir;
  rv = NS_GetSpecialDirectory(NS_OS_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir));
  if (NS_SUCCEEDED(rv)) {
    return dir;
  }

  // On some OSes, there is no guarantee that `NS_OS_DEFAULT_DOWNLOAD_DIR`
  // exists. Fall back to $HOME + Downloads.

  // If we've done this before, use the cached value:
  if (sFallbackDownloadDir) {
    MOZ_TRY(sFallbackDownloadDir->Clone(getter_AddRefs(dir)));
    return dir;
  }

  MOZ_TRY(NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(dir)));

  // Get the appropriate translation of "Downloads"...
  nsAutoString downloadLocalized;
  rv = [&downloadLocalized]() {
    nsresult rv;

    nsCOMPtr<nsIStringBundleService> bundleService =
        do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIStringBundle> downloadBundle;
    rv = bundleService->CreateBundle(
        "chrome://mozapps/locale/downloads/downloads.properties",
        getter_AddRefs(downloadBundle));
    NS_ENSURE_SUCCESS(rv, rv);

    return downloadBundle->GetStringFromName("downloadsFolder",
                                             downloadLocalized);
  }();
  // ... or, failing that, just use "Downloads".
  if (NS_FAILED(rv)) {
    downloadLocalized.AssignLiteral("Downloads");
  }
  MOZ_TRY(dir->Append(downloadLocalized));

  // Cache the result for next time.
  {
    // Can't getter_AddRefs on StaticRefPtr, so do some copying.
    nsCOMPtr<nsIFile> copy;
    dir->Clone(getter_AddRefs(copy));
    sFallbackDownloadDir = copy.forget();
    ClearOnShutdown(&sFallbackDownloadDir);
  }

  // Check for availability if requested.
  if (!aSkipChecks) {
    MOZ_TRY(EnsureDirectoryExists(dir));
  }

  return dir;
}

NS_IMETHODIMP nsExternalHelperAppService::GetPreferredDownloadsDirectory(
    nsIFile** aOutFile) {
  auto res = ::GetPreferredDownloadsDirectory();
  if (res.isErr()) return res.unwrapErr();
  res.unwrap().forget(aOutFile);
  return NS_OK;
}

/**
 * Obtains the initial directory to save downloads to. (This may differ from the
 * actual download directory if "browser.download.start_downloads_in_tmp_dir" is
 * set.)
 *
 * Optionally, skip availability of the directory and storage.
 */

static Result<nsCOMPtr<nsIFile>, nsresult> GetInitialDownloadDirectory(
    bool aSkipChecks = false) {
#if defined(ANDROID)
  return Err(NS_ERROR_FAILURE);
#endif

  if (StaticPrefs::browser_download_start_downloads_in_tmp_dir()) {
    return GetOsTmpDownloadDirectory();
  }

  return GetPreferredDownloadsDirectory(aSkipChecks);
}

/**
 * Helper for random bytes for the filename of downloaded part files.
 */

nsresult GenerateRandomName(nsACString& result) {
  // We will request raw random bytes, and transform that to a base64 string,
  // using url-based base64 encoding so that all characters from the base64
  // result will be acceptable for filenames.
  // For each three bytes of random data, we will get four bytes of ASCII.
  // Request a bit more, to be safe, then truncate in the end.

  nsresult rv;
  const uint32_t wantedFileNameLength = 8;
  const uint32_t requiredBytesLength =
      static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);

  uint8_t buffer[requiredBytesLength];
  if (!mozilla::GenerateRandomBytesFromOS(buffer, requiredBytesLength)) {
    return NS_ERROR_FAILURE;
  }

  nsAutoCString tempLeafName;
  // We're forced to specify a padding policy, though this is guaranteed
  // not to need padding due to requiredBytesLength being a multiple of 3.
  rv = Base64URLEncode(requiredBytesLength, buffer,
                       Base64URLEncodePaddingPolicy::Omit, tempLeafName);
  NS_ENSURE_SUCCESS(rv, rv);

  tempLeafName.Truncate(wantedFileNameLength);

  result.Assign(tempLeafName);
  return NS_OK;
}

/**
 * Structure for storing extension->type mappings.
 * @see defaultMimeEntries
 */

struct nsDefaultMimeTypeEntry {
  const char* mMimeType;
  const char* mFileExtension;
};

/**
 * Default extension->mimetype mappings. These are not overridable.
 * If you add types here, make sure they are lowercase, or you'll regret it.
 */

static const nsDefaultMimeTypeEntry defaultMimeEntries[] = {
    // The following are those extensions that we're asked about during startup,
    // sorted by order used
    {TEXT_XML, "xml"},
    {IMAGE_PNG, "png"},
    // -- end extensions used during startup
    {TEXT_CSS, "css"},
    {IMAGE_JPEG, "jpeg"},
    {IMAGE_JPEG, "jpg"},
    {IMAGE_SVG_XML, "svg"},
    {TEXT_HTML, "html"},
    {TEXT_HTML, "htm"},
    {IMAGE_GIF, "gif"},
    {IMAGE_WEBP, "webp"},
    {APPLICATION_XPINSTALL, "xpi"},
    {APPLICATION_XHTML_XML, "xhtml"},
    {APPLICATION_XHTML_XML, "xht"},
    {TEXT_PLAIN, "txt"},
    {APPLICATION_JSON, "json"},
    {APPLICATION_RDF, "rdf"},
    {APPLICATION_XJAVASCRIPT, "mjs"},
    {APPLICATION_XJAVASCRIPT, "js"},
    {APPLICATION_XJAVASCRIPT, "jsm"},
    {VIDEO_OGG, "ogv"},
    {APPLICATION_OGG, "ogg"},
    {AUDIO_OGG, "oga"},
    {AUDIO_OGG, "opus"},
    {APPLICATION_PDF, "pdf"},
    {VIDEO_WEBM, "webm"},
    {AUDIO_WEBM, "webm"},
    {IMAGE_ICO, "ico"},
    {TEXT_PLAIN, "properties"},
    {TEXT_PLAIN, "locale"},
    {TEXT_PLAIN, "ftl"},
#if defined(MOZ_WMF)
    {VIDEO_MP4, "mp4"},
    {AUDIO_MP4, "m4a"},
    {AUDIO_MP3, "mp3"},
#endif
#ifdef MOZ_RAW
    {VIDEO_RAW, "yuv"}
#endif
};

/**
 * This is a small private struct used to help us initialize some
 * default mime types.
 */

struct nsExtraMimeTypeEntry {
  const char* mMimeType;
  const char* mFileExtensions;
  const char* mDescription;
};

/**
 * This table lists all of the 'extra' content types that we can deduce from
 * particular file extensions.  These entries also ensure that we provide a good
 * descriptive name when we encounter files with these content types and/or
 * extensions.  These can be overridden by user helper app prefs. If you add
 * types here, make sure they are lowercase, or you'll regret it.
 */

static const nsExtraMimeTypeEntry extraMimeEntries[] = {
#if defined(XP_MACOSX)  // don't define .bin on the mac...use internet config to
                        // look that up...
    {APPLICATION_OCTET_STREAM, "exe,com""Binary File"},
#else
    {APPLICATION_OCTET_STREAM, "exe,com,bin""Binary File"},
#endif
    {APPLICATION_GZIP2, "gz""gzip"},
    {"application/x-arj""arj""ARJ file"},
    {"application/rtf""rtf""Rich Text Format File"},
    {APPLICATION_ZIP, "zip""ZIP Archive"},
    {APPLICATION_XPINSTALL, "xpi""XPInstall Install"},
    {APPLICATION_PDF, "pdf""Portable Document Format"},
    {APPLICATION_POSTSCRIPT, "ps,eps,ai""Postscript File"},
    {APPLICATION_XJAVASCRIPT, "js""Javascript Source File"},
    {APPLICATION_XJAVASCRIPT, "jsm,mjs""Javascript Module Source File"},
#ifdef MOZ_WIDGET_ANDROID
    {"application/vnd.android.package-archive""apk""Android Package"},
#endif

    // OpenDocument formats
    {"application/vnd.oasis.opendocument.text""odt""OpenDocument Text"},
    {"application/vnd.oasis.opendocument.presentation""odp",
     "OpenDocument Presentation"},
    {"application/vnd.oasis.opendocument.spreadsheet""ods",
     "OpenDocument Spreadsheet"},
    {"application/vnd.oasis.opendocument.graphics""odg",
     "OpenDocument Graphics"},

    // Legacy Microsoft Office
    {"application/msword""doc""Microsoft Word"},
    {"application/vnd.ms-powerpoint""ppt""Microsoft PowerPoint"},
    {"application/vnd.ms-excel""xls""Microsoft Excel"},

    // Office Open XML
    {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
     "docx""Microsoft Word (Open XML)"},
    {"application/"
     "vnd.openxmlformats-officedocument.presentationml.presentation",
     "pptx""Microsoft PowerPoint (Open XML)"},
    {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
     "xlsx""Microsoft Excel (Open XML)"},

    {IMAGE_ART, "art""ART Image"},
    {IMAGE_BMP, "bmp""BMP Image"},
    {IMAGE_GIF, "gif""GIF Image"},
    {IMAGE_ICO, "ico,cur""ICO Image"},
    {IMAGE_JPEG, "jpg,jpeg,jfif,pjpeg,pjp""JPEG Image"},
    {IMAGE_PNG, "png""PNG Image"},
    {IMAGE_APNG, "apng""APNG Image"},
    {IMAGE_TIFF, "tiff,tif""TIFF Image"},
    {IMAGE_XBM, "xbm""XBM Image"},
    {IMAGE_SVG_XML, "svg""Scalable Vector Graphics"},
    {IMAGE_WEBP, "webp""WebP Image"},
    {IMAGE_AVIF, "avif""AV1 Image File"},
    {IMAGE_JXL, "jxl""JPEG XL Image File"},

    {MESSAGE_RFC822, "eml""RFC-822 data"},
    {TEXT_PLAIN, "txt,text""Text File"},
    {APPLICATION_JSON, "json""JavaScript Object Notation"},
    {TEXT_VTT, "vtt""Web Video Text Tracks"},
    {TEXT_CACHE_MANIFEST, "appcache""Application Cache Manifest"},
    {TEXT_HTML, "html,htm,shtml,ehtml""HyperText Markup Language"},
    {"application/xhtml+xml""xhtml,xht",
     "Extensible HyperText Markup Language"},
    {APPLICATION_MATHML_XML, "mml""Mathematical Markup Language"},
    {APPLICATION_RDF, "rdf""Resource Description Framework"},
    {"text/csv""csv""CSV File"},
    {TEXT_XML, "xml,xsl,xbl""Extensible Markup Language"},
    {TEXT_CSS, "css""Style Sheet"},
    {TEXT_VCARD, "vcf,vcard""Contact Information"},
    {TEXT_CALENDAR, "ics,ical,ifb,icalendar""iCalendar"},
    {VIDEO_OGG, "ogv,ogg""Ogg Video"},
    {APPLICATION_OGG, "ogg""Ogg Video"},
    {AUDIO_OGG, "oga""Ogg Audio"},
    {AUDIO_OGG, "opus""Opus Audio"},
    {VIDEO_WEBM, "webm""Web Media Video"},
    {AUDIO_WEBM, "webm""Web Media Audio"},
    {AUDIO_MP3, "mp3,mpega,mp2""MPEG Audio"},
    {VIDEO_MP4, "mp4,m4a,m4b""MPEG-4 Video"},
    {AUDIO_MP4, "m4a,m4b""MPEG-4 Audio"},
    {VIDEO_RAW, "yuv""Raw YUV Video"},
    {AUDIO_WAV, "wav""Waveform Audio"},
    {VIDEO_3GPP, "3gpp,3gp""3GPP Video"},
    {VIDEO_3GPP2, "3g2""3GPP2 Video"},
    {AUDIO_AAC, "aac""AAC Audio"},
    {AUDIO_FLAC, "flac""FLAC Audio"},
    {AUDIO_MIDI, "mid""Standard MIDI Audio"},
    {APPLICATION_WASM, "wasm""WebAssembly Module"}};

static const nsDefaultMimeTypeEntry sForbiddenPrimaryExtensions[] = {
    {IMAGE_JPEG, "jfif"}};

/**
 * File extensions for which decoding should be disabled.
 * NOTE: These MUST be lower-case and ASCII.
 */

static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
    {APPLICATION_GZIP, "gz"},
    {APPLICATION_GZIP, "tgz"},
    {APPLICATION_ZIP, "zip"},
    {APPLICATION_COMPRESS, "z"},
    {APPLICATION_GZIP, "svgz"}};

/**
 * Mimetypes for which we enforce using a known extension.
 *
 * In addition to this list, we do this for all audio/, video/ and
 * image/ mimetypes.
 */

static const char* forcedExtensionMimetypes[] = {
    APPLICATION_PDF, APPLICATION_OGG, APPLICATION_WASM,
    TEXT_CALENDAR,   TEXT_CSS,        TEXT_VCARD};

/**
 * Primary extensions of types whose descriptions should be overwritten.
 * This extension is concatenated with "ExtHandlerDescription" to look up the
 * description in unknownContentType.properties.
 * NOTE: These MUST be lower-case and ASCII.
 */

static const char* descriptionOverwriteExtensions[] = {
    "avif""jxl""pdf""svg""webp""xml",
};

static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;

/**
 * In child processes, return an nsOSHelperAppServiceChild for remoting
 * OS calls to the parent process.  In the parent process itself, use
 * nsOSHelperAppService.
 */

/* static */
already_AddRefed<nsExternalHelperAppService>
nsExternalHelperAppService::GetSingleton() {
  if (!sExtHelperAppSvcSingleton) {
    if (XRE_IsParentProcess()) {
      sExtHelperAppSvcSingleton = new nsOSHelperAppService();
    } else {
      sExtHelperAppSvcSingleton = new nsOSHelperAppServiceChild();
    }
    ClearOnShutdown(&sExtHelperAppSvcSingleton);
  }

  return do_AddRef(sExtHelperAppSvcSingleton);
}

NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
                  nsPIExternalAppLauncher, nsIExternalProtocolService,
                  nsIMIMEService, nsIObserver, nsISupportsWeakReference)

nsExternalHelperAppService::nsExternalHelperAppService() {}
nsresult nsExternalHelperAppService::Init() {
  // Add an observer for profile change
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (!obs) return NS_ERROR_FAILURE;

  nsresult rv = obs->AddObserver(this"profile-before-change"true);
  NS_ENSURE_SUCCESS(rv, rv);
  return obs->AddObserver(this"last-pb-context-exited"true);
}

nsExternalHelperAppService::~nsExternalHelperAppService() {}

nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
    const nsACString& aMimeContentType, nsIChannel* aChannel,
    BrowsingContext* aContentContext, bool aForceSave,
    nsIInterfaceRequestor* aWindowContext,
    nsIStreamListener** aStreamListener) {
  NS_ENSURE_ARG_POINTER(aChannel);

  // We need to get a hold of a ContentChild so that we can begin forwarding
  // this data to the parent.  In the HTTP case, this is unfortunate, since
  // we're actually passing data from parent->child->parent wastefully, but
  // the Right Fix will eventually be to short-circuit those channels on the
  // parent side based on some sort of subscription concept.
  using mozilla::dom::ContentChild;
  using mozilla::dom::ExternalHelperAppChild;
  ContentChild* child = ContentChild::GetSingleton();
  if (!child) {
    return NS_ERROR_FAILURE;
  }

  nsCString disp;
  nsCOMPtr<nsIURI> uri;
  int64_t contentLength = -1;
  bool wasFileChannel = false;
  uint32_t contentDisposition = -1;
  nsAutoString fileName;
  nsCOMPtr<nsILoadInfo> loadInfo;

  aChannel->GetURI(getter_AddRefs(uri));
  aChannel->GetContentLength(&contentLength);
  aChannel->GetContentDisposition(&contentDisposition);
  aChannel->GetContentDispositionFilename(fileName);
  aChannel->GetContentDispositionHeader(disp);
  loadInfo = aChannel->LoadInfo();

  nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aChannel));
  wasFileChannel = fileChan != nullptr;

  nsCOMPtr<nsIURI> referrer;
  NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));

  mozilla::net::LoadInfoArgs loadInfoArgs;
  MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));

  // Now we build a protocol for forwarding our data to the parent.  The
  // protocol will act as a listener on the child-side and create a "real"
  // helperAppService listener on the parent-side, via another call to
  // DoContent.
  RefPtr<ExternalHelperAppChild> childListener = new ExternalHelperAppChild();
  MOZ_ALWAYS_TRUE(child->SendPExternalHelperAppConstructor(
      childListener, uri, loadInfoArgs, nsCString(aMimeContentType), disp,
      contentDisposition, fileName, aForceSave, contentLength, wasFileChannel,
      referrer, aContentContext));

  NS_ADDREF(*aStreamListener = childListener);

  uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;

  SanitizeFileName(fileName, 0);

  RefPtr<nsExternalAppHandler> handler =
      new nsExternalAppHandler(nullptr, u""_ns, aContentContext, aWindowContext,
                               this, fileName, reason, aForceSave);
  if (!handler) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  childListener->SetHandler(handler);
  return NS_OK;
}

NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
    const nsACString& aMimeContentType, nsIChannel* aChannel,
    BrowsingContext* aContentContext, bool aForceSave,
    nsIInterfaceRequestor* aWindowContext,
    nsIStreamListener** aStreamListener) {
  MOZ_ASSERT(!XRE_IsContentProcess());
  NS_ENSURE_ARG_POINTER(aChannel);

  nsAutoString fileName;
  nsAutoCString fileExtension;
  uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;

  uint32_t contentDisposition = -1;
  aChannel->GetContentDisposition(&contentDisposition);
  if (contentDisposition == nsIChannel::DISPOSITION_ATTACHMENT) {
    reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
  }

  *aStreamListener = nullptr;

  // Get the file extension and name that we will need later
  nsCOMPtr<nsIURI> uri;
  bool allowURLExtension =
      GetFileNameFromChannel(aChannel, fileName, getter_AddRefs(uri));

  uint32_t flags = VALIDATE_ALLOW_EMPTY;
  if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
                              nsCaseInsensitiveCStringComparator)) {
    flags |= VALIDATE_GUESS_FROM_EXTENSION;
  }

  nsCOMPtr<nsIMIMEInfo> mimeInfo = ValidateFileNameForSaving(
      fileName, aMimeContentType, uri, nullptr, flags, allowURLExtension);

  LOG("Type/Ext lookup found 0x%p\n", mimeInfo.get());

  // No mimeinfo -> we can't continue. probably OOM.
  if (!mimeInfo) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (flags & VALIDATE_GUESS_FROM_EXTENSION) {
    // Replace the content type with what was guessed.
    nsAutoCString mimeType;
    mimeInfo->GetMIMEType(mimeType);
    aChannel->SetContentType(mimeType);

    if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
      reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
    }
  }

  nsAutoString extension;
  int32_t dotidx = fileName.RFind(u".");
  if (dotidx != -1) {
    extension = Substring(fileName, dotidx + 1);
  }

  // NB: ExternalHelperAppParent depends on this listener always being an
  // nsExternalAppHandler. If this changes, make sure to update that code.
  nsExternalAppHandler* handler = new nsExternalAppHandler(
      mimeInfo, extension, aContentContext, aWindowContext, this, fileName,
      reason, aForceSave);
  if (!handler) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  NS_ADDREF(*aStreamListener = handler);
  return NS_OK;
}

NS_IMETHODIMP nsExternalHelperAppService::DoContent(
    const nsACString& aMimeContentType, nsIChannel* aChannel,
    nsIInterfaceRequestor* aContentContext, bool aForceSave,
    nsIInterfaceRequestor* aWindowContext,
    nsIStreamListener** aStreamListener) {
  // Scripted interface requestors cannot return an instance of the
  // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
  // get to the window via `nsIDOMWindow`.  Unfortunately, at that point we
  // don't know whether the thing we got is an inner or outer window, so have to
  // work with either one.
  RefPtr<BrowsingContext> bc;
  nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(aContentContext);
  if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_QueryInterface(domWindow)) {
    bc = outerWindow->GetBrowsingContext();
  } else if (nsCOMPtr<nsPIDOMWindowInner> innerWindow =
                 do_QueryInterface(domWindow)) {
    bc = innerWindow->GetBrowsingContext();
  }

  if (XRE_IsContentProcess()) {
    return DoContentContentProcessHelper(aMimeContentType, aChannel, bc,
                                         aForceSave, aWindowContext,
                                         aStreamListener);
  }

  nsresult rv = CreateListener(aMimeContentType, aChannel, bc, aForceSave,
                               aWindowContext, aStreamListener);
  return rv;
}

NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
    const nsACString& aExtension, const nsACString& aEncodingType,
    bool* aApplyDecoding) {
  *aApplyDecoding = true;
  uint32_t i;
  for (i = 0; i < std::size(nonDecodableExtensions); ++i) {
    if (aExtension.LowerCaseEqualsASCII(
            nonDecodableExtensions[i].mFileExtension) &&
        aEncodingType.LowerCaseEqualsASCII(
            nonDecodableExtensions[i].mMimeType)) {
      *aApplyDecoding = false;
      break;
    }
  }
  return NS_OK;
}

nsresult nsExternalHelperAppService::GetFileTokenForPath(
    const char16_t* aPlatformAppPath, nsIFile** aFile) {
  nsDependentString platformAppPath(aPlatformAppPath);
  // First, check if we have an absolute path
  nsIFile* localFile = nullptr;
  nsresult rv = NS_NewLocalFile(platformAppPath, &localFile);
  if (NS_SUCCEEDED(rv)) {
    *aFile = localFile;
    bool exists;
    if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
      NS_RELEASE(*aFile);
      return NS_ERROR_FILE_NOT_FOUND;
    }
    return NS_OK;
  }

  // Second, check if file exists in mozilla program directory
  rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
  if (NS_SUCCEEDED(rv)) {
    rv = (*aFile)->Append(platformAppPath);
    if (NS_SUCCEEDED(rv)) {
      bool exists = false;
      rv = (*aFile)->Exists(&exists);
      if (NS_SUCCEEDED(rv) && exists) return NS_OK;
    }
    NS_RELEASE(*aFile);
  }

  return NS_ERROR_NOT_AVAILABLE;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
// begin external protocol service default implementation...
//////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(
    const char* aProtocolScheme, bool* aHandlerExists) {
  nsCOMPtr<nsIHandlerInfo> handlerInfo;
  nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
                                       getter_AddRefs(handlerInfo));
  if (NS_SUCCEEDED(rv)) {
    // See if we have any known possible handler apps for this
    nsCOMPtr<nsIMutableArray> possibleHandlers;
    handlerInfo->GetPossibleApplicationHandlers(
        getter_AddRefs(possibleHandlers));

    uint32_t length;
    possibleHandlers->GetLength(&length);
    if (length) {
      *aHandlerExists = true;
      return NS_OK;
    }
  }

  // if not, fall back on an os-based handler
  return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
}

NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(
    const char* aProtocolScheme, bool* aResult) {
  // check the per protocol setting first.  it always takes precedence.
  // if not set, then use the global setting.

  nsAutoCString prefName("network.protocol-handler.expose.");
  prefName += aProtocolScheme;
  bool val;
  if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
    *aResult = val;
    return NS_OK;
  }

  // by default, no protocol is exposed.  i.e., by default all link clicks must
  // go through the external protocol service.  most applications override this
  // default behavior.
  *aResult = Preferences::GetBool("network.protocol-handler.expose-all"false);

  return NS_OK;
}

static const char kExternalProtocolPrefPrefix[] =
    "network.protocol-handler.external.";
static const char kExternalProtocolDefaultPref[] =
    "network.protocol-handler.external-default";

// static
nsresult nsExternalHelperAppService::EscapeURI(nsIURI* aURI, nsIURI** aResult) {
  MOZ_ASSERT(aURI);
  MOZ_ASSERT(aResult);

  nsAutoCString spec;
  aURI->GetSpec(spec);

  if (spec.Find("%00") != -1) return NS_ERROR_MALFORMED_URI;

  nsAutoCString escapedSpec;
  nsresult rv = NS_EscapeURL(spec, esc_AlwaysCopy | esc_ExtHandler, escapedSpec,
                             fallible);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIIOService> ios(do_GetIOService());
  return ios->NewURI(escapedSpec, nullptr, nullptr, aResult);
}

bool ExternalProtocolIsBlockedBySandbox(
    BrowsingContext* aBrowsingContext,
    const bool aHasValidUserGestureActivation) {
  if (!StaticPrefs::dom_block_external_protocol_navigation_from_sandbox()) {
    return false;
  }

  if (!aBrowsingContext || aBrowsingContext->IsTop()) {
    return false;
  }

  uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();

  if (sandboxFlags == SANDBOXED_NONE) {
    return false;
  }

  if (!(sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION)) {
    return false;
  }

  if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) {
    return false;
  }

  if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_CUSTOM_PROTOCOLS)) {
    return false;
  }

  if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) &&
      aHasValidUserGestureActivation) {
    return false;
  }

  return true;
}

NS_IMETHODIMP
nsExternalHelperAppService::LoadURI(nsIURI* aURI,
                                    nsIPrincipal* aTriggeringPrincipal,
                                    nsIPrincipal* aRedirectPrincipal,
                                    BrowsingContext* aBrowsingContext,
                                    bool aTriggeredExternally,
                                    bool aHasValidUserGestureActivation,
                                    bool aNewWindowTarget) {
  NS_ENSURE_ARG_POINTER(aURI);

  if (XRE_IsContentProcess()) {
    mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
        aURI, aTriggeringPrincipal, aRedirectPrincipal, aBrowsingContext,
        aTriggeredExternally, aHasValidUserGestureActivation, aNewWindowTarget);
    return NS_OK;
  }

  // Prevent sandboxed BrowsingContexts from navigating to external protocols.
  // This only uses the sandbox flags of the target BrowsingContext of the
  // load. The navigating document's CSP sandbox flags do not apply.
  if (aBrowsingContext &&
      ExternalProtocolIsBlockedBySandbox(aBrowsingContext,
                                         aHasValidUserGestureActivation)) {
    // Log an error to the web console of the sandboxed BrowsingContext.
    nsAutoString localizedMsg;
    nsAutoCString spec;
    aURI->GetSpec(spec);

    AutoTArray<nsString, 1> params = {NS_ConvertUTF8toUTF16(spec)};
    nsresult rv = nsContentUtils::FormatLocalizedString(
        nsContentUtils::eSECURITY_PROPERTIES, "SandboxBlockedCustomProtocols",
        params, localizedMsg);
    NS_ENSURE_SUCCESS(rv, rv);

    // Log to the the parent window of the iframe. If there is no parent, fall
    // back to the iframe window itself.
    WindowContext* windowContext = aBrowsingContext->GetParentWindowContext();
    if (!windowContext) {
      windowContext = aBrowsingContext->GetCurrentWindowContext();
    }

    // Skip logging if we still don't have a WindowContext.
    NS_ENSURE_TRUE(windowContext, NS_ERROR_FAILURE);

    nsContentUtils::ReportToConsoleByWindowID(
        localizedMsg, nsIScriptError::errorFlag, "Security"_ns,
        windowContext->InnerWindowId(),
        SourceLocation(windowContext->Canonical()->GetDocumentURI()));

    return NS_OK;
  }

  nsCOMPtr<nsIURI> escapedURI;
  nsresult rv = EscapeURI(aURI, getter_AddRefs(escapedURI));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString scheme;
  escapedURI->GetScheme(scheme);
  if (scheme.IsEmpty()) return NS_OK;  // must have a scheme

  // Deny load if the prefs say to do so
  nsAutoCString externalPref(kExternalProtocolPrefPrefix);
  externalPref += scheme;
  bool allowLoad = false;
  if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
    // no scheme-specific value, check the default
    if (NS_FAILED(
            Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) {
      return NS_OK;  // missing default pref
    }
  }

  if (!allowLoad) {
    return NS_OK;  // explicitly denied
  }

  // Now check if the principal is allowed to access the navigated context.
  // We allow navigating subframes, even if not same-origin - non-external
  // links can always navigate everywhere, so this is a minor additional
  // restriction, only aiming to prevent some types of spoofing attacks
  // from otherwise disjoint browsingcontext trees.
  if (aBrowsingContext && aTriggeringPrincipal &&
      // Add-on principals are always allowed:
      !BasePrincipal::Cast(aTriggeringPrincipal)->AddonPolicy() &&
      // As is chrome code:
      !aTriggeringPrincipal->IsSystemPrincipal()) {
    RefPtr<BrowsingContext> bc = aBrowsingContext;
    WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal();
    bool foundAccessibleFrame = false;

    // Don't block the load if it is the first load in a new window (e.g. due to
    // a call to window.open, or a target=_blank link click).
    if (aNewWindowTarget) {
      MOZ_ASSERT(bc->IsTop());
      foundAccessibleFrame = true;
    }

    // Also allow this load if the target is a toplevel BC which contains a
    // non-web-controlled about:blank document.
    // NOTE: This catches cases like shift-clicking a link which do not set
    // `newWindowTarget`, but do open a link in a new window on behalf of web
    // content.
    if (!foundAccessibleFrame && bc->IsTop() &&
        !bc->GetTopLevelCreatedByWebContent() && wgp) {
      nsIURI* uri = wgp->GetDocumentURI();
      foundAccessibleFrame = uri && NS_IsAboutBlank(uri);
    }

    while (!foundAccessibleFrame) {
      if (wgp) {
        foundAccessibleFrame =
            aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
      }
      // We have to get the parent via the bc, because there may not
      // be a window global for the innermost bc; see bug 1650162.
      BrowsingContext* parent = bc->GetParent();
      if (!parent) {
        break;
      }
      bc = parent;
      wgp = parent->Canonical()->GetCurrentWindowGlobal();
    }

    if (!foundAccessibleFrame) {
      // See if this navigation could have come from a subframe.
      nsTArray<RefPtr<BrowsingContext>> contexts;
      aBrowsingContext->GetAllBrowsingContextsInSubtree(contexts);
      for (const auto& kid : contexts) {
        wgp = kid->Canonical()->GetCurrentWindowGlobal();
        if (wgp && aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal())) {
          foundAccessibleFrame = true;
          break;
        }
      }
    }

    if (!foundAccessibleFrame) {
      return NS_OK;  // deny the load.
    }
  }

  nsCOMPtr<nsIHandlerInfo> handler;
  rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIContentDispatchChooser> chooser =
      do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return chooser->HandleURI(
      handler, escapedURI,
      aRedirectPrincipal ? aRedirectPrincipal : aTriggeringPrincipal,
      aBrowsingContext, aTriggeredExternally);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
// Methods related to deleting temporary files on exit
//////////////////////////////////////////////////////////////////////////////////////////////////////

/* static */
nsresult nsExternalHelperAppService::DeleteTemporaryFileHelper(
    nsIFile* aTemporaryFile, nsCOMArray<nsIFile>& aFileList) {
  bool isFile = false;

  // as a safety measure, make sure the nsIFile is really a file and not a
  // directory object.
  aTemporaryFile->IsFile(&isFile);
  if (!isFile) return NS_OK;

  aFileList.AppendObject(aTemporaryFile);

  return NS_OK;
}

NS_IMETHODIMP
nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) {
  return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
}

NS_IMETHODIMP
nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
    nsIFile* aTemporaryFile) {
  return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
}

void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
    nsCOMArray<nsIFile>& fileList) {
  int32_t numEntries = fileList.Count();
  nsIFile* localFile;
  for (int32_t index = 0; index < numEntries; index++) {
    localFile = fileList[index];
    if (localFile) {
      // First make the file writable, since the temp file is probably readonly.
      localFile->SetPermissions(0600);
      localFile->Remove(false);
    }
  }

  fileList.Clear();
}

void nsExternalHelperAppService::ExpungeTemporaryFiles() {
  ExpungeTemporaryFilesHelper(mTemporaryFilesList);
}

void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
  ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
}

static const char kExternalWarningPrefPrefix[] =
    "network.protocol-handler.warn-external.";
static const char kExternalWarningDefaultPref[] =
    "network.protocol-handler.warn-external-default";

NS_IMETHODIMP
nsExternalHelperAppService::GetProtocolHandlerInfo(
    const nsACString& aScheme, nsIHandlerInfo** aHandlerInfo) {
  // XXX enterprise customers should be able to turn this support off with a
  // single master pref (maybe use one of the "exposed" prefs here?)

  bool exists;
  nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
  if (NS_FAILED(rv)) {
    // Either it knows nothing, or we ran out of memory
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIHandlerService> handlerSvc =
      do_GetService(NS_HANDLERSERVICE_CONTRACTID);
  if (handlerSvc) {
    bool hasHandler = false;
    (void)handlerSvc->Exists(*aHandlerInfo, &hasHandler);
    if (hasHandler) {
      rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, ""_ns);
      if (NS_SUCCEEDED(rv)) return NS_OK;
    }
  }

  return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
}

NS_IMETHODIMP
nsExternalHelperAppService::SetProtocolHandlerDefaults(
    nsIHandlerInfo* aHandlerInfo, bool aOSHandlerExists) {
  // this type isn't in our database, so we've only got an OS default handler,
  // if one exists

  if (aOSHandlerExists) {
    // we've got a default, so use it
    aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);

    // whether or not to ask the user depends on the warning preference
    nsAutoCString scheme;
    aHandlerInfo->GetType(scheme);

    nsAutoCString warningPref(kExternalWarningPrefPrefix);
    warningPref += scheme;
    bool warn;
    if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
      // no scheme-specific value, check the default
      warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
    }
    aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
  } else {
    // If no OS default existed, we set the preferred action to alwaysAsk.
    // This really means not initialized (i.e. there's no available handler)
    // to all the code...
    aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
  }

  return NS_OK;
}

// XPCOM profile change observer
NS_IMETHODIMP
nsExternalHelperAppService::Observe(nsISupports* aSubject, const char* aTopic,
                                    const char16_t* someData) {
  if (!strcmp(aTopic, "profile-before-change")) {
    ExpungeTemporaryFiles();
  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
    ExpungeTemporaryPrivateFiles();
  }
  return NS_OK;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
// begin external app handler implementation
//////////////////////////////////////////////////////////////////////////////////////////////////////

NS_IMPL_ADDREF(nsExternalAppHandler)
NS_IMPL_RELEASE(nsExternalAppHandler)

NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
  NS_INTERFACE_MAP_ENTRY(nsICancelable)
  NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
  NS_INTERFACE_MAP_ENTRY(nsINamed)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler)
NS_INTERFACE_MAP_END

nsExternalAppHandler::nsExternalAppHandler(
    nsIMIMEInfo* aMIMEInfo, const nsAString& aFileExtension,
    BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext,
    nsExternalHelperAppService* aExtProtSvc,
    const nsAString& aSuggestedFileName, uint32_t aReason, bool aForceSave)
    : mMimeInfo(aMIMEInfo),
      mBrowsingContext(aBrowsingContext),
      mWindowContext(aWindowContext),
      mSuggestedFileName(aSuggestedFileName),
      mForceSave(aForceSave),
      mForceSaveInternallyHandled(false),
      mCanceled(false),
      mStopRequestIssued(false),
      mIsFileChannel(false),
      mHandleInternally(false),
      mDialogShowing(false),
      mReason(aReason),
      mTempFileIsExecutable(false),
      mTimeDownloadStarted(0),
      mContentLength(-1),
      mProgress(0),
      mSaver(nullptr),
      mDialogProgressListener(nullptr),
      mTransfer(nullptr),
      mRequest(nullptr),
      mExtProtSvc(aExtProtSvc) {
  // make sure the extention includes the '.'
  if (!aFileExtension.IsEmpty() && aFileExtension.First() != '.') {
    mFileExtension = char16_t('.');
  }
  mFileExtension.Append(aFileExtension);

  mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
}

nsExternalAppHandler::~nsExternalAppHandler() {
  MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
}

void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
  MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
  // Remove our request from the child loadGroup
  RetargetLoadNotifications(request);
}

NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(
    nsIWebProgressListener2* aWebProgressListener) {
  // This is always called by nsHelperDlg.js. Go ahead and register the
  // progress listener. At this point, we don't have mTransfer.
  mDialogProgressListener = aWebProgressListener;
  return NS_OK;
}

NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) {
  if (mFinalFileDestination)
    *aTarget = mFinalFileDestination;
  else
    *aTarget = mTempFile;

  NS_IF_ADDREF(*aTarget);
  return NS_OK;
}

NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec) {
  // Use the real target if it's been set
  if (mFinalFileDestination) return mFinalFileDestination->IsExecutable(aExec);

  // Otherwise, use the stored executable-ness of the temporary
  *aExec = mTempFileIsExecutable;
  return NS_OK;
}

NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) {
  *aTime = mTimeDownloadStarted;
  return NS_OK;
}

NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t* aContentLength) {
  *aContentLength = mContentLength;
  return NS_OK;
}

NS_IMETHODIMP nsExternalAppHandler::GetBrowsingContextId(
    uint64_t* aBrowsingContextId) {
  *aBrowsingContextId = mBrowsingContext ? mBrowsingContext->Id() : 0;
  return NS_OK;
}

void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest* request) {
  // we are going to run the downloading of the helper app in our own little
  // docloader / load group context. so go ahead and force the creation of a
  // load group and doc loader for us to use...
  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  if (!aChannel) return;

  bool isPrivate = NS_UsePrivateBrowsing(aChannel);

  nsCOMPtr<nsILoadGroup> oldLoadGroup;
  aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));

  if (oldLoadGroup) {
    oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
  }

  aChannel->SetLoadGroup(nullptr);
  aChannel->SetNotificationCallbacks(nullptr);

  nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
  if (pbChannel) {
    pbChannel->SetPrivate(isPrivate);
  }
}

nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
  // First we need to try to get the destination directory for the temporary
  // file.
  auto res = GetInitialDownloadDirectory();
  if (res.isErr()) return res.unwrapErr();
  mTempFile = res.unwrap();

  // At this point, we do not have a filename for the temp file.  For security
  // purposes, this cannot be predictable, so we must use a cryptographic
  // quality PRNG to generate one.
  nsAutoCString tempLeafName;
  nsresult rv = GenerateRandomName(tempLeafName);
  NS_ENSURE_SUCCESS(rv, rv);

  // now append our extension.
  nsAutoCString ext;
  mMimeInfo->GetPrimaryExtension(ext);
  if (!ext.IsEmpty()) {
    ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
    if (ext.First() != '.') tempLeafName.Append('.');
    tempLeafName.Append(ext);
  }

  // We need to temporarily create a dummy file with the correct
  // file extension to determine the executable-ness, so do this before adding
  // the extra .part extension.
  nsCOMPtr<nsIFile> dummyFile;
  rv = mTempFile->Clone(getter_AddRefs(dummyFile));
  NS_ENSURE_SUCCESS(rv, rv);

  // Set the file name without .part
  rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
  NS_ENSURE_SUCCESS(rv, rv);

  // Store executable-ness then delete
  dummyFile->IsExecutable(&mTempFileIsExecutable);
  dummyFile->Remove(false);

  // Add an additional .part to prevent the OS from running this file in the
  // default application.
  tempLeafName.AppendLiteral(".part");

  rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
  // make this file unique!!!
  NS_ENSURE_SUCCESS(rv, rv);
  rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
  NS_ENSURE_SUCCESS(rv, rv);

  // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
  // This is a bit broken in the case when createUnique actually had to append
  // some numbers, because then we now have a filename like foo.bar-1.part and
  // we'll end up with foo.bar-1.bar as our final filename if we end up using
  // this.  But the other options are all bad too....  Ideally we'd have a way
  // to tell createUnique to put its unique marker before the extension that
  // comes before ".part" or something.
  rv = mTempFile->GetLeafName(mTempLeafName);
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, u".part"_ns),
                 NS_ERROR_UNEXPECTED);

  // Strip off the ".part" from mTempLeafName
  mTempLeafName.Truncate(mTempLeafName.Length() - std::size(".part") + 1);

  MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
  mSaver =
      do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mSaver->SetObserver(this);
  if (NS_FAILED(rv)) {
    mSaver = nullptr;
    return rv;
  }

  rv = mSaver->EnableSha256();
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mSaver->EnableSignatureInfo();
  NS_ENSURE_SUCCESS(rv, rv);
  LOG("Enabled hashing and signature verification");

  rv = mSaver->SetTarget(mTempFile, false);
  NS_ENSURE_SUCCESS(rv, rv);

  return rv;
}

void nsExternalAppHandler::MaybeApplyDecodingForExtension(
    nsIRequest* aRequest) {
  MOZ_ASSERT(aRequest);

  nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
  if (!encChannel) {
    return;
  }

  // Turn off content encoding conversions if needed
  bool applyConversion = true;

  // First, check to see if conversion is already disabled.  If so, we
  // have nothing to do here.
  encChannel->GetApplyConversion(&applyConversion);
  if (!applyConversion) {
    return;
  }

  nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
  if (sourceURL) {
    nsAutoCString extension;
    sourceURL->GetFileExtension(extension);
    if (!extension.IsEmpty()) {
      nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
      encChannel->GetContentEncodings(getter_AddRefs(encEnum));
      if (encEnum) {
        bool hasMore;
        nsresult rv = encEnum->HasMore(&hasMore);
        if (NS_SUCCEEDED(rv) && hasMore) {
          nsAutoCString encType;
          rv = encEnum->GetNext(encType);
          if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) {
            MOZ_ASSERT(mExtProtSvc);
            mExtProtSvc->ApplyDecodingForExtension(extension, encType,
                                                   &applyConversion);
          }
        }
      }
    }
  }

  encChannel->SetApplyConversion(applyConversion);
}

already_AddRefed<nsIInterfaceRequestor>
nsExternalAppHandler::GetDialogParent() {
  nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext;

  if (!dialogParent && mBrowsingContext) {
    dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
  }
  if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
    RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement();
    if (element) {
      dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
    }
  }
  return dialogParent.forget();
}

NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
  MOZ_ASSERT(request, "OnStartRequest without request?");

  // Set mTimeDownloadStarted here as the download has already started and
  // we want to record the start time before showing the filepicker.
  mTimeDownloadStarted = PR_Now();

  mRequest = request;

  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);

  nsresult rv;
  nsAutoCString MIMEType;
  if (mMimeInfo) {
    mMimeInfo->GetMIMEType(MIMEType);
  }
  // Now get the URI
  if (aChannel) {
    aChannel->GetURI(getter_AddRefs(mSourceUrl));
    // HTTPS-Only/HTTPS-FirstMode tries to upgrade connections to https. Once
    // the download is in progress we set that flag so that timeout counter
    // measures do not kick in.
    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
    if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin) ||
        nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
      uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
      httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS;
      loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
    }
  }

  if (!mForceSave && StaticPrefs::browser_download_enable_spam_prevention() &&
      IsDownloadSpam(aChannel)) {
    return NS_OK;
  }

  mDownloadClassification =
      nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType);

  if (mDownloadClassification == nsITransfer::DOWNLOAD_FORBIDDEN) {
    // If the download is rated as forbidden,
    // cancel the request so no ui knows about this.
    mCanceled = true;
    request->Cancel(NS_ERROR_ABORT);
    return NS_OK;
  }

  nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
  mIsFileChannel = fileChan != nullptr;
  if (!mIsFileChannel) {
    // It's possible that this request came from the child process and the
    // file channel actually lives there. If this returns true, then our
    // mSourceUrl will be an nsIFileURL anyway.
    nsCOMPtr<dom::nsIExternalHelperAppParent> parent(
        do_QueryInterface(request));
    mIsFileChannel = parent && parent->WasFileChannel();
  }

  // Get content length
  if (aChannel) {
    aChannel->GetContentLength(&mContentLength);
  }

  if (mBrowsingContext) {
    mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mBrowsingContext);

    // Determine whether a new window was opened specifically for this request
    if (aChannel) {
      nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
      mMaybeCloseWindowHelper->SetShouldCloseWindow(
          loadInfo->GetIsNewWindowTarget());
    }
  }

  // retarget all load notifications to our docloader instead of the original
  // window's docloader...
  RetargetLoadNotifications(request);

  // Close the underlying DOMWindow if it was opened specifically for the
  // download. We don't run this in the content process, since we have
  // an instance running in the parent as well, which will handle this
  // if needed.
  if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper) {
    mBrowsingContext = mMaybeCloseWindowHelper->MaybeCloseWindow();
  }

  // In an IPC setting, we're allowing the child process, here, to make
  // decisions about decoding the channel (e.g. decompression).  It will
  // still forward the decoded (uncompressed) data back to the parent.
  // Con: Uncompressed data means more IPC overhead.
  // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
  //       Parent process doesn't need to expect CPU time on decompression.
  MaybeApplyDecodingForExtension(aChannel);

  // At this point, the child process has done everything it can usefully do
  // for OnStartRequest.
  if (XRE_IsContentProcess()) {
    return NS_OK;
  }

  rv = SetUpTempFile(aChannel);
  if (NS_FAILED(rv)) {
    nsresult transferError = rv;

    rv = CreateFailedTransfer();
    if (NS_FAILED(rv)) {
      LOG("Failed to create transfer to report failure."
          "Will fallback to prompter!");
    }

    mCanceled = true;
    request->Cancel(transferError);

    auto res = GetInitialDownloadDirectory(true);
    if (res.isErr()) {
      // Just send the file name as we can't get a download path.
      // TODO: evaluate adding a more specific error here.
      SendStatusChange(kWriteError, transferError, request, mSuggestedFileName);
      return res.unwrapErr();
    }

    nsCOMPtr<nsIFile> pseudoFile = res.unwrap();
    MOZ_ALWAYS_SUCCEEDS(pseudoFile->Append(mSuggestedFileName));
    nsAutoString path;
    MOZ_ALWAYS_SUCCEEDS(pseudoFile->GetPath(path));
    SendStatusChange(kWriteError, transferError, request, path);

    return NS_OK;
  }

  // Inform channel it is open on behalf of a download to throttle it during
  // page loads and prevent its caching.
  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
  if (httpInternal) {
    rv = httpInternal->SetChannelIsForDownload(true);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  if (mSourceUrl->SchemeIs("data")) {
    // In case we're downloading a data:// uri
    // we don't want to apply AllowTopLevelNavigationToDataURI.
    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    loadInfo->SetForceAllowDataURI(true);
  }

  // now that the temp file is set up, find out if we need to invoke a dialog
  // asking the user what they want us to do with this content...

  // We can get here for three reasons: "can't handle", "sniffed type", or
  // "server sent content-disposition:attachment".  In the first case we want
  // to honor the user's "always ask" pref; in the other two cases we want to
  // honor it only if the default action is "save".  Opening attachments in
  // helper apps by default breaks some websites (especially if the attachment
  // is one part of a multipart document).  Opening sniffed content in helper
  // apps by default introduces security holes that we'd rather not have.

  // So let's find out whether the user wants to be prompted.  If he does not,
  // check mReason and the preferred action to see what we should do.

  bool alwaysAsk = true;
  mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
  if (alwaysAsk) {
    // But we *don't* ask if this mimeInfo didn't come from
    // our user configuration datastore and the user has said
    // at some point in the distant past that they don't
    // want to be asked.  The latter fact would have been
    // stored in pref strings back in the old days.

    bool mimeTypeIsInDatastore = false;
    nsCOMPtr<nsIHandlerService> handlerSvc =
        do_GetService(NS_HANDLERSERVICE_CONTRACTID);
    if (handlerSvc) {
      handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
    }
    if (!handlerSvc || !mimeTypeIsInDatastore) {
      if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF,
                                   MIMEType.get())) {
        // Don't need to ask after all.
        alwaysAsk = false;
        // Make sure action matches pref (save to disk).
        mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
      } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF,
                                          MIMEType.get())) {
        // Don't need to ask after all.
        alwaysAsk = false;
      }
    }
  } else if (MIMEType.EqualsLiteral("text/plain")) {
    nsAutoCString ext;
    mMimeInfo->GetPrimaryExtension(ext);
    // If people are sending us ApplicationReputation-eligible files with
    // text/plain mimetypes, enforce asking the user what to do.
    if (!ext.IsEmpty()) {
      nsAutoCString dummyFileName("f");
      if (ext.First() != '.') {
        dummyFileName.Append(".");
      }
      ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
      nsCOMPtr<nsIApplicationReputationService> appRep =
          components::ApplicationReputation::Service();
      appRep->IsBinary(dummyFileName + ext, &alwaysAsk);
    }
  }

  int32_t action = nsIMIMEInfo::saveToDisk;
  mMimeInfo->GetPreferredAction(&action);

  bool forcePrompt = mReason == nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;

  // OK, now check why we're here
  if (!alwaysAsk && forcePrompt) {
    // Force asking if we're not saving.  See comment back when we fetched the
    // alwaysAsk boolean for details.
    alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
  }

  bool shouldAutomaticallyHandleInternally =
      action == nsIMIMEInfo::handleInternally;

  if (aChannel) {
    uint32_t disposition = -1;
    aChannel->GetContentDisposition(&disposition);
    mForceSaveInternallyHandled =
        shouldAutomaticallyHandleInternally &&
        disposition == nsIChannel::DISPOSITION_ATTACHMENT &&
        mozilla::StaticPrefs::
            browser_download_force_save_internally_handled_attachments();
  }

  // If we're not asking, check we actually know what to do:
  if (!alwaysAsk) {
    alwaysAsk = action != nsIMIMEInfo::saveToDisk &&
                action != nsIMIMEInfo::useHelperApp &&
                action != nsIMIMEInfo::useSystemDefault &&
                !shouldAutomaticallyHandleInternally;
  }

  // If we're handling with the OS default and we are that default, force
  // asking, so we don't end up in an infinite loop:
  if (!alwaysAsk && action == nsIMIMEInfo::useSystemDefault) {
    bool areOSDefault = false;
    alwaysAsk = NS_SUCCEEDED(mMimeInfo->IsCurrentAppOSDefault(&areOSDefault)) &&
                areOSDefault;
  } else if (!alwaysAsk && action == nsIMIMEInfo::useHelperApp) {
    nsCOMPtr<nsIHandlerApp> preferredApp;
    mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(preferredApp));
    nsCOMPtr<nsILocalHandlerApp> handlerApp = do_QueryInterface(preferredApp);
    if (handlerApp) {
      nsCOMPtr<nsIFile> executable;
      handlerApp->GetExecutable(getter_AddRefs(executable));
      nsCOMPtr<nsIFile> ourselves;
      if (executable &&
          // Despite the name, this really just fetches an nsIFile...
          NS_SUCCEEDED(NS_GetSpecialDirectory(XRE_EXECUTABLE_FILE,
                                              getter_AddRefs(ourselves)))) {
        ourselves = nsMIMEInfoBase::GetCanonicalExecutable(ourselves);
        executable = nsMIMEInfoBase::GetCanonicalExecutable(executable);
        bool isSameApp = false;
        alwaysAsk =
            NS_FAILED(executable->Equals(ourselves, &isSameApp)) || isSameApp;
      }
    }
  }

  // if we were told that we _must_ save to disk without asking, all the stuff
  // before this is irrelevant; override it
  if (mForceSave || mForceSaveInternallyHandled) {
    alwaysAsk = false;
    action = nsIMIMEInfo::saveToDisk;
    shouldAutomaticallyHandleInternally = false;
  }
  // Additionally, if we are asked by the OS to open a local file,
  // automatically downloading it to create a second copy of that file doesn't
  // really make sense. We should ask the user what they want to do.
  if (mSourceUrl->SchemeIs("file") && !alwaysAsk &&
      action == nsIMIMEInfo::saveToDisk) {
    alwaysAsk = true;
  }

  // If adding new checks, make sure this is the last check before telemetry
  // and going ahead with opening the file!
#ifdef XP_WIN
  /* We need to see whether the file we've got here could be
   * executable.  If it could, we had better not try to open it!
   * We can skip this check, though, if we have a setting to open in a
   * helper app.
   */

  if (!alwaysAsk && action != nsIMIMEInfo::saveToDisk &&
      !shouldAutomaticallyHandleInternally) {
    nsCOMPtr<nsIHandlerApp> prefApp;
    mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
    if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
      nsCOMPtr<nsIFile> fileToTest;
      GetTargetFile(getter_AddRefs(fileToTest));
      if (fileToTest) {
        bool isExecutable;
        rv = fileToTest->IsExecutable(&isExecutable);
        if (NS_FAILED(rv) || mTempFileIsExecutable ||
            isExecutable) {  // checking NS_FAILED, because paranoia is good
          alwaysAsk = true;
        }
--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Normalansicht

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.