Bug 1706871 - Fix HandleInternally + Insecure Downloads r=mak

Differential Revision: https://phabricator.services.mozilla.com/D117412
This commit is contained in:
Sebastian Streich 2021-07-30 13:16:38 +00:00
parent 1f2fc62fd5
commit 24b5b729d8
5 changed files with 112 additions and 19 deletions

View File

@ -41,8 +41,15 @@ async function task_openPanel() {
await promise;
}
function shouldPromptDownload() {
// Waits until the download Prompt is shown
/**
* Waits until the download Prompt is shown, selects
* the choosen <action>, then accepts the dialog
* @param [action] Which action to select, either:
* "handleInternally", "save" or "open".
* @returns {Promise} Resolved once done.
*/
function shouldPromptDownload(action = "save") {
return new Promise((resolve, reject) => {
Services.wm.addListener({
onOpenWindow(xulWin) {
@ -55,8 +62,8 @@ function shouldPromptDownload() {
) {
let dialog = win.document.getElementById("unknownContentType");
let button = dialog.getButton("accept");
let saveRadio = win.document.getElementById("save");
saveRadio.click();
let actionRadio = win.document.getElementById(action);
actionRadio.click();
button.disabled = false;
dialog.acceptDialog();
resolve();
@ -99,14 +106,22 @@ async function shouldNotifyDownloadUI() {
// Waits until a Blocked download was added to the Download List
// -> returns the blocked Download
let list = await Downloads.getList(Downloads.ALL);
return new Promise(res => {
return new Promise((res, err) => {
const view = {
onDownloadAdded: aDownload => {
onDownloadAdded: async aDownload => {
let { error } = aDownload;
if (
error.becauseBlockedByReputationCheck &&
error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE
) {
// It's an insecure Download, now Check that it has been cleaned up properly
if ((await IOUtils.stat(aDownload.target.path)).size != 0) {
throw new Error(`Download target is not empty!`);
}
if ((await IOUtils.stat(aDownload.target.path)).size != 0) {
throw new Error(`Download partFile was not cleaned up properly`);
}
res(aDownload);
list.removeView(view);
}
@ -116,7 +131,7 @@ async function shouldNotifyDownloadUI() {
});
}
async function runTest(url, link, checkFunction, decscription) {
async function runTest(url, link, checkFunction, description) {
await SpecialPowers.pushPrefEnv({
set: [["dom.block_download_insecure", true]],
});
@ -128,7 +143,7 @@ async function runTest(url, link, checkFunction, decscription) {
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
info("Checking: " + decscription);
info("Checking: " + description);
let checkPromise = checkFunction();
// Click the Link to trigger the download
@ -138,7 +153,7 @@ async function runTest(url, link, checkFunction, decscription) {
await checkPromise;
ok(true, decscription);
ok(true, description);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
@ -213,3 +228,31 @@ add_task(async function() {
"A Blocked Download Should open the Download Panel"
);
});
// Test Download an insecure pdf and choose "Open with Firefox"
add_task(async function download_open_insecure_pdf() {
await promiseFocus();
await runTest(
SECURE_BASE_URL,
"insecurePDF",
async () => {
info("awaiting that the Download Prompt is shown");
await shouldPromptDownload("handleInternally");
let download = await shouldNotifyDownloadUI();
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
await download.unblock();
ok(download.error == null, "There should be no error after unblocking");
let tab = await newTabPromise;
// Fix (possible) windows file delemiters
let usablePath = download.target.path.replace("\\", "/");
ok(
tab.linkedBrowser._documentURI.filePath.includes(".pdf"),
"The download target was opened"
);
BrowserTestUtils.removeTab(tab);
ok(true, "The Content was opened in a new tab");
},
"A Blocked PDF can be opened internally"
);
});

View File

@ -19,7 +19,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=676619
secureLink.href=`https://${host}/${path}`;
secureLink.download="true";
secureLink.textContent="Secure Link";
secureLink.id="secure";
const insecureLink = document.createElement("a");
@ -28,6 +27,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=676619
insecureLink.id="insecure";
insecureLink.textContent="Not secure Link";
const insecurePDF = document.createElement("a");
insecurePDF.href=`http://${host}/${path}?type=application/pdf&name=example.pdf`;
insecurePDF.download="true";
insecurePDF.id="insecurePDF";
insecurePDF.textContent="Not secure Link to PDF";
document.body.append(insecurePDF);
document.body.append(secureLink);
document.body.append(insecureLink);
</script>

View File

@ -2,8 +2,20 @@
function handleRequest(request, response)
{
let type = "image/png";
let filename = "hello.png";
request.queryString.split('&').forEach((val) => {
var [key, value] = val.split('=');
if (key == "type"){
type= value;
}
if (key == "name"){
filename = value;
}
});
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Disposition", "attachment");
response.setHeader("Content-Type", "image/png");
response.setHeader("Content-Disposition", `attachment; filename=${filename}`);
response.setHeader("Content-Type", type);
response.write('🙈🙊🐵🙊');
}

View File

@ -29,6 +29,7 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
DownloadHistory: "resource://gre/modules/DownloadHistory.jsm",
DownloadPaths: "resource://gre/modules/DownloadPaths.jsm",
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
NetUtil: "resource://gre/modules/NetUtil.jsm",
@ -657,10 +658,23 @@ Download.prototype = {
// This ensures the verdict will not get set again after the browser
// restarts and the download gets serialized and de-serialized again.
delete this._unknownProperties?.errorObj;
this.start().catch(e => {
this.error = e;
this._notifyChange();
});
this.start()
.catch(err => {
if (err.becauseTargetFailed) {
// In case we cannot write to the target file
// retry with a new unique name
let uniquePath = DownloadPaths.createNiceUniqueFile(
new FileUtils.File(this.target.path)
).path;
this.target.path = uniquePath;
return this.start();
}
return Promise.reject(err);
})
.catch(err => {
this.error = err;
this._notifyChange();
});
this._notifyChange();
this._promiseUnblock = DownloadIntegration.downloadDone(this);
return this._promiseUnblock;
@ -2480,7 +2494,21 @@ DownloadCopySaver.prototype = {
}
if (partFilePath) {
await IOUtils.move(partFilePath, targetPath);
try {
await IOUtils.move(partFilePath, targetPath);
} catch (e) {
if (e.name === "NotAllowedError") {
// In case we cannot write to the target file
// retry with a new unique name
let uniquePath = DownloadPaths.createNiceUniqueFile(
new FileUtils.File(targetPath)
).path;
await IOUtils.move(partFilePath, uniquePath);
this.download.target.path = uniquePath;
} else {
throw e;
}
}
}
},

View File

@ -2314,6 +2314,10 @@ nsresult nsExternalAppHandler::CreateTransfer() {
if (mDownloadClassification != nsITransfer::DOWNLOAD_ACCEPTABLE) {
mCanceled = true;
mRequest->Cancel(NS_ERROR_ABORT);
if (mSaver) {
mSaver->Finish(NS_ERROR_ABORT);
mSaver = nullptr;
}
return CreateFailedTransfer();
}
nsresult rv;
@ -2420,11 +2424,11 @@ nsresult nsExternalAppHandler::CreateFailedTransfer() {
if (mBrowsingContext) {
rv = transfer->InitWithBrowsingContext(
mSourceUrl, pseudoTarget, u""_ns, mMimeInfo, mTimeDownloadStarted,
nullptr, this, channel && NS_UsePrivateBrowsing(channel),
mTempFile, this, channel && NS_UsePrivateBrowsing(channel),
mDownloadClassification, mBrowsingContext, mHandleInternally);
} else {
rv = transfer->Init(mSourceUrl, pseudoTarget, u""_ns, mMimeInfo,
mTimeDownloadStarted, nullptr, this,
mTimeDownloadStarted, mTempFile, this,
channel && NS_UsePrivateBrowsing(channel),
mDownloadClassification);
}