author | Sebastian Streich <sstreich@mozilla.com> |
Mon, 24 Aug 2020 15:29:23 +0000 | |
changeset 545813 | 4475d1e0186a31b81cf6b47aab19619219fc8b9e |
parent 545812 | d79afaa0590df7d3a739f5e3849c7a55965c6dca |
child 545814 | 7cc46e8968d78081910d53c6f4230184c161465c |
push id | 37727 |
push user | csabou@mozilla.com |
push date | Mon, 24 Aug 2020 21:50:21 +0000 |
treeherder | mozilla-central@ced8e8e8322c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ckerschb, Gijs |
bugs | 1656296 |
milestone | 81.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/downloads/DownloadsViewUI.jsm +++ b/browser/components/downloads/DownloadsViewUI.jsm @@ -534,16 +534,17 @@ DownloadsViewUI.DownloadElementShell.pro hover = { l10n: "downloads-show-more-information" }; } else { // This download was blocked temporarily by reputation check. In the // Downloads View, the interface depends on the threat severity. switch (verdict) { case Downloads.Error.BLOCK_VERDICT_UNCOMMON: this.showButton("askOpenOrRemoveFile"); break; + case Downloads.Error.BLOCK_VERDICT_INSECURE: case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED: this.showButton("askRemoveFileOrAllow"); break; default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE this.showButton("removeFile"); break; } @@ -621,16 +622,18 @@ DownloadsViewUI.DownloadElementShell.pro !this.download.error || !this.download.error.becauseBlockedByReputationCheck ) { return [null, null]; } switch (this.download.error.reputationCheckVerdict) { case Downloads.Error.BLOCK_VERDICT_UNCOMMON: return [s.blockedUncommon2, [s.unblockTypeUncommon2, s.unblockTip2]]; + case Downloads.Error.BLOCK_VERDICT_INSECURE: + return [s.blockedInsecure, [s.blockedInsecure, s.unblockTip2]]; case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED: return [ s.blockedPotentiallyUnwanted, [s.unblockTypePotentiallyUnwanted2, s.unblockTip2], ]; case Downloads.Error.BLOCK_VERDICT_MALWARE: return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]]; }
--- a/browser/components/downloads/test/browser/browser_downloads_panel_block.js +++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js @@ -3,16 +3,17 @@ add_task(async function mainTest() { await task_resetState(); let verdicts = [ Downloads.Error.BLOCK_VERDICT_UNCOMMON, Downloads.Error.BLOCK_VERDICT_MALWARE, Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + Downloads.Error.BLOCK_VERDICT_INSECURE, ]; await task_addDownloads(verdicts.map(v => makeDownload(v))); // Check that the richlistitem for each download is correct. for (let i = 0; i < verdicts.length; i++) { await openPanel(); // The current item is always the first one in the listbox since each
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.properties +++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties @@ -27,16 +27,17 @@ stateCompleted=Completed stateBlockedParentalControls=Blocked by Parental Controls # LOCALIZATION NOTE (blockedMalware, blockedPotentiallyUnwanted, # blockedUncommon2): # These strings are shown in the panel for some types of blocked downloads. You # may need to adjust "downloads.width" in "downloads.dtd" if this turns out to # be longer than the other existing status strings. blockedMalware=This file contains a virus or malware. blockedPotentiallyUnwanted=This file may harm your computer. +blockedInsecure = This file could not be downloaded securely. blockedUncommon2=This file is not commonly downloaded. # LOCALIZATION NOTE (fileMovedOrMissing): # Displayed when a complete download which is not at the original folder. fileMovedOrMissing=File moved or missing # LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen, # unblockTypeMalware, unblockTypePotentiallyUnwanted2,
--- a/dom/security/nsContentSecurityUtils.cpp +++ b/dom/security/nsContentSecurityUtils.cpp @@ -8,16 +8,17 @@ #include "nsContentSecurityUtils.h" #include "nsIContentSecurityPolicy.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" #include "nsIMultiPartChannel.h" #include "nsIURI.h" +#include "nsITransfer.h" #if defined(XP_WIN) # include "WinUtils.h" # include <wininet.h> #endif #include "js/Array.h" // JS::GetArrayLength #include "mozilla/ExtensionPolicyService.h" #include "mozilla/Logging.h" @@ -1110,17 +1111,17 @@ void nsContentSecurityUtils::LogMessageT return; } nsContentUtils::ReportToConsoleByWindowID( localizedMsg, nsIScriptError::warningFlag, "Security"_ns, windowID, uri); } /* static */ -bool nsContentSecurityUtils::IsDownloadAllowed( +long nsContentSecurityUtils::ClassifyDownload( nsIChannel* aChannel, const nsAutoCString& aMimeTypeGuess) { MOZ_ASSERT(aChannel, "IsDownloadAllowed without channel?"); nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); nsCOMPtr<nsIURI> contentLocation; aChannel->GetURI(getter_AddRefs(contentLocation)); @@ -1146,33 +1147,33 @@ bool nsContentSecurityUtils::IsDownloadA decission != nsIContentPolicy::ACCEPT); if (StaticPrefs::dom_block_download_insecure() && decission != nsIContentPolicy::ACCEPT) { nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); if (httpChannel) { LogMessageToConsole(httpChannel, "MixedContentBlockedDownload"); } - return false; + return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE; } if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { - return true; + return nsITransfer::DOWNLOAD_ACCEPTABLE; } if (!StaticPrefs::dom_block_download_in_sandboxed_iframes()) { - return true; + return nsITransfer::DOWNLOAD_ACCEPTABLE; } uint32_t triggeringFlags = loadInfo->GetTriggeringSandboxFlags(); uint32_t currentflags = loadInfo->GetSandboxFlags(); if ((triggeringFlags & SANDBOXED_ALLOW_DOWNLOADS) || (currentflags & SANDBOXED_ALLOW_DOWNLOADS)) { nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); if (httpChannel) { LogMessageToConsole(httpChannel, "IframeSandboxBlockedDownload"); } - return false; + return nsITransfer::DOWNLOAD_FORBIDDEN; } - return true; -} + return nsITransfer::DOWNLOAD_ACCEPTABLE; +} \ No newline at end of file
--- a/dom/security/nsContentSecurityUtils.h +++ b/dom/security/nsContentSecurityUtils.h @@ -46,26 +46,22 @@ class nsContentSecurityUtils { // Helper function which performs the following framing checks // * CSP frame-ancestors // * x-frame-options // If any of the two disallows framing, the channel will be cancelled. static void PerformCSPFrameAncestorAndXFOCheck(nsIChannel* aChannel); // Helper function to Check if a Download is allowed; - static bool IsDownloadAllowed(nsIChannel* aChannel, - const nsAutoCString& aMimeTypeGuess); + static long ClassifyDownload(nsIChannel* aChannel, + const nsAutoCString& aMimeTypeGuess); #if defined(DEBUG) static void AssertAboutPageHasCSP(mozilla::dom::Document* aDocument); #endif static bool ValidateScriptFilename(const char* aFilename, bool aIsSystemRealm); - /* - * Checks if a Channel should be able to proceed a download. - */ - static bool IsDownloadAllowed(nsIChannel* aChannel); // Helper Function to Post a message to the corresponding JS-Console static void LogMessageToConsole(nsIHttpChannel* aChannel, const char* aMsg); }; #endif /* nsContentSecurityUtils_h___ */
--- a/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js +++ b/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js @@ -1,8 +1,14 @@ +ChromeUtils.defineModuleGetter( + this, + "Downloads", + "resource://gre/modules/Downloads.jsm" +); + let INSECURE_BASE_URL = getRootDirectory(gTestPath).replace( "chrome://mochitests/content/", "http://example.com/" ) + "download_page.html"; let SECURE_BASE_URL = getRootDirectory(gTestPath).replace( "chrome://mochitests/content/", @@ -44,20 +50,51 @@ function shouldConsoleError() { Services.console.unregisterListener(listener); resolve(); } } Services.console.registerListener(listener); }); } +async function resetDownloads() { + // Removes all downloads from the download List + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + publicList.remove(download); + await download.finalize(true); + } +} + +async function shouldNotifyDownloadUI() { + // Waits until a Blocked download was added to the Download List + let list = await Downloads.getList(Downloads.ALL); + await new Promise(res => { + const view = { + onDownloadAdded: aDownload => { + let { error } = aDownload; + if ( + error.becauseBlockedByReputationCheck && + error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE + ) { + res(true); + list.removeView(view); + } + }, + }; + list.addView(view); + }); +} + async function runTest(url, link, checkFunction, decscription) { await SpecialPowers.pushPrefEnv({ set: [["dom.block_download_insecure", true]], }); + await resetDownloads(); let tab = BrowserTestUtils.addTab(gBrowser, url); gBrowser.selectedTab = tab; let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); info("Checking: " + decscription); @@ -87,17 +124,17 @@ add_task(async function() { INSECURE_BASE_URL, "secure", shouldPromptDownload, "Insecure -> Secure should download" ); await runTest( SECURE_BASE_URL, "insecure", - shouldConsoleError, + () => Promise.all([shouldNotifyDownloadUI(), shouldConsoleError()]), "Secure -> Insecure should Error" ); await runTest( SECURE_BASE_URL, "secure", shouldPromptDownload, "Secure -> Secure should Download" );
--- a/toolkit/components/downloads/DownloadCore.jsm +++ b/toolkit/components/downloads/DownloadCore.jsm @@ -1685,16 +1685,17 @@ var DownloadError = function(aProperties /** * These constants are used by the reputationCheckVerdict property and indicate * the detailed reason why a download is blocked. * * @note These values should not be changed because they can be serialized. */ DownloadError.BLOCK_VERDICT_MALWARE = "Malware"; DownloadError.BLOCK_VERDICT_POTENTIALLY_UNWANTED = "PotentiallyUnwanted"; +DownloadError.BLOCK_VERDICT_INSECURE = "Insecure"; DownloadError.BLOCK_VERDICT_UNCOMMON = "Uncommon"; DownloadError.prototype = { __proto__: Error.prototype, /** * The result code associated with this error. */
--- a/toolkit/components/downloads/DownloadLegacy.jsm +++ b/toolkit/components/downloads/DownloadLegacy.jsm @@ -14,16 +14,21 @@ "use strict"; ChromeUtils.defineModuleGetter( this, "Downloads", "resource://gre/modules/Downloads.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "DownloadError", + "resource://gre/modules/DownloadCore.jsm" +); /** * nsITransfer implementation that provides a bridge to a Download object. * * Legacy downloads work differently than the JavaScript implementation. In the * latter, the caller only provides the properties for the Download object and * the entire process is handled by the "start" method. In the legacy * implementation, the caller must create a separate object to execute the @@ -264,40 +269,43 @@ DownloadLegacyTransfer.prototype = { init: function DLT_init( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aTempFile, aCancelable, - aIsPrivate + aIsPrivate, + aDownloadClassification ) { return this._nsITransferInitInternal( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aTempFile, aCancelable, - aIsPrivate + aIsPrivate, + aDownloadClassification ); }, // nsITransfer initWithBrowsingContext( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aTempFile, aCancelable, aIsPrivate, + aDownloadClassification, aBrowsingContext, aHandleInternally ) { let browsingContextId; let userContextId; if (aBrowsingContext && aBrowsingContext.currentWindowGlobal) { browsingContextId = aBrowsingContext.id; let windowGlobal = aBrowsingContext.currentWindowGlobal; @@ -308,36 +316,42 @@ DownloadLegacyTransfer.prototype = { aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aTempFile, aCancelable, aIsPrivate, + aDownloadClassification, userContextId, browsingContextId, aHandleInternally ); }, _nsITransferInitInternal( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aTempFile, aCancelable, isPrivate, + aDownloadClassification, userContextId = 0, browsingContextId = 0, handleInternally = false ) { - this._cancelable = aCancelable; + if (aDownloadClassification == Ci.nsITransfer.DOWNLOAD_ACCEPTABLE) { + // Only keep a refrence if it acceptable, as the download was canceled + // already if it isn't. + this._cancelable = aCancelable; + } let launchWhenSucceeded = false, contentType = null, launcherPath = null; if (aMIMEInfo instanceof Ci.nsIMIMEInfo) { launchWhenSucceeded = aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk; @@ -346,54 +360,70 @@ DownloadLegacyTransfer.prototype = { let appHandler = aMIMEInfo.preferredApplicationHandler; if ( aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp && appHandler instanceof Ci.nsILocalHandlerApp ) { launcherPath = appHandler.executable.path; } } - // Create a new Download object associated to a DownloadLegacySaver, and // wait for it to be available. This operation may cause the entire // download system to initialize before the object is created. - Downloads.createDownload({ + let serialisedDownload = { source: { url: aSource.spec, isPrivate, userContextId, browsingContextId, }, target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path, partFilePath: aTempFile && aTempFile.path, }, saver: "legacy", launchWhenSucceeded, contentType, launcherPath, handleInternally, - }) - .then(aDownload => { + }; + + // In case the Download was classified as insecure/dangerous + // it is already canceled, so we need to generate and attach the + // corresponding error to the download. + if (aDownloadClassification == Ci.nsITransfer.DOWNLOAD_POTENTIALLY_UNSAFE) { + serialisedDownload.errorObj = { + becauseBlockedByReputationCheck: true, + reputationCheckVerdict: DownloadError.BLOCK_VERDICT_INSECURE, + }; + } + + Downloads.createDownload(serialisedDownload) + .then(async aDownload => { // Legacy components keep partial data when they use a ".part" file. if (aTempFile) { aDownload.tryToKeepPartialData = true; } // Start the download before allowing it to be controlled. Ignore errors. aDownload.start().catch(() => {}); // Start processing all the other events received through nsITransfer. this._download = aDownload; this._resolveDownload(aDownload); // Add the download to the list, allowing it to be seen and canceled. - return Downloads.getList(Downloads.ALL).then(list => - list.add(aDownload) - ); + await (await Downloads.getList(Downloads.ALL)).add(aDownload); + if (serialisedDownload.errorObj) { + // In case we added an already canceled dummy download + // we need to manually trigger a change event + // as all the animations for finishing downloads are + // listening on onChange. + aDownload._notifyChange(); + } }) .catch(Cu.reportError); }, setSha256Hash(hash) { this._sha256Hash = hash; },
--- a/toolkit/components/downloads/test/unit/head.js +++ b/toolkit/components/downloads/test/unit/head.js @@ -402,17 +402,18 @@ function promiseStartLegacyDownload(aSou transfer.init( sourceURI, NetUtil.newURI(targetFile), null, mimeInfo, null, null, persist, - isPrivate + isPrivate, + Ci.nsITransfer.DOWNLOAD_ACCEPTABLE ); persist.progressListener = transfer; // Start the actual download process. persist.savePrivacyAwareURI( sourceURI, Services.scriptSecurityManager.getSystemPrincipal(), 0,
--- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -420,17 +420,18 @@ function internalPersist(persistArgs) { tr.init( persistArgs.sourceURI, targetFileURL, "", null, null, null, persist, - persistArgs.isPrivate + persistArgs.isPrivate, + Ci.nsITransfer.DOWNLOAD_ACCEPTABLE ); persist.progressListener = new DownloadListener(window, tr); if (persistArgs.sourceDocument) { // Saving a Document, not a URI: var filesFolder = null; if (persistArgs.targetContentType != "text/plain") { // Create the local directory into which to save associated files.
--- a/uriloader/base/nsITransfer.idl +++ b/uriloader/base/nsITransfer.idl @@ -10,16 +10,20 @@ interface nsIURI; interface nsICancelable; interface nsIMIMEInfo; interface nsIFile; webidl BrowsingContext; [scriptable, uuid(37ec75d3-97ad-4da8-afaa-eabe5b4afd73)] interface nsITransfer : nsIWebProgressListener2 { + const unsigned long DOWNLOAD_ACCEPTABLE = 0; + const unsigned long DOWNLOAD_FORBIDDEN = 1; + const unsigned long DOWNLOAD_POTENTIALLY_UNSAFE = 2; + /** * Initializes the transfer with certain properties. This function must * be called prior to accessing any properties on this interface. * * @param aSource The source URI of the transfer. Must not be null. * * @param aTarget The target URI of the transfer. Must not be null. * @@ -46,25 +50,28 @@ interface nsITransfer : nsIWebProgressLi * reference to this object until the download is * finished, at which point they should release the * reference. * * @param aIsPrivate Used to determine the privacy status of the new transfer. * If true, indicates that the transfer was initiated from * a source that desires privacy. * + * @param aDownloadClassification Indicates wheter the dowload is unwanted, + * should be considered dangerous or insecure. */ void init(in nsIURI aSource, in nsIURI aTarget, in AString aDisplayName, in nsIMIMEInfo aMIMEInfo, in PRTime startTime, in nsIFile aTempFile, in nsICancelable aCancelable, - in boolean aIsPrivate); + in boolean aIsPrivate, + in long aDownloadClassification); /** * Same as init, but allows for passing the browsingContext * which will allow for opening the download with the same * userContextId * * @param aBrowsingContext BrowsingContext of the initiating document. * @@ -74,16 +81,17 @@ interface nsITransfer : nsIWebProgressLi void initWithBrowsingContext(in nsIURI aSource, in nsIURI aTarget, in AString aDisplayName, in nsIMIMEInfo aMIMEInfo, in PRTime startTime, in nsIFile aTempFile, in nsICancelable aCancelable, in boolean aIsPrivate, + in long aDownloadClassification, in BrowsingContext aBrowsingContext, in boolean aHandleInternally); /* * Used to notify the transfer object of the hash of the downloaded file. * Must be called on the main thread, only after the download has finished * successfully. * @param aHash The SHA-256 hash in raw bytes of the downloaded file.
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -1576,20 +1576,32 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); nsresult rv; nsAutoCString MIMEType; if (mMimeInfo) { mMimeInfo->GetMIMEType(MIMEType); } - - if (!nsContentSecurityUtils::IsDownloadAllowed(aChannel, MIMEType)) { + // Now get the URI + if (aChannel) { + aChannel->GetURI(getter_AddRefs(mSourceUrl)); + } + + mDownloadClassification = + nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType); + if (mDownloadClassification != nsITransfer::DOWNLOAD_ACCEPTABLE) { + // If the download is rated as forbidden, + // we need to silently cancel the request to make sure + // it wont show up in the download ui. mCanceled = true; request->Cancel(NS_ERROR_ABORT); + if (mDownloadClassification != nsITransfer::DOWNLOAD_FORBIDDEN) { + CreateFailedTransfer(); + } 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 @@ -1613,21 +1625,16 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt bool tmp = false; if (NS_SUCCEEDED( props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp))) { mMaybeCloseWindowHelper->SetShouldCloseWindow(tmp); } } } - // Now get the URI - if (aChannel) { - aChannel->GetURI(getter_AddRefs(mSourceUrl)); - } - // 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. @@ -2178,21 +2185,22 @@ nsresult nsExternalAppHandler::CreateTra rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); if (mBrowsingContext) { rv = transfer->InitWithBrowsingContext( mSourceUrl, target, EmptyString(), mMimeInfo, mTimeDownloadStarted, mTempFile, this, channel && NS_UsePrivateBrowsing(channel), - mBrowsingContext, mHandleInternally); + mDownloadClassification, mBrowsingContext, mHandleInternally); } else { rv = transfer->Init(mSourceUrl, target, EmptyString(), mMimeInfo, mTimeDownloadStarted, mTempFile, this, - channel && NS_UsePrivateBrowsing(channel)); + channel && NS_UsePrivateBrowsing(channel), + mDownloadClassification); } NS_ENSURE_SUCCESS(rv, rv); // If we were cancelled since creating the transfer, just return. It is // always ok to return NS_OK if we are cancelled. Callers of this function // must call Cancel if CreateTransfer fails, but there's no need to cancel // twice. @@ -2246,22 +2254,23 @@ nsresult nsExternalAppHandler::CreateFai rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); if (mBrowsingContext) { rv = transfer->InitWithBrowsingContext( mSourceUrl, pseudoTarget, EmptyString(), mMimeInfo, mTimeDownloadStarted, nullptr, this, - channel && NS_UsePrivateBrowsing(channel), mBrowsingContext, - mHandleInternally); + channel && NS_UsePrivateBrowsing(channel), mDownloadClassification, + mBrowsingContext, mHandleInternally); } else { rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(), mMimeInfo, mTimeDownloadStarted, nullptr, this, - channel && NS_UsePrivateBrowsing(channel)); + channel && NS_UsePrivateBrowsing(channel), + mDownloadClassification); } NS_ENSURE_SUCCESS(rv, rv); // Our failed transfer is ready. mTransfer = std::move(transfer); return NS_OK; }
--- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -337,16 +337,23 @@ class nsExternalAppHandler final : publi /** * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the * reason the dialog was shown (unknown content type, server requested it, * etc). */ uint32_t mReason; /** + * Indicates if the nsContentSecurityUtils rate this download as + * acceptable, potentialy unwanted or illigal request. + * + */ + int32_t mDownloadClassification; + + /** * Track the executable-ness of the temporary file. */ bool mTempFileIsExecutable; PRTime mTimeDownloadStarted; int64_t mContentLength; int64_t mProgress; /**< Number of bytes received (for sending progress notifications). */