Bug 1965504 part 5 - integrate Content Analysis into Downloads a=diannaS
The actual call to content analysis is in DownloadCore.sys.mjs's _checkReputationAndMove() method. Original Revision: https://phabricator.services.mozilla.com/D251881 Differential Revision: https://phabricator.services.mozilla.com/D258207
This commit is contained in:
committed by
dsmith@mozilla.com
parent
143dc7420f
commit
099ed944b9
@@ -143,6 +143,7 @@ export var DownloadsCommon = {
|
|||||||
DOWNLOAD_BLOCKED_PARENTAL: 6,
|
DOWNLOAD_BLOCKED_PARENTAL: 6,
|
||||||
DOWNLOAD_DIRTY: 8,
|
DOWNLOAD_DIRTY: 8,
|
||||||
DOWNLOAD_BLOCKED_POLICY: 9,
|
DOWNLOAD_BLOCKED_POLICY: 9,
|
||||||
|
DOWNLOAD_BLOCKED_CONTENT_ANALYSIS: 10,
|
||||||
|
|
||||||
// The following are the possible values of the "attention" property.
|
// The following are the possible values of the "attention" property.
|
||||||
ATTENTION_NONE: "",
|
ATTENTION_NONE: "",
|
||||||
@@ -295,6 +296,18 @@ export var DownloadsCommon = {
|
|||||||
if (download.error.becauseBlockedByReputationCheck) {
|
if (download.error.becauseBlockedByReputationCheck) {
|
||||||
return DownloadsCommon.DOWNLOAD_DIRTY;
|
return DownloadsCommon.DOWNLOAD_DIRTY;
|
||||||
}
|
}
|
||||||
|
if (download.error.becauseBlockedByContentAnalysis) {
|
||||||
|
// BLOCK_VERDICT_MALWARE indicates that the download was
|
||||||
|
// blocked by the content analysis service, so return
|
||||||
|
// DOWNLOAD_BLOCKED_CONTENT_ANALYSIS to indicate this.
|
||||||
|
// Otherwise, the content analysis service returned
|
||||||
|
// WARN, so the user has a chance to unblock the download,
|
||||||
|
// which corresponds with DOWNLOAD_DIRTY.
|
||||||
|
return download.error.reputationCheckVerdict ===
|
||||||
|
lazy.Downloads.Error.BLOCK_VERDICT_MALWARE
|
||||||
|
? DownloadsCommon.DOWNLOAD_BLOCKED_CONTENT_ANALYSIS
|
||||||
|
: DownloadsCommon.DOWNLOAD_DIRTY;
|
||||||
|
}
|
||||||
return DownloadsCommon.DOWNLOAD_FAILED;
|
return DownloadsCommon.DOWNLOAD_FAILED;
|
||||||
}
|
}
|
||||||
if (download.canceled) {
|
if (download.canceled) {
|
||||||
@@ -311,7 +324,7 @@ export var DownloadsCommon = {
|
|||||||
*/
|
*/
|
||||||
async deleteDownload(download) {
|
async deleteDownload(download) {
|
||||||
// Check hasBlockedData to avoid double counting if you click the X button
|
// Check hasBlockedData to avoid double counting if you click the X button
|
||||||
// in the Libarary view and then delete the download from the history.
|
// in the Library view and then delete the download from the history.
|
||||||
if (
|
if (
|
||||||
download.error?.becauseBlockedByReputationCheck &&
|
download.error?.becauseBlockedByReputationCheck &&
|
||||||
download.hasBlockedData
|
download.hasBlockedData
|
||||||
@@ -331,6 +344,9 @@ export var DownloadsCommon = {
|
|||||||
}
|
}
|
||||||
let list = await lazy.Downloads.getList(lazy.Downloads.ALL);
|
let list = await lazy.Downloads.getList(lazy.Downloads.ALL);
|
||||||
await list.remove(download);
|
await list.remove(download);
|
||||||
|
if (download.error?.becauseBlockedByContentAnalysis) {
|
||||||
|
await download.respondToContentAnalysisWarnWithBlock();
|
||||||
|
}
|
||||||
await download.finalize(true);
|
await download.finalize(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -359,6 +375,9 @@ export var DownloadsCommon = {
|
|||||||
await list.remove(download);
|
await list.remove(download);
|
||||||
}
|
}
|
||||||
await download.manuallyRemoveData();
|
await download.manuallyRemoveData();
|
||||||
|
if (download.error?.becauseBlockedByContentAnalysis) {
|
||||||
|
await download.respondToContentAnalysisWarnWithBlock();
|
||||||
|
}
|
||||||
if (clearHistory < 2) {
|
if (clearHistory < 2) {
|
||||||
lazy.DownloadHistory.updateMetaData(download).catch(console.error);
|
lazy.DownloadHistory.updateMetaData(download).catch(console.error);
|
||||||
}
|
}
|
||||||
@@ -629,6 +648,8 @@ export var DownloadsCommon = {
|
|||||||
* the "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown
|
* the "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown
|
||||||
* reason is specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is
|
* reason is specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is
|
||||||
* assumed.
|
* assumed.
|
||||||
|
* becauseBlockedByReputationCheck:
|
||||||
|
* Whether the the download was blocked by a reputation check.
|
||||||
* window:
|
* window:
|
||||||
* The window with which this action is associated.
|
* The window with which this action is associated.
|
||||||
* dialogType:
|
* dialogType:
|
||||||
@@ -645,7 +666,12 @@ export var DownloadsCommon = {
|
|||||||
* - "confirmBlock" to delete the blocked data permanently.
|
* - "confirmBlock" to delete the blocked data permanently.
|
||||||
* - "cancel" to do nothing and cancel the operation.
|
* - "cancel" to do nothing and cancel the operation.
|
||||||
*/
|
*/
|
||||||
async confirmUnblockDownload({ verdict, window, dialogType }) {
|
async confirmUnblockDownload({
|
||||||
|
verdict,
|
||||||
|
becauseBlockedByReputationCheck,
|
||||||
|
window,
|
||||||
|
dialogType,
|
||||||
|
}) {
|
||||||
let s = DownloadsCommon.strings;
|
let s = DownloadsCommon.strings;
|
||||||
|
|
||||||
// All the dialogs have an action button and a cancel button, while only
|
// All the dialogs have an action button and a cancel button, while only
|
||||||
@@ -685,12 +711,18 @@ export var DownloadsCommon = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
|
let tip = s.unblockTip2;
|
||||||
switch (verdict) {
|
switch (verdict) {
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
case lazy.Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||||
message = s.unblockTypeUncommon2;
|
message = s.unblockTypeUncommon2;
|
||||||
break;
|
break;
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||||
message = s.unblockTypePotentiallyUnwanted2;
|
if (becauseBlockedByReputationCheck) {
|
||||||
|
message = s.unblockTypePotentiallyUnwanted2;
|
||||||
|
} else {
|
||||||
|
message = s.unblockTypeContentAnalysisWarn;
|
||||||
|
tip = s.unblockContentAnalysisTip;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_INSECURE:
|
case lazy.Downloads.Error.BLOCK_VERDICT_INSECURE:
|
||||||
message = s.unblockInsecure2;
|
message = s.unblockInsecure2;
|
||||||
@@ -700,7 +732,7 @@ export var DownloadsCommon = {
|
|||||||
message = s.unblockTypeMalware;
|
message = s.unblockTypeMalware;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
message += "\n\n" + s.unblockTip2;
|
message += "\n\n" + tip;
|
||||||
|
|
||||||
Services.ww.registerNotification(function onOpen(subj, topic) {
|
Services.ww.registerNotification(function onOpen(subj, topic) {
|
||||||
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
|
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
|
||||||
@@ -860,7 +892,10 @@ DownloadsDataCtor.prototype = {
|
|||||||
download,
|
download,
|
||||||
DownloadsCommon.stateOfDownload(download)
|
DownloadsCommon.stateOfDownload(download)
|
||||||
);
|
);
|
||||||
if (download.error?.becauseBlockedByReputationCheck) {
|
if (
|
||||||
|
download.error?.becauseBlockedByReputationCheck ||
|
||||||
|
download.error?.becauseBlockedByContentAnalysis
|
||||||
|
) {
|
||||||
this._notifyDownloadEvent("error");
|
this._notifyDownloadEvent("error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -770,7 +770,10 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
lazy.DownloadsCommon.strings.stateBlockedParentalControls
|
lazy.DownloadsCommon.strings.stateBlockedParentalControls
|
||||||
);
|
);
|
||||||
this.hideButton();
|
this.hideButton();
|
||||||
} else if (this.download.error.becauseBlockedByReputationCheck) {
|
} else if (
|
||||||
|
this.download.error.becauseBlockedByReputationCheck ||
|
||||||
|
this.download.error.becauseBlockedByContentAnalysis
|
||||||
|
) {
|
||||||
verdict = this.download.error.reputationCheckVerdict;
|
verdict = this.download.error.reputationCheckVerdict;
|
||||||
let hover = "";
|
let hover = "";
|
||||||
if (!this.download.hasBlockedData) {
|
if (!this.download.hasBlockedData) {
|
||||||
@@ -882,7 +885,8 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
let s = lazy.DownloadsCommon.strings;
|
let s = lazy.DownloadsCommon.strings;
|
||||||
if (
|
if (
|
||||||
!this.download.error ||
|
!this.download.error ||
|
||||||
!this.download.error.becauseBlockedByReputationCheck
|
(!this.download.error.becauseBlockedByReputationCheck &&
|
||||||
|
!this.download.error.becauseBlockedByContentAnalysis)
|
||||||
) {
|
) {
|
||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
@@ -895,14 +899,37 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
[s.unblockInsecure2, s.unblockTip2],
|
[s.unblockInsecure2, s.unblockTip2],
|
||||||
];
|
];
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||||
|
if (this.download.error.becauseBlockedByReputationCheck) {
|
||||||
|
return [
|
||||||
|
s.blockedPotentiallyUnwanted,
|
||||||
|
[s.unblockTypePotentiallyUnwanted2, s.unblockTip2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (!this.download.error.becauseBlockedByContentAnalysis) {
|
||||||
|
// We expect one of becauseBlockedByReputationCheck or
|
||||||
|
// becauseBlockedByContentAnalysis to be true; if not,
|
||||||
|
// fall through to the error case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
s.blockedPotentiallyUnwanted,
|
s.warnedByContentAnalysis,
|
||||||
[s.unblockTypePotentiallyUnwanted2, s.unblockTip2],
|
[s.unblockTypeContentAnalysisWarn, s.unblockContentAnalysisWarnTip],
|
||||||
];
|
];
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_MALWARE:
|
case lazy.Downloads.Error.BLOCK_VERDICT_MALWARE:
|
||||||
return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
|
if (this.download.error.becauseBlockedByReputationCheck) {
|
||||||
|
return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
|
||||||
case lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM:
|
}
|
||||||
|
if (!this.download.error.becauseBlockedByContentAnalysis) {
|
||||||
|
// We expect one of becauseBlockedByReputationCheck or
|
||||||
|
// becauseBlockedByContentAnalysis to be true; if not,
|
||||||
|
// fall through to the error case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
s.blockedByContentAnalysis,
|
||||||
|
[s.unblockContentAnalysis1, s.unblockContentAnalysis2],
|
||||||
|
];
|
||||||
|
case lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM: {
|
||||||
let title = {
|
let title = {
|
||||||
id: "downloads-files-not-downloaded",
|
id: "downloads-files-not-downloaded",
|
||||||
args: {
|
args: {
|
||||||
@@ -914,6 +941,7 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
args: { url: DownloadsViewUI.getStrippedUrl(this.download) },
|
args: { url: DownloadsViewUI.getStrippedUrl(this.download) },
|
||||||
};
|
};
|
||||||
return [{ l10n: title }, [{ l10n: details }, null]];
|
return [{ l10n: title }, [{ l10n: details }, null]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unexpected reputationCheckVerdict: " +
|
"Unexpected reputationCheckVerdict: " +
|
||||||
@@ -944,6 +972,8 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
confirmUnblock(window, dialogType) {
|
confirmUnblock(window, dialogType) {
|
||||||
lazy.DownloadsCommon.confirmUnblockDownload({
|
lazy.DownloadsCommon.confirmUnblockDownload({
|
||||||
verdict: this.download.error.reputationCheckVerdict,
|
verdict: this.download.error.reputationCheckVerdict,
|
||||||
|
becauseBlockedByReputationCheck:
|
||||||
|
this.download.error.becauseBlockedByReputationCheck,
|
||||||
window,
|
window,
|
||||||
dialogType,
|
dialogType,
|
||||||
})
|
})
|
||||||
@@ -990,6 +1020,7 @@ DownloadsViewUI.DownloadElementShell.prototype = {
|
|||||||
case lazy.DownloadsCommon.DOWNLOAD_FINISHED:
|
case lazy.DownloadsCommon.DOWNLOAD_FINISHED:
|
||||||
return "downloadsCmd_open";
|
return "downloadsCmd_open";
|
||||||
case lazy.DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:
|
case lazy.DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:
|
||||||
|
case lazy.DownloadsCommon.DOWNLOAD_BLOCKED_CONTENT_ANALYSIS:
|
||||||
return "downloadsCmd_openReferrer";
|
return "downloadsCmd_openReferrer";
|
||||||
case lazy.DownloadsCommon.DOWNLOAD_DIRTY:
|
case lazy.DownloadsCommon.DOWNLOAD_DIRTY:
|
||||||
return "downloadsCmd_showBlockedInfo";
|
return "downloadsCmd_showBlockedInfo";
|
||||||
|
|||||||
@@ -41,9 +41,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-state:not([state="6"],/* Blocked (parental) */
|
.download-state:not([state="6"], /* Blocked (parental) */
|
||||||
[state="8"],/* Blocked (dirty) */
|
[state="8"], /* Blocked (dirty) */
|
||||||
[state="9"] /* Blocked (policy) */)
|
[state="9"], /* Blocked (policy) */
|
||||||
|
[state="10"] /* Blocked (content analysis) */)
|
||||||
.downloadBlockedBadge,
|
.downloadBlockedBadge,
|
||||||
|
|
||||||
.download-state:not([state="-1"],/* Starting (initial) */
|
.download-state:not([state="-1"],/* Starting (initial) */
|
||||||
|
|||||||
@@ -1710,6 +1710,14 @@ var DownloadsBlockedSubview = {
|
|||||||
let e = this.elements;
|
let e = this.elements;
|
||||||
let s = DownloadsCommon.strings;
|
let s = DownloadsCommon.strings;
|
||||||
|
|
||||||
|
e.deleteButton.hidden =
|
||||||
|
download.error?.becauseBlockedByContentAnalysis &&
|
||||||
|
download.error?.reputationCheckVerdict === "Malware";
|
||||||
|
|
||||||
|
e.unblockButton.hidden =
|
||||||
|
download.error?.becauseBlockedByContentAnalysis &&
|
||||||
|
download.error?.reputationCheckVerdict === "Malware";
|
||||||
|
|
||||||
title.l10n
|
title.l10n
|
||||||
? document.l10n.setAttributes(e.title, title.l10n.id, title.l10n.args)
|
? document.l10n.setAttributes(e.title, title.l10n.id, title.l10n.args)
|
||||||
: (e.title.textContent = title);
|
: (e.title.textContent = title);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ blockedMalware=This file contains a virus or malware.
|
|||||||
blockedPotentiallyUnwanted=This file may harm your computer.
|
blockedPotentiallyUnwanted=This file may harm your computer.
|
||||||
blockedPotentiallyInsecure=File not downloaded: Potential security risk.
|
blockedPotentiallyInsecure=File not downloaded: Potential security risk.
|
||||||
blockedUncommon2=This file is not commonly downloaded.
|
blockedUncommon2=This file is not commonly downloaded.
|
||||||
|
blockedByContentAnalysis=This file was blocked by your organization.
|
||||||
|
warnedByContentAnalysis=This download may be unsafe and requires confirmation.
|
||||||
|
|
||||||
# LOCALIZATION NOTE (fileMovedOrMissing):
|
# LOCALIZATION NOTE (fileMovedOrMissing):
|
||||||
# Displayed when a complete download which is not at the original folder.
|
# Displayed when a complete download which is not at the original folder.
|
||||||
@@ -46,8 +48,10 @@ fileDeleted=File deleted
|
|||||||
|
|
||||||
# LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen,
|
# LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen,
|
||||||
# unblockTypeMalware, unblockTypePotentiallyUnwanted2,
|
# unblockTypeMalware, unblockTypePotentiallyUnwanted2,
|
||||||
# unblockTypeUncommon2, unblockTip2, unblockButtonOpen,
|
# unblockTypeContentAnalysisWarn, unblockTypeUncommon2,
|
||||||
# unblockButtonUnblock, unblockButtonConfirmBlock, unblockInsecure2):
|
# unblockTip2, unblockContentAnalysisWarnTip, unblockButtonOpen,
|
||||||
|
# unblockButtonUnblock, unblockButtonConfirmBlock, unblockInsecure2,
|
||||||
|
# unblockContentAnalysis1, unblockContentAnalysis2, unblock):
|
||||||
# These strings are displayed in the dialog shown when the user asks a blocked
|
# These strings are displayed in the dialog shown when the user asks a blocked
|
||||||
# download to be unblocked. The severity of the threat is expressed in
|
# download to be unblocked. The severity of the threat is expressed in
|
||||||
# descending order by the unblockType strings, it is higher for files detected
|
# descending order by the unblockType strings, it is higher for files detected
|
||||||
@@ -56,12 +60,17 @@ unblockHeaderUnblock=Are you sure you want to allow this download?
|
|||||||
unblockHeaderOpen=Are you sure you want to open this file?
|
unblockHeaderOpen=Are you sure you want to open this file?
|
||||||
unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
|
unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
|
||||||
unblockTypePotentiallyUnwanted2=This file is disguised as a helpful download, but it can make unexpected changes to your programs and settings.
|
unblockTypePotentiallyUnwanted2=This file is disguised as a helpful download, but it can make unexpected changes to your programs and settings.
|
||||||
|
unblockTypeContentAnalysisWarn=Your organization uses data-loss prevention software that has flagged this content as unsafe.
|
||||||
unblockTypeUncommon2=This file is not commonly downloaded and may not be safe to open. It may contain a virus or make unexpected changes to your programs and settings.
|
unblockTypeUncommon2=This file is not commonly downloaded and may not be safe to open. It may contain a virus or make unexpected changes to your programs and settings.
|
||||||
unblockInsecure2=The download is offered over HTTP even though the current document was delivered over a secure HTTPS connection. If you proceed, the download may be corrupted or tampered with during the download process.
|
unblockInsecure2=The download is offered over HTTP even though the current document was delivered over a secure HTTPS connection. If you proceed, the download may be corrupted or tampered with during the download process.
|
||||||
unblockTip2=You can search for an alternate download source or try again later.
|
unblockTip2=You can search for an alternate download source or try again later.
|
||||||
|
unblockContentAnalysisWarnTip=You can choose to download it anyway.
|
||||||
unblockButtonOpen=Open
|
unblockButtonOpen=Open
|
||||||
unblockButtonUnblock=Allow download
|
unblockButtonUnblock=Allow download
|
||||||
unblockButtonConfirmBlock=Remove file
|
unblockButtonConfirmBlock=Remove file
|
||||||
|
unblockContentAnalysis1=Your organization uses data-loss prevention software that has blocked this download.
|
||||||
|
unblockContentAnalysis2=This decision can only be overridden by your organization.
|
||||||
|
unblockContentAnalysisTip=You may continue your download or cancel it.
|
||||||
|
|
||||||
# LOCALIZATION NOTE (sizeWithUnits):
|
# LOCALIZATION NOTE (sizeWithUnits):
|
||||||
# %1$S is replaced with the size number, and %2$S with the measurement unit.
|
# %1$S is replaced with the size number, and %2$S with the measurement unit.
|
||||||
|
|||||||
@@ -370,10 +370,13 @@ Download.prototype = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.error && this.error.becauseBlockedByReputationCheck) {
|
if (
|
||||||
|
this.error?.becauseBlockedByReputationCheck ||
|
||||||
|
this.error?.becauseBlockedByContentAnalysis
|
||||||
|
) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new DownloadError({
|
new DownloadError({
|
||||||
message: "Cannot start after being blocked by a reputation check.",
|
message: "Cannot start after being blocked by a safety check.",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -708,6 +711,10 @@ Download.prototype = {
|
|||||||
return this._promiseUnblock;
|
return this._promiseUnblock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.error?.becauseBlockedByContentAnalysis) {
|
||||||
|
this.respondToContentAnalysisWarnWithAllow();
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.hasBlockedData) {
|
if (!this.hasBlockedData) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("unblock may only be called on Downloads with blocked data.")
|
new Error("unblock may only be called on Downloads with blocked data.")
|
||||||
@@ -716,7 +723,9 @@ Download.prototype = {
|
|||||||
|
|
||||||
this._promiseUnblock = (async () => {
|
this._promiseUnblock = (async () => {
|
||||||
try {
|
try {
|
||||||
await IOUtils.move(this.target.partFilePath, this.target.path);
|
if (this.target.partFilePath) {
|
||||||
|
await IOUtils.move(this.target.partFilePath, this.target.path);
|
||||||
|
}
|
||||||
await this.target.refresh();
|
await this.target.refresh();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
@@ -733,6 +742,44 @@ Download.prototype = {
|
|||||||
return this._promiseUnblock;
|
return this._promiseUnblock;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the download should be allowed. Will do nothing
|
||||||
|
* if content analysis was not used.
|
||||||
|
*/
|
||||||
|
respondToContentAnalysisWarnWithAllow() {
|
||||||
|
if (this.error?.contentAnalysisWarnRequestToken) {
|
||||||
|
lazy.DownloadIntegration.getContentAnalysisService().respondToWarnDialog(
|
||||||
|
this.error.contentAnalysisWarnRequestToken,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
this.error.contentAnalysisWarnRequestToken = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the download should be blocked. Will do nothing
|
||||||
|
* if content analysis was not used.
|
||||||
|
*/
|
||||||
|
async respondToContentAnalysisWarnWithBlock() {
|
||||||
|
if (this.error?.contentAnalysisWarnRequestToken) {
|
||||||
|
lazy.DownloadIntegration.getContentAnalysisService().respondToWarnDialog(
|
||||||
|
this.error.contentAnalysisWarnRequestToken,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
this.error.contentAnalysisWarnRequestToken = undefined;
|
||||||
|
if (!this.target.partFilePath) {
|
||||||
|
// Callers will be finalizing the download after this.
|
||||||
|
// But if the download happened in place, we need to
|
||||||
|
// remove the final target file.
|
||||||
|
try {
|
||||||
|
await this.saver.removeData(true);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirms that a blocked download should be cleaned up.
|
* Confirms that a blocked download should be cleaned up.
|
||||||
*
|
*
|
||||||
@@ -773,6 +820,9 @@ Download.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._promiseConfirmBlock = (async () => {
|
this._promiseConfirmBlock = (async () => {
|
||||||
|
if (this.error?.becauseBlockedByContentAnalysis) {
|
||||||
|
await this.respondToContentAnalysisWarnWithBlock();
|
||||||
|
}
|
||||||
// This call never throws exceptions. If the removal fails, the blocked
|
// This call never throws exceptions. If the removal fails, the blocked
|
||||||
// data remains stored on disk in the ".part" file.
|
// data remains stored on disk in the ".part" file.
|
||||||
await this.saver.removeData();
|
await this.saver.removeData();
|
||||||
@@ -1854,7 +1904,8 @@ export var DownloadError = function (aProperties) {
|
|||||||
} else if (
|
} else if (
|
||||||
aProperties.becauseBlocked ||
|
aProperties.becauseBlocked ||
|
||||||
aProperties.becauseBlockedByParentalControls ||
|
aProperties.becauseBlockedByParentalControls ||
|
||||||
aProperties.becauseBlockedByReputationCheck
|
aProperties.becauseBlockedByReputationCheck ||
|
||||||
|
aProperties.becauseBlockedByContentAnalysis
|
||||||
) {
|
) {
|
||||||
this.message = "Download blocked.";
|
this.message = "Download blocked.";
|
||||||
} else {
|
} else {
|
||||||
@@ -1882,6 +1933,12 @@ export var DownloadError = function (aProperties) {
|
|||||||
this.becauseBlocked = true;
|
this.becauseBlocked = true;
|
||||||
this.becauseBlockedByReputationCheck = true;
|
this.becauseBlockedByReputationCheck = true;
|
||||||
this.reputationCheckVerdict = aProperties.reputationCheckVerdict || "";
|
this.reputationCheckVerdict = aProperties.reputationCheckVerdict || "";
|
||||||
|
} else if (aProperties.becauseBlockedByContentAnalysis) {
|
||||||
|
this.becauseBlocked = true;
|
||||||
|
this.becauseBlockedByContentAnalysis = true;
|
||||||
|
this.contentAnalysisWarnRequestToken =
|
||||||
|
aProperties.contentAnalysisWarnRequestToken;
|
||||||
|
this.reputationCheckVerdict = aProperties.reputationCheckVerdict;
|
||||||
} else if (aProperties.becauseBlocked) {
|
} else if (aProperties.becauseBlocked) {
|
||||||
this.becauseBlocked = true;
|
this.becauseBlocked = true;
|
||||||
}
|
}
|
||||||
@@ -1939,6 +1996,11 @@ DownloadError.prototype = {
|
|||||||
*/
|
*/
|
||||||
becauseBlockedByReputationCheck: false,
|
becauseBlockedByReputationCheck: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the download was blocked by a local content analysis tool.
|
||||||
|
*/
|
||||||
|
becauseBlockedByContentAnalysis: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If becauseBlockedByReputationCheck is true, indicates the detailed reason
|
* If becauseBlockedByReputationCheck is true, indicates the detailed reason
|
||||||
* why the download was blocked, according to the "BLOCK_VERDICT_" constants.
|
* why the download was blocked, according to the "BLOCK_VERDICT_" constants.
|
||||||
@@ -2000,6 +2062,7 @@ DownloadError.fromSerializable = function (aSerializable) {
|
|||||||
property != "becauseBlocked" &&
|
property != "becauseBlocked" &&
|
||||||
property != "becauseBlockedByParentalControls" &&
|
property != "becauseBlockedByParentalControls" &&
|
||||||
property != "becauseBlockedByReputationCheck" &&
|
property != "becauseBlockedByReputationCheck" &&
|
||||||
|
property != "becauseBlockedByContentAnalysis" &&
|
||||||
property != "reputationCheckVerdict"
|
property != "reputationCheckVerdict"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2573,6 +2636,8 @@ DownloadCopySaver.prototype = {
|
|||||||
* @rejects DownloadError if the download should be blocked.
|
* @rejects DownloadError if the download should be blocked.
|
||||||
*/
|
*/
|
||||||
async _checkReputationAndMove(aSetPropertiesFn) {
|
async _checkReputationAndMove(aSetPropertiesFn) {
|
||||||
|
const REPUTATION_CHECK = 0;
|
||||||
|
const CONTENT_ANALYSIS_CHECK = 1;
|
||||||
/**
|
/**
|
||||||
* Maps nsIApplicationReputationService verdicts with the DownloadError ones.
|
* Maps nsIApplicationReputationService verdicts with the DownloadError ones.
|
||||||
*/
|
*/
|
||||||
@@ -2587,25 +2652,108 @@ DownloadCopySaver.prototype = {
|
|||||||
DownloadError.BLOCK_VERDICT_MALWARE,
|
DownloadError.BLOCK_VERDICT_MALWARE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let checkContentAnalysis = download => {
|
||||||
|
// Start an asynchronous content analysis check.
|
||||||
|
return lazy.DownloadIntegration.shouldBlockForContentAnalysis(
|
||||||
|
download
|
||||||
|
).then(result => {
|
||||||
|
result.check = CONTENT_ANALYSIS_CHECK;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let checkReputation = download => {
|
||||||
|
// Start an asynchronous reputation check.
|
||||||
|
return lazy.DownloadIntegration.shouldBlockForReputationCheck(
|
||||||
|
download
|
||||||
|
).then(result => {
|
||||||
|
result.check = REPUTATION_CHECK;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let hasMostRestrictiveResult = ([result1, result2]) => {
|
||||||
|
// Verdicts are sorted from least-to-most restrictive. However, a result that
|
||||||
|
// shouldBlock is always more restrictive than one that does not. Since
|
||||||
|
// reputation allows shouldBlock to be overridden by prefs but content
|
||||||
|
// analysis does not, we need to be careful of that.
|
||||||
|
if (result1.shouldBlock && !result2.shouldBlock) {
|
||||||
|
return result1;
|
||||||
|
}
|
||||||
|
if (result2.shouldBlock) {
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
// Verdicts are in a pre-defined order (see nsIApplicationReputationService),
|
||||||
|
// so find the most restrictive one.
|
||||||
|
const verdictToRestrictiveness = {
|
||||||
|
[Ci.nsIApplicationReputationService.VERDICT_SAFE]: 0,
|
||||||
|
[Ci.nsIApplicationReputationService.VERDICT_POTENTIALLY_UNWANTED]: 1,
|
||||||
|
[Ci.nsIApplicationReputationService.VERDICT_UNCOMMON]: 2,
|
||||||
|
[Ci.nsIApplicationReputationService.VERDICT_DANGEROUS_HOST]: 3,
|
||||||
|
[Ci.nsIApplicationReputationService.VERDICT_DANGEROUS]: 4,
|
||||||
|
};
|
||||||
|
return verdictToRestrictiveness[result1.verdict] >
|
||||||
|
verdictToRestrictiveness[result2.verdict]
|
||||||
|
? result1
|
||||||
|
: result2;
|
||||||
|
};
|
||||||
|
|
||||||
let download = this.download;
|
let download = this.download;
|
||||||
let targetPath = this.download.target.path;
|
let targetPath = this.download.target.path;
|
||||||
let partFilePath = this.download.target.partFilePath;
|
let partFilePath = this.download.target.partFilePath;
|
||||||
|
|
||||||
let { shouldBlock, verdict } =
|
let reputationPromise = checkReputation(download);
|
||||||
await lazy.DownloadIntegration.shouldBlockForReputationCheck(download);
|
let caPromise = checkContentAnalysis(download);
|
||||||
let downloadErrorVerdict = kVerdictMap[verdict] || "";
|
|
||||||
if (shouldBlock) {
|
let permissionResult = await Promise.any([
|
||||||
Glean.downloads.userActionOnBlockedDownload[
|
reputationPromise,
|
||||||
downloadErrorVerdict
|
caPromise,
|
||||||
].accumulateSingleSample(0);
|
]).then(async result => {
|
||||||
|
// If the first result is the most restrictive one, we can return it
|
||||||
|
// immediately.
|
||||||
|
if (
|
||||||
|
result.shouldBlock &&
|
||||||
|
result.verdict == Ci.nsIApplicationReputationService.VERDICT_DANGEROUS
|
||||||
|
) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// Otherwise wait for both results and compare them.
|
||||||
|
return await Promise.all([reputationPromise, caPromise]).then(
|
||||||
|
hasMostRestrictiveResult
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let downloadErrorVerdict = kVerdictMap[permissionResult.verdict] || "";
|
||||||
|
permissionResult.verdict = downloadErrorVerdict;
|
||||||
|
if (permissionResult.shouldBlock) {
|
||||||
|
if (permissionResult.check === REPUTATION_CHECK) {
|
||||||
|
Glean.downloads.userActionOnBlockedDownload[
|
||||||
|
downloadErrorVerdict
|
||||||
|
].accumulateSingleSample(0);
|
||||||
|
}
|
||||||
|
|
||||||
let newProperties = { progress: 100, hasPartialData: false };
|
let newProperties = { progress: 100, hasPartialData: false };
|
||||||
|
|
||||||
// We will remove the potentially dangerous file if instructed by
|
// We will remove the potentially dangerous file if instructed by
|
||||||
// DownloadIntegration. We will always remove the file when the
|
// DownloadIntegration. We will always remove the file when the
|
||||||
// download did not use a partial file path, meaning it
|
// download did not use a partial file path, meaning it
|
||||||
// currently has its final filename.
|
// currently has its final filename, or if it was blocked by
|
||||||
if (!lazy.DownloadIntegration.shouldKeepBlockedData() || !partFilePath) {
|
// content analysis.
|
||||||
|
let neverRemoveData = false;
|
||||||
|
let alwaysRemoveData = false;
|
||||||
|
if (permissionResult.check === CONTENT_ANALYSIS_CHECK) {
|
||||||
|
if (downloadErrorVerdict === DownloadError.BLOCK_VERDICT_MALWARE) {
|
||||||
|
alwaysRemoveData = true;
|
||||||
|
} else {
|
||||||
|
neverRemoveData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let removeData =
|
||||||
|
!neverRemoveData &&
|
||||||
|
(alwaysRemoveData ||
|
||||||
|
!lazy.DownloadIntegration.shouldKeepBlockedData() ||
|
||||||
|
!partFilePath);
|
||||||
|
if (removeData) {
|
||||||
await this.removeData(!partFilePath);
|
await this.removeData(!partFilePath);
|
||||||
} else {
|
} else {
|
||||||
newProperties.hasBlockedData = true;
|
newProperties.hasBlockedData = true;
|
||||||
@@ -2613,10 +2761,19 @@ DownloadCopySaver.prototype = {
|
|||||||
|
|
||||||
aSetPropertiesFn(newProperties);
|
aSetPropertiesFn(newProperties);
|
||||||
|
|
||||||
throw new DownloadError({
|
if (permissionResult.check == REPUTATION_CHECK) {
|
||||||
becauseBlockedByReputationCheck: true,
|
throw new DownloadError({
|
||||||
reputationCheckVerdict: downloadErrorVerdict,
|
becauseBlockedByReputationCheck: true,
|
||||||
});
|
reputationCheckVerdict: downloadErrorVerdict,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new DownloadError({
|
||||||
|
becauseBlockedByContentAnalysis: true,
|
||||||
|
reputationCheckVerdict: downloadErrorVerdict,
|
||||||
|
contentAnalysisWarnRequestToken:
|
||||||
|
permissionResult.contentAnalysisWarnRequestToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partFilePath) {
|
if (partFilePath) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DownloadList } from "resource://gre/modules/DownloadList.sys.mjs";
|
import { DownloadList } from "resource://gre/modules/DownloadList.sys.mjs";
|
||||||
|
import { DownloadError } from "resource://gre/modules/DownloadCore.sys.mjs";
|
||||||
|
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ const METADATA_STATE_CANCELED = 3;
|
|||||||
const METADATA_STATE_PAUSED = 4;
|
const METADATA_STATE_PAUSED = 4;
|
||||||
const METADATA_STATE_BLOCKED_PARENTAL = 6;
|
const METADATA_STATE_BLOCKED_PARENTAL = 6;
|
||||||
const METADATA_STATE_DIRTY = 8;
|
const METADATA_STATE_DIRTY = 8;
|
||||||
|
const METADATA_STATE_BLOCKED_CONTENT_ANALYSIS = 9;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods to retrieve downloads from previous sessions and store
|
* Provides methods to retrieve downloads from previous sessions and store
|
||||||
@@ -125,6 +127,12 @@ export let DownloadHistory = {
|
|||||||
state = METADATA_STATE_BLOCKED_PARENTAL;
|
state = METADATA_STATE_BLOCKED_PARENTAL;
|
||||||
} else if (download.error.becauseBlockedByReputationCheck) {
|
} else if (download.error.becauseBlockedByReputationCheck) {
|
||||||
state = METADATA_STATE_DIRTY;
|
state = METADATA_STATE_DIRTY;
|
||||||
|
} else if (download.error.becauseBlockedByContentAnalysis) {
|
||||||
|
state =
|
||||||
|
download.error.reputationCheckVerdict ===
|
||||||
|
DownloadError.BLOCK_VERDICT_MALWARE
|
||||||
|
? METADATA_STATE_BLOCKED_CONTENT_ANALYSIS
|
||||||
|
: METADATA_STATE_DIRTY;
|
||||||
} else {
|
} else {
|
||||||
state = METADATA_STATE_FAILED;
|
state = METADATA_STATE_FAILED;
|
||||||
}
|
}
|
||||||
@@ -433,6 +441,11 @@ class HistoryDownload {
|
|||||||
this.error = { message: "History download failed." };
|
this.error = { message: "History download failed." };
|
||||||
} else if (metaData.state == METADATA_STATE_BLOCKED_PARENTAL) {
|
} else if (metaData.state == METADATA_STATE_BLOCKED_PARENTAL) {
|
||||||
this.error = { becauseBlockedByParentalControls: true };
|
this.error = { becauseBlockedByParentalControls: true };
|
||||||
|
} else if (metaData.state == METADATA_STATE_BLOCKED_CONTENT_ANALYSIS) {
|
||||||
|
this.error = {
|
||||||
|
becauseBlockedByContentAnalysis: true,
|
||||||
|
reputationCheckVerdict: metaData.reputationCheckVerdict || "",
|
||||||
|
};
|
||||||
} else if (metaData.state == METADATA_STATE_DIRTY) {
|
} else if (metaData.state == METADATA_STATE_DIRTY) {
|
||||||
this.error = {
|
this.error = {
|
||||||
becauseBlockedByReputationCheck: true,
|
becauseBlockedByReputationCheck: true,
|
||||||
@@ -503,6 +516,17 @@ class HistoryDownload {
|
|||||||
this.deleted = true;
|
this.deleted = true;
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method mimicks the "respondToContentAnalysisWarnWithBlock"
|
||||||
|
* method of session downloads.
|
||||||
|
*/
|
||||||
|
async respondToContentAnalysisWarnWithBlock() {
|
||||||
|
// A history download cannot be pending a content
|
||||||
|
// analysis response (since it doesn't persist after Firefox
|
||||||
|
// is closed), so just do nothing.
|
||||||
|
console.warn("attempted to block via Content Analysis a history download");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ const kSaveDelayMs = 1500;
|
|||||||
*/
|
*/
|
||||||
const kObserverTopics = [
|
const kObserverTopics = [
|
||||||
"quit-application-requested",
|
"quit-application-requested",
|
||||||
|
"quit-application-granted",
|
||||||
"offline-requested",
|
"offline-requested",
|
||||||
"last-pb-context-exiting",
|
"last-pb-context-exiting",
|
||||||
"last-pb-context-exited",
|
"last-pb-context-exited",
|
||||||
@@ -423,6 +424,151 @@ export var DownloadIntegration = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getContentAnalysisService() {
|
||||||
|
// Do not use a lazy service getter for this, because tests set up different mocks,
|
||||||
|
// so if multiple tests run that call into this we can end up calling into an old mock.
|
||||||
|
return Cc["@mozilla.org/contentanalysis;1"].getService(
|
||||||
|
Ci.nsIContentAnalysis
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async shouldBlockForContentAnalysis(download) {
|
||||||
|
const contentAnalysis = this.getContentAnalysisService();
|
||||||
|
|
||||||
|
if (!contentAnalysis.isActive) {
|
||||||
|
return {
|
||||||
|
verdict: Ci.nsIApplicationReputationService.VERDICT_SAFE,
|
||||||
|
shouldBlock: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For PDF files loaded in pdf.js the originalUrl is the original URL
|
||||||
|
// where the PDF was loaded from, and the url is the URL of the pdf.js
|
||||||
|
// resource.
|
||||||
|
let downloadUrl = download.source.originalUrl ?? download.source.url;
|
||||||
|
let resources = [
|
||||||
|
{
|
||||||
|
url: downloadUrl,
|
||||||
|
type: Ci.nsIClientDownloadResource.DOWNLOAD_URL,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let redirects = download.saver.getRedirects();
|
||||||
|
if (redirects) {
|
||||||
|
for (let redirect of redirects) {
|
||||||
|
resources.push({
|
||||||
|
url: redirect.referrerURI,
|
||||||
|
type: Ci.nsIClientDownloadResource.DOWNLOAD_REDIRECT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// source.referrerInfo is a string or nsIReferrerInfo that
|
||||||
|
// represents the download referrer. May be null.
|
||||||
|
if (download.source.referrerInfo) {
|
||||||
|
const url =
|
||||||
|
download.source.referrerInfo instanceof Ci.nsIReferrerInfo
|
||||||
|
? download.source.referrerInfo.originalReferrer?.spec
|
||||||
|
: download.source.referrerInfo;
|
||||||
|
if (url) {
|
||||||
|
resources.push({
|
||||||
|
url,
|
||||||
|
type: Ci.nsIClientDownloadResource.TAB_URL,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = lazy.NetUtil.newURI(downloadUrl);
|
||||||
|
let fileNameForDisplay = download.target.path;
|
||||||
|
try {
|
||||||
|
// Try to get a prettier name
|
||||||
|
let file = new lazy.FileUtils.File(download.target.path);
|
||||||
|
fileNameForDisplay = file.displayName;
|
||||||
|
} catch (ex) {
|
||||||
|
// oh well
|
||||||
|
}
|
||||||
|
const requestToken = Services.uuid.generateUUID().toString();
|
||||||
|
let warnResponseObserver = undefined;
|
||||||
|
// Set up a separate promise to wait specifically for a WARN
|
||||||
|
// response (if it comes) while we also wait for a final response.
|
||||||
|
// This is necessary because if the agent sends a WARN response,
|
||||||
|
// it doesn't count as a real response, and the Content Analysis code
|
||||||
|
// won't respond to the callback until respondToWarnDialog() is called.
|
||||||
|
const warnResultPromise = new Promise(resolve => {
|
||||||
|
warnResponseObserver = function (subject, topic, _data) {
|
||||||
|
if (topic == "dlp-response") {
|
||||||
|
/** @type nsIContentAnalysisResponse */
|
||||||
|
let response = subject;
|
||||||
|
if (
|
||||||
|
response.requestToken === requestToken &&
|
||||||
|
response.action === Ci.nsIContentAnalysisResponse.eWarn
|
||||||
|
) {
|
||||||
|
resolve({
|
||||||
|
isContentAnalysisWarn: true,
|
||||||
|
verdict:
|
||||||
|
Ci.nsIApplicationReputationService.VERDICT_POTENTIALLY_UNWANTED,
|
||||||
|
contentAnalysisWarnRequestToken: requestToken,
|
||||||
|
shouldBlock: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Services.obs.addObserver(warnResponseObserver, "dlp-response");
|
||||||
|
});
|
||||||
|
let finalResultPromise = contentAnalysis
|
||||||
|
.analyzeContentRequests(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
analysisType: Ci.nsIContentAnalysisRequest.eFileDownloaded,
|
||||||
|
operationTypeForDisplay: Ci.nsIContentAnalysisRequest.eDownload,
|
||||||
|
fileNameForDisplay,
|
||||||
|
// "Save As" downloads do not have a browsing context
|
||||||
|
reason:
|
||||||
|
download.source.browsingContextId === 0
|
||||||
|
? Ci.nsIContentAnalysisRequest.eSaveAsDownload
|
||||||
|
: Ci.nsIContentAnalysisRequest.eNormalDownload,
|
||||||
|
resources,
|
||||||
|
requestToken,
|
||||||
|
url,
|
||||||
|
filePath: download.target.path,
|
||||||
|
// When doing a download analysis, the Content Analysis code won't
|
||||||
|
// display dialogs in the window, but the code still wants a
|
||||||
|
// content window and will get the topChromeWindow to show
|
||||||
|
// a notification.
|
||||||
|
windowGlobalParent: BrowsingContext.get(
|
||||||
|
download.source.browsingContextId
|
||||||
|
)?.topWindowContext,
|
||||||
|
sha256Digest: download.saver.getSha256Hash(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
/* autoAcknowledge*/ true
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
return {
|
||||||
|
verdict: response.shouldAllowContent
|
||||||
|
? Ci.nsIApplicationReputationService.VERDICT_SAFE
|
||||||
|
: Ci.nsIApplicationReputationService.VERDICT_DANGEROUS,
|
||||||
|
shouldBlock: !response.shouldAllowContent,
|
||||||
|
contentAnalysisWarnRequestToken: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
let finalOrWarnResult = await Promise.race([
|
||||||
|
finalResultPromise,
|
||||||
|
warnResultPromise,
|
||||||
|
]);
|
||||||
|
return finalOrWarnResult;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
verdict: Ci.nsIApplicationReputationService.VERDICT_DANGEROUS,
|
||||||
|
shouldBlock: true,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
Services.obs.removeObserver(warnResponseObserver, "dlp-response");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether downloaded files should be marked as coming from
|
* Checks whether downloaded files should be marked as coming from
|
||||||
* Internet Zone.
|
* Internet Zone.
|
||||||
@@ -961,6 +1107,15 @@ var DownloadObserver = {
|
|||||||
*/
|
*/
|
||||||
_privateInProgressDownloads: new Set(),
|
_privateInProgressDownloads: new Set(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of downloads that have finished but have gotten a content analysis
|
||||||
|
* WARN response. These downloads need to be canceled when quitting, because
|
||||||
|
* the next time we start Firefox the content analysis agent may not have
|
||||||
|
* the same context as the one that was running when it analyzed the
|
||||||
|
* download.
|
||||||
|
*/
|
||||||
|
_contentAnalysisWarnInProgressDownloads: new Set(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set that contains the downloads that have been canceled when going offline
|
* Set that contains the downloads that have been canceled when going offline
|
||||||
* or to sleep. These are started again when returning online or waking. This
|
* or to sleep. These are started again when returning online or waking. This
|
||||||
@@ -991,6 +1146,11 @@ var DownloadObserver = {
|
|||||||
},
|
},
|
||||||
onDownloadChanged: aDownload => {
|
onDownloadChanged: aDownload => {
|
||||||
if (aDownload.stopped) {
|
if (aDownload.stopped) {
|
||||||
|
if (aDownload.error?.contentAnalysisWarnRequestToken) {
|
||||||
|
this._contentAnalysisWarnInProgressDownloads.add(aDownload);
|
||||||
|
} else {
|
||||||
|
this._contentAnalysisWarnInProgressDownloads.delete(aDownload);
|
||||||
|
}
|
||||||
downloadsSet.delete(aDownload);
|
downloadsSet.delete(aDownload);
|
||||||
} else {
|
} else {
|
||||||
downloadsSet.add(aDownload);
|
downloadsSet.add(aDownload);
|
||||||
@@ -998,6 +1158,7 @@ var DownloadObserver = {
|
|||||||
},
|
},
|
||||||
onDownloadRemoved: aDownload => {
|
onDownloadRemoved: aDownload => {
|
||||||
downloadsSet.delete(aDownload);
|
downloadsSet.delete(aDownload);
|
||||||
|
this._contentAnalysisWarnInProgressDownloads.delete(aDownload);
|
||||||
// The download must also be removed from the canceled when offline set.
|
// The download must also be removed from the canceled when offline set.
|
||||||
this._canceledOfflineDownloads.delete(aDownload);
|
this._canceledOfflineDownloads.delete(aDownload);
|
||||||
},
|
},
|
||||||
@@ -1065,12 +1226,41 @@ var DownloadObserver = {
|
|||||||
observe: function DO_observe(aSubject, aTopic, aData) {
|
observe: function DO_observe(aSubject, aTopic, aData) {
|
||||||
let downloadsCount;
|
let downloadsCount;
|
||||||
switch (aTopic) {
|
switch (aTopic) {
|
||||||
case "quit-application-requested":
|
case "quit-application-requested": {
|
||||||
downloadsCount =
|
downloadsCount =
|
||||||
this._publicInProgressDownloads.size +
|
this._publicInProgressDownloads.size +
|
||||||
this._privateInProgressDownloads.size;
|
this._privateInProgressDownloads.size +
|
||||||
|
this._contentAnalysisWarnInProgressDownloads.size;
|
||||||
this._confirmCancelDownloads(aSubject, downloadsCount, "ON_QUIT");
|
this._confirmCancelDownloads(aSubject, downloadsCount, "ON_QUIT");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case "quit-application-granted": {
|
||||||
|
let blockPromises = [];
|
||||||
|
for (let download of this._contentAnalysisWarnInProgressDownloads) {
|
||||||
|
blockPromises.push(
|
||||||
|
(async () => {
|
||||||
|
await download.respondToContentAnalysisWarnWithBlock();
|
||||||
|
await download.finalize(true);
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (blockPromises.length) {
|
||||||
|
// Wait for all the downloads to be blocked (and the files deleted)
|
||||||
|
// before proceeding with the quit.
|
||||||
|
let promiseDone = false;
|
||||||
|
Promise.all(blockPromises).finally(() => {
|
||||||
|
promiseDone = true;
|
||||||
|
});
|
||||||
|
Services.tm.spinEventLoopUntil(
|
||||||
|
"DownloadIntegration.sys.mjs:DI_observe_quit-application-granted",
|
||||||
|
() => {
|
||||||
|
return promiseDone;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._contentAnalysisWarnInProgressDownloads.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "offline-requested":
|
case "offline-requested":
|
||||||
downloadsCount =
|
downloadsCount =
|
||||||
this._publicInProgressDownloads.size +
|
this._publicInProgressDownloads.size +
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ interface nsIReferrerInfo;
|
|||||||
interface nsIApplicationReputationService : nsISupports {
|
interface nsIApplicationReputationService : nsISupports {
|
||||||
/**
|
/**
|
||||||
* Indicates the reason for the application reputation block.
|
* Indicates the reason for the application reputation block.
|
||||||
|
* These values should not be modified as they match the values in
|
||||||
|
* ClientDownloadResponse.Verdict in csd.proto.
|
||||||
*/
|
*/
|
||||||
const unsigned long VERDICT_SAFE = 0;
|
const unsigned long VERDICT_SAFE = 0;
|
||||||
const unsigned long VERDICT_DANGEROUS = 1;
|
const unsigned long VERDICT_DANGEROUS = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user