Bug 1725353 - Store download permissions for the websites with multiple automatic downloads. r=mtigley

Differential Revision: https://phabricator.services.mozilla.com/D119512
This commit is contained in:
Ava Katushka ava8katushka 2021-09-01 12:14:07 +00:00
parent bc47016bf9
commit 79de8d7ca7
6 changed files with 201 additions and 1 deletions

View File

@ -1683,6 +1683,11 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
aChannel->GetURI(getter_AddRefs(mSourceUrl));
}
if (StaticPrefs::browser_download_improvements_to_download_panel() &&
IsDownloadSpam(aChannel)) {
return NS_OK;
}
mDownloadClassification =
nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType);
@ -1958,6 +1963,62 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
return NS_OK;
}
bool nsExternalAppHandler::IsDownloadSpam(nsIChannel* aChannel) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
if (loadInfo->GetHasValidUserGestureActivation()) {
return false;
}
nsCOMPtr<nsIPermissionManager> permissionManager =
mozilla::services::GetPermissionManager();
nsCOMPtr<nsIPrincipal> principal = loadInfo->TriggeringPrincipal();
bool exactHostMatch = false;
constexpr auto type = "automatic-download"_ns;
nsCOMPtr<nsIPermission> permission;
permissionManager->GetPermissionObject(principal, type, exactHostMatch,
getter_AddRefs(permission));
if (permission) {
uint32_t capability;
permission->GetCapability(&capability);
if (capability == nsIPermissionManager::DENY_ACTION) {
mCanceled = true;
aChannel->Cancel(NS_ERROR_ABORT);
return true;
}
if (capability == nsIPermissionManager::ALLOW_ACTION) {
return false;
}
// If no action is set (i.e: null), we set PROMPT_ACTION by default,
// which will notify the Downloads UI to open the panel on the next request.
if (capability == nsIPermissionManager::PROMPT_ACTION) {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
nsAutoCString cStringURI;
loadInfo->TriggeringPrincipal()->GetPrePath(cStringURI);
observerService->NotifyObservers(
nullptr, "blocked-automatic-download",
NS_ConvertASCIItoUTF16(cStringURI.get()).get());
// FIXME: In order to escape memory leaks, currently we cancel blocked
// downloads. This is temporary solution, because download data should be
// kept in order to restart the blocked download.
mCanceled = true;
aChannel->Cancel(NS_ERROR_ABORT);
// End cancel
return true;
}
} else {
permissionManager->AddFromPrincipal(
principal, type, nsIPermissionManager::PROMPT_ACTION,
nsIPermissionManager::EXPIRE_NEVER, 0 /* expire time */);
}
return false;
}
// Convert error info into proper message text and send OnStatusChange
// notification to the dialog progress listener or nsITransfer implementation.
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,

View File

@ -14,11 +14,13 @@
#include "nsIWebProgressListener2.h"
#include "nsIHelperAppLauncherDialog.h"
#include "nsILoadInfo.h"
#include "nsIMIMEInfo.h"
#include "nsIMIMEService.h"
#include "nsINamed.h"
#include "nsIStreamListener.h"
#include "nsIFile.h"
#include "nsIPermission.h"
#include "nsString.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
@ -267,6 +269,8 @@ class nsExternalAppHandler final : public nsIStreamListener,
void SetShouldCloseWindow() { mShouldCloseWindow = true; }
protected:
bool IsDownloadSpam(nsIChannel* aChannel);
~nsExternalAppHandler();
nsCOMPtr<nsIFile> mTempFile;

View File

@ -29,6 +29,9 @@ support-files =
file_xml_attachment_test.xml
file_xml_attachment_test.xml^headers^
[browser_download_skips_dialog.js]
[browser_download_spam_permissions.js]
support-files =
test_spammy_page.html
[browser_download_urlescape.js]
support-files =
file_with@@funny_name.png

View File

@ -0,0 +1,103 @@
/* 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/. */
"use strict";
const { PermissionTestUtils } = ChromeUtils.import(
"resource://testing-common/PermissionTestUtils.jsm"
);
const TEST_URI = "https://example.com";
const TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
TEST_URI
);
const AUTOMATIC_DOWNLOAD_TOPIC = "blocked-automatic-download";
add_task(async function setup() {
// Create temp directory
let time = new Date().getTime();
let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
tempDir.append(time);
Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, tempDir);
PermissionTestUtils.add(
TEST_URI,
"automatic-download",
Services.perms.UNKNOWN
);
await SpecialPowers.pushPrefEnv({
set: [["browser.download.improvements_to_download_panel", true]],
});
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("browser.download.dir");
await IOUtils.remove(tempDir.path, { recursive: true });
});
});
add_task(async function check_download_spam_permissions() {
const INITIAL_TABS_COUNT = gBrowser.tabs.length;
let publicList = await Downloads.getList(Downloads.PUBLIC);
let downloadFinishedPromise = promiseDownloadFinished(
publicList,
true /* stop the download from openning */
);
let blockedDownloadsCount = 0;
let blockedDownloadsURI = "";
let automaticDownloadObserver = {
observe: function automatic_download_observe(aSubject, aTopic, aData) {
if (aTopic === AUTOMATIC_DOWNLOAD_TOPIC) {
blockedDownloadsCount++;
blockedDownloadsURI = aData;
}
},
};
Services.obs.addObserver(automaticDownloadObserver, AUTOMATIC_DOWNLOAD_TOPIC);
let newTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
TEST_PATH + "test_spammy_page.html"
);
registerCleanupFunction(async () => {
await publicList.removeFinished();
BrowserTestUtils.removeTab(newTab);
Services.obs.removeObserver(
automaticDownloadObserver,
AUTOMATIC_DOWNLOAD_TOPIC
);
});
let download = await downloadFinishedPromise;
TestUtils.waitForCondition(
() => gBrowser.tabs.length == INITIAL_TABS_COUNT + 1
);
is(
PermissionTestUtils.testPermission(TEST_URI, "automatic-download"),
Services.perms.PROMPT_ACTION,
"The permission to prompt the user should be stored."
);
ok(
await IOUtils.exists(download.target.path),
"One file should be downloaded"
);
let aCopyFilePath = download.target.path.replace(".pdf", "(1).pdf");
is(
await IOUtils.exists(aCopyFilePath),
false,
"An other file should be blocked"
);
TestUtils.waitForCondition(() => blockedDownloadsCount >= 99);
is(blockedDownloadsCount, 99, "Browser should block 99 downloads");
is(
blockedDownloadsURI,
TEST_URI,
"The test URI should have blocked automatic downloads"
);
});

View File

@ -199,10 +199,13 @@ async function waitForProtocolAppChooserDialog(browser, state) {
);
}
async function promiseDownloadFinished(list) {
async function promiseDownloadFinished(list, stopFromOpening) {
return new Promise(resolve => {
list.addView({
onDownloadChanged(download) {
if (stopFromOpening) {
download.launchWhenSucceeded = false;
}
info("Download changed!");
if (download.succeeded || download.error) {
info("Download succeeded or errored");

View File

@ -0,0 +1,26 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>...</title>
</head>
<body>
<p> Hello, it's the spammy page! </p>
<script type="text/javascript">
let count = 0;
window.onload = window.onclick = function() {
if (count < 100) {
count++;
let l = document.createElement('a');
l.href = 'data:text/plain,some text';
l.download = 'sometext.pdf';
document.body.appendChild(l);
l.click();
}
}
</script>
</body>
</html>