blob: e491a595572713c041ad01b3615cc2f553f64be8 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/safe_browsing/content/browser/ui_manager.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/values.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/content/browser/content_unsafe_resource_util.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page_factory.h"
#include "components/safe_browsing/content/browser/safe_browsing_controller_client.h"
#include "components/safe_browsing/core/browser/db/util.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/settings_page_helper.h"
#include "components/security_interstitials/core/base_safe_browsing_error_ui.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "services/network/public/cpp/cross_thread_pending_shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using content::BrowserThread;
static const char* kGoodURL = "https://www.good.com";
static const char* kGoodIpAddress = "http://1.2.3.4/";
static const char* kBadURL = "https://www.malware.com";
static const char* kBadURLWithAlternateScheme = "http://www.malware.com";
static const char* kBadURLWithPath = "https://www.malware.com/index.html";
static const char* kIpAddress = "https://195.127.0.11/uploads";
static const char* kIpAddressAlternatePathAndScheme =
"http://195.127.0.11/otherPath";
static const char* kRedirectURL = "https://www.test1.com";
static const char* kAnotherRedirectURL = "https://www.test2.com";
namespace safe_browsing {
class SafeBrowsingCallbackWaiter {
public:
SafeBrowsingCallbackWaiter() = default;
bool callback_called() const { return callback_called_; }
bool proceed() const { return proceed_; }
bool showed_interstitial() const { return showed_interstitial_; }
bool has_post_commit_interstitial_skipped() const {
return has_post_commit_interstitial_skipped_;
}
void OnBlockingPageDone(
security_interstitials::UnsafeResource::UrlCheckResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback_called_ = true;
proceed_ = result.proceed;
showed_interstitial_ = result.showed_interstitial;
has_post_commit_interstitial_skipped_ =
result.has_post_commit_interstitial_skipped;
loop_.Quit();
}
void WaitForCallback() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
loop_.Run();
}
private:
bool callback_called_ = false;
bool proceed_ = false;
bool showed_interstitial_ = false;
bool has_post_commit_interstitial_skipped_ = false;
base::RunLoop loop_;
};
// A test blocking page that does not create windows.
class TestSafeBrowsingBlockingPage : public SafeBrowsingBlockingPage {
public:
TestSafeBrowsingBlockingPage(BaseUIManager* manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const UnsafeResourceList& unsafe_resources)
: SafeBrowsingBlockingPage(
manager,
web_contents,
main_frame_url,
unsafe_resources,
std::make_unique<safe_browsing::SafeBrowsingControllerClient>(
web_contents,
std::make_unique<security_interstitials::MetricsHelper>(
unsafe_resources[0].url,
BaseBlockingPage::GetReportingInfo(
unsafe_resources,
/*blocked_page_shown_timestamp=*/std::nullopt),
/*history_service=*/nullptr),
/*prefs=*/nullptr,
manager->app_locale(),
manager->default_safe_page(),
/*settings_helper=*/nullptr),
BaseSafeBrowsingErrorUI::SBErrorDisplayOptions(
BaseBlockingPage::IsMainPageResourceLoadPending(
unsafe_resources),
false, // is_extended_reporting_opt_in_allowed
false, // is_off_the_record
false, // is_extended_reporting_enabled
false, // is_extended_reporting_policy_managed
false, // is_enhanced_protection_enabled
false, // is_proceed_anyway_disabled
false, // should_open_links_in_new_tab
true, // always_show_back_to_safety
false, // is_enhanced_protection_message_enabled
false, // is_safe_browsing_managed
"cpn_safe_browsing"), // help_center_article_link
true, // should_trigger_reporting
/*history_service=*/nullptr,
/*navigation_observer_manager=*/nullptr,
/*metrics_collector=*/nullptr,
/*trigger_manager=*/nullptr,
/*is_proceed_anyway_disabled=*/false,
/*is_safe_browsing_surveys_enabled=*/true,
/*trust_safety_sentiment_service_trigger=*/base::NullCallback(),
/*ignore_auto_revocation_notifications_trigger=*/
base::NullCallback()) {
// Don't delay details at all for the unittest.
SetThreatDetailsProceedDelayForTesting(0);
DontCreateViewForTesting();
}
};
// A factory that creates TestSafeBrowsingBlockingPages.
class TestSafeBrowsingBlockingPageFactory
: public SafeBrowsingBlockingPageFactory {
public:
TestSafeBrowsingBlockingPageFactory() = default;
~TestSafeBrowsingBlockingPageFactory() override = default;
SafeBrowsingBlockingPage* CreateSafeBrowsingPage(
BaseUIManager* delegate,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources,
bool should_trigger_reporting,
std::optional<base::TimeTicks> blocked_page_shown_timestamp) override {
return new TestSafeBrowsingBlockingPage(delegate, web_contents,
main_frame_url, unsafe_resources);
}
security_interstitials::SecurityInterstitialPage* CreateEnterpriseWarnPage(
BaseUIManager* ui_manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
NOTREACHED();
}
security_interstitials::SecurityInterstitialPage* CreateEnterpriseBlockPage(
BaseUIManager* ui_manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
NOTREACHED();
}
};
class TestSafeBrowsingUIManagerDelegate
: public SafeBrowsingUIManager::Delegate {
public:
TestSafeBrowsingUIManagerDelegate() {
safe_browsing::RegisterProfilePrefs(pref_service_.registry());
}
~TestSafeBrowsingUIManagerDelegate() override = default;
// SafeBrowsingUIManager::Delegate:
std::string GetApplicationLocale() override { return "en-us"; }
void TriggerSecurityInterstitialShownExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& reason,
int net_error_code) override {}
void TriggerSecurityInterstitialProceededExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& reason,
int net_error_code) override {}
void TriggerUrlFilteringInterstitialExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& threat_type,
safe_browsing::RTLookupResponse rt_lookup_response) override {}
prerender::NoStatePrefetchContents* GetNoStatePrefetchContentsIfExists(
content::WebContents* web_contents) override {
return nullptr;
}
bool IsHostingExtension(content::WebContents* web_contents) override {
return is_hosting_extension_;
}
PrefService* GetPrefs(content::BrowserContext* browser_context) override {
return &pref_service_;
}
history::HistoryService* GetHistoryService(
content::BrowserContext* browser_context) override {
return nullptr;
}
PingManager* GetPingManager(
content::BrowserContext* browser_context) override {
return nullptr;
}
bool IsMetricsAndCrashReportingEnabled() override { return false; }
void set_is_hosting_extension(bool is_hosting_extension) {
is_hosting_extension_ = is_hosting_extension;
}
private:
bool is_hosting_extension_ = false;
TestingPrefServiceSimple pref_service_;
};
class SafeBrowsingUIManagerTest : public content::RenderViewHostTestHarness {
public:
SafeBrowsingUIManagerTest() {
auto ui_manager_delegate =
std::make_unique<TestSafeBrowsingUIManagerDelegate>();
raw_ui_manager_delegate_ = ui_manager_delegate.get();
ui_manager_ = new SafeBrowsingUIManager(
std::move(ui_manager_delegate),
std::make_unique<TestSafeBrowsingBlockingPageFactory>(),
GURL("chrome://new-tab-page/"));
}
~SafeBrowsingUIManagerTest() override = default;
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
SafeBrowsingUIManager::CreateAllowlistForTesting(web_contents());
}
bool IsAllowlistedForMalware(const char* url) {
auto* primary_main_frame = web_contents()->GetPrimaryMainFrame();
return ui_manager_->IsAllowlisted(
GURL(url),
security_interstitials::UnsafeResourceLocator::
CreateForRenderFrameToken(
primary_main_frame->GetGlobalId().child_id,
primary_main_frame->GetFrameToken().value()),
/*navigation_id=*/std::nullopt,
SBThreatType::SB_THREAT_TYPE_URL_MALWARE);
}
bool IsAllowlisted(security_interstitials::UnsafeResource resource) {
return ui_manager_->IsAllowlisted(resource.url, resource.rfh_locator,
resource.navigation_id,
resource.threat_type);
}
void AddToAllowlist(security_interstitials::UnsafeResource resource,
bool pending) {
ui_manager_->AddToAllowlistUrlSet(resource.url, resource.navigation_id,
web_contents(), pending,
resource.threat_type);
}
void AddToAllowlistForMalware(const char* url, bool pending) {
ui_manager_->AddToAllowlistUrlSet(GURL(url), /*navigation_id=*/std::nullopt,
web_contents(), pending,
SBThreatType::SB_THREAT_TYPE_URL_MALWARE);
}
security_interstitials::UnsafeResource MakeUnsafeResource(
const char* url,
const SBThreatType threat_type =
SBThreatType::SB_THREAT_TYPE_URL_MALWARE) {
auto* primary_main_frame = web_contents()->GetPrimaryMainFrame();
return MakeUnsafeResource(url, primary_main_frame->GetGlobalId(),
primary_main_frame->GetFrameToken(), threat_type);
}
security_interstitials::UnsafeResource MakeUnsafeResource(
const char* url,
content::GlobalRenderFrameHostId frame_id,
const blink::LocalFrameToken& frame_token,
const SBThreatType threat_type) {
security_interstitials::UnsafeResource resource;
resource.url = GURL(url);
resource.rfh_locator = security_interstitials::UnsafeResourceLocator::
CreateForRenderFrameToken(frame_id.child_id, frame_token.value());
resource.threat_type = threat_type;
return resource;
}
void StartNavigation(const char* url) {
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL(url), web_contents());
navigation->Start();
}
security_interstitials::UnsafeResource MakeUnsafeResourceAndStartNavigation(
const char* url) {
security_interstitials::UnsafeResource resource = MakeUnsafeResource(url);
// The WC doesn't have a URL without a navigation. A main-frame malware
// unsafe resource must be a pending navigation.
StartNavigation(url);
return resource;
}
void SimulateBlockingPageDone(
const std::vector<security_interstitials::UnsafeResource>& resources,
bool proceed) {
GURL main_frame_url;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
if (entry)
main_frame_url = entry->GetURL();
ui_manager_->OnBlockingPageDone(resources, proceed, web_contents(),
main_frame_url,
true /* showed_interstitial */);
}
protected:
SafeBrowsingUIManager* ui_manager() { return ui_manager_.get(); }
TestSafeBrowsingUIManagerDelegate* ui_manager_delegate() {
return raw_ui_manager_delegate_;
}
private:
scoped_refptr<SafeBrowsingUIManager> ui_manager_;
raw_ptr<TestSafeBrowsingUIManagerDelegate> raw_ui_manager_delegate_ = nullptr;
};
TEST_F(SafeBrowsingUIManagerTest, Allowlist) {
StartNavigation(kBadURL);
AddToAllowlistForMalware(kBadURL, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(kBadURL));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresSitesNotAdded) {
StartNavigation(kGoodURL);
EXPECT_FALSE(IsAllowlistedForMalware(kGoodURL));
}
TEST_F(SafeBrowsingUIManagerTest,
PendingAllowlistOnlyAddedOnceForSameNavigation) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
resource.navigation_id = 1;
// AddToAllowlist is called twice with the same navigation id.
AddToAllowlist(resource, /*pending=*/true);
AddToAllowlist(resource, /*pending=*/true);
EXPECT_FALSE(IsAllowlisted(resource));
SBThreatType threat_type;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
resource.url, entry,
unsafe_resource_util::GetWebContentsForResource(resource),
/*allowlist_only=*/false, &threat_type));
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
// Calling OnBlockingPageDone once should be sufficient to remove the
// URL from the pending allowlist.
SimulateBlockingPageDone(resources, /*proceed=*/false);
EXPECT_FALSE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
resource.url, entry,
unsafe_resource_util::GetWebContentsForResource(resource),
/*allowlist_only=*/false, &threat_type));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistRemembersThreatType) {
StartNavigation(kBadURL);
AddToAllowlistForMalware(kBadURL, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(kBadURL));
SBThreatType threat_type;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
GURL(kBadURL), entry, web_contents(), true, &threat_type));
EXPECT_EQ(SBThreatType::SB_THREAT_TYPE_URL_MALWARE, threat_type);
}
TEST_F(SafeBrowsingUIManagerTest, Allowlisted_RedirectChain) {
security_interstitials::UnsafeResource resource = MakeUnsafeResource(
kBadURL, SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING);
AddToAllowlist(resource, /*pending=*/false);
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL(kGoodURL), web_contents());
navigation->Start();
navigation->Redirect(GURL(kBadURL));
navigation->Redirect(GURL(kAnotherRedirectURL));
navigation->Commit();
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_EQ(entry->GetRedirectChain().size(), 3u);
resource.url = GURL(kAnotherRedirectURL);
// Although kAnotherRedirectURL is not directly allowlisted, IsAllowlisted
// should return true because one of the URLs on its redirect chain is
// allowlisted.
EXPECT_TRUE(IsAllowlisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresPath) {
StartNavigation(kBadURL);
AddToAllowlistForMalware(kBadURL, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(kBadURL));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
EXPECT_TRUE(IsAllowlistedForMalware(kBadURLWithPath));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresScheme) {
StartNavigation(kBadURL);
AddToAllowlistForMalware(kBadURL, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(kBadURL));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
EXPECT_TRUE(IsAllowlistedForMalware(kBadURLWithAlternateScheme));
EXPECT_FALSE(IsAllowlistedForMalware(kGoodURL));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIPAddress) {
StartNavigation(kIpAddress);
AddToAllowlistForMalware(kIpAddress, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(kIpAddress));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
EXPECT_TRUE(IsAllowlistedForMalware(kIpAddressAlternatePathAndScheme));
EXPECT_FALSE(IsAllowlistedForMalware(kGoodIpAddress));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresThreatType) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToAllowlist(resource, /*pending=*/false);
EXPECT_TRUE(IsAllowlisted(resource));
security_interstitials::UnsafeResource resource_phishing =
MakeUnsafeResource(kBadURL);
resource_phishing.threat_type = SBThreatType::SB_THREAT_TYPE_URL_PHISHING;
EXPECT_TRUE(IsAllowlisted(resource_phishing));
}
TEST_F(SafeBrowsingUIManagerTest, CallbackProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
}
TEST_F(SafeBrowsingUIManagerTest, UICallbackDontProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, false);
EXPECT_FALSE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_FALSE(waiter.proceed());
}
namespace {
// A WebContentsDelegate that records whether
// VisibleSecurityStateChanged() was called.
class SecurityStateWebContentsDelegate : public content::WebContentsDelegate {
public:
SecurityStateWebContentsDelegate() = default;
SecurityStateWebContentsDelegate(const SecurityStateWebContentsDelegate&) =
delete;
SecurityStateWebContentsDelegate& operator=(
const SecurityStateWebContentsDelegate&) = delete;
~SecurityStateWebContentsDelegate() override = default;
bool visible_security_state_changed() const {
return visible_security_state_changed_;
}
void ClearVisibleSecurityStateChanged() {
visible_security_state_changed_ = false;
}
// WebContentsDelegate:
void VisibleSecurityStateChanged(content::WebContents* source) override {
visible_security_state_changed_ = true;
}
private:
bool visible_security_state_changed_ = false;
};
} // namespace
// Tests that the WebContentsDelegate is notified of a visible security
// state change when a blocking page is shown.
TEST_F(SafeBrowsingUIManagerTest, VisibleSecurityStateChanged) {
SecurityStateWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
// Simulate a blocking page.
security_interstitials::UnsafeResource resource = MakeUnsafeResource(kBadURL);
// Needed for showing the blocking page.
resource.threat_source = safe_browsing::ThreatSource::ANDROID_SAFEBROWSING;
NavigateAndCommit(GURL(kBadURL));
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
ui_manager()->DisplayBlockingPage(resource);
EXPECT_TRUE(delegate.visible_security_state_changed());
// Simulate proceeding through the blocking page.
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(delegate.visible_security_state_changed());
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
EXPECT_TRUE(IsAllowlisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, ShowBlockPageNoCallback) {
SecurityStateWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
// Simulate a blocking page.
security_interstitials::UnsafeResource resource = MakeUnsafeResource(kBadURL);
// Needed for showing the blocking page.
resource.threat_source = safe_browsing::ThreatSource::ANDROID_SAFEBROWSING;
// This call caused a crash in https://crbug.com/1058094. Just verify that we
// don't crash anymore.
ui_manager()->DisplayBlockingPage(resource);
}
TEST_F(SafeBrowsingUIManagerTest, NoInterstitialInExtensions) {
// Pretend the current web contents is in an extension.
ui_manager_delegate()->set_is_hosting_extension(true);
security_interstitials::UnsafeResource resource = MakeUnsafeResource(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
ui_manager()->StartDisplayingBlockingPage(resource);
waiter.WaitForCallback();
EXPECT_FALSE(waiter.proceed());
EXPECT_FALSE(waiter.showed_interstitial());
}
TEST_F(SafeBrowsingUIManagerTest, DisplayInterstitial) {
security_interstitials::UnsafeResource resource = MakeUnsafeResource(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
ui_manager()->StartDisplayingBlockingPage(resource);
waiter.WaitForCallback();
EXPECT_FALSE(waiter.proceed());
EXPECT_TRUE(waiter.showed_interstitial());
EXPECT_TRUE(waiter.has_post_commit_interstitial_skipped());
}
TEST_F(SafeBrowsingUIManagerTest, DisplayInterstitial_PostCommitInterstitial) {
security_interstitials::UnsafeResource resource = MakeUnsafeResource(kBadURL);
resource.threat_source = safe_browsing::ThreatSource::ANDROID_SAFEBROWSING;
// Make it a post commit interstitial.
resource.threat_type = SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING;
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
ui_manager()->StartDisplayingBlockingPage(resource);
waiter.WaitForCallback();
EXPECT_FALSE(waiter.proceed());
EXPECT_FALSE(waiter.showed_interstitial());
EXPECT_FALSE(waiter.has_post_commit_interstitial_skipped());
}
TEST_F(SafeBrowsingUIManagerTest, InvalidRenderFrameHostId) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
// Clobber the resource's RenderFrameHost id so that subsequent WebContents
// lookups return null. This simulates the destruction of a RenderFrameHost
// (for the purposes of a WebContents lookup) which SafeBrowsing needs to
// handle.
content::GlobalRenderFrameHostId invalid_rfh_id;
resource.rfh_locator =
security_interstitials::UnsafeResourceLocator::CreateForRenderFrameToken(
invalid_rfh_id.child_id, base::UnguessableToken::Create());
ASSERT_FALSE(unsafe_resource_util::GetWebContentsForResource(resource));
EXPECT_FALSE(IsAllowlisted(resource));
}
// Regression test for https://g-issues.chromium.org/issues/327838835
TEST_F(SafeBrowsingUIManagerTest,
DontSendClientSafeBrowsingWarningShownReportNullWebContents) {
ASSERT_FALSE(
ui_manager()->ShouldSendClientSafeBrowsingWarningShownReport(nullptr));
}
TEST_F(SafeBrowsingUIManagerTest,
AllowlistSetSeverestThreatTypeInRedirectChain) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kGoodURL, SBThreatType::SB_THREAT_TYPE_API_ABUSE);
AddToAllowlist(resource, true);
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL(kGoodURL), web_contents());
navigation->Start();
navigation->Redirect(GURL(kRedirectURL));
navigation->Redirect(GURL(kAnotherRedirectURL));
navigation->Commit();
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_EQ(entry->GetRedirectChain().size(), 3u);
// The threat type for the final redirected url should be set to the first url
// in the chain.
SBThreatType threat_type;
security_interstitials::UnsafeResource final_resource =
MakeUnsafeResource(kAnotherRedirectURL);
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
final_resource.url, entry,
unsafe_resource_util::GetWebContentsForResource(final_resource), false,
&threat_type));
EXPECT_EQ(threat_type, resource.threat_type);
security_interstitials::UnsafeResource redirect_resource =
MakeUnsafeResource(kRedirectURL, SBThreatType::SB_THREAT_TYPE_BILLING);
AddToAllowlist(redirect_resource, true);
// The second redirect url has a less severe threat type, the final
// url's threat type should still be the first one in the chain.
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
final_resource.url, entry,
unsafe_resource_util::GetWebContentsForResource(final_resource), false,
&threat_type));
EXPECT_EQ(threat_type, resource.threat_type);
redirect_resource = MakeUnsafeResource(
kRedirectURL, SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK);
AddToAllowlist(redirect_resource, true);
// Now that the second redirect url has a more severe threat type, the final
// url's threat type should be set to that one.
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
final_resource.url, entry,
unsafe_resource_util::GetWebContentsForResource(final_resource), false,
&threat_type));
EXPECT_EQ(threat_type, redirect_resource.threat_type);
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistViewSource) {
const char* view_source_url = "view-source:https://www.malware.com";
StartNavigation(view_source_url);
AddToAllowlistForMalware(view_source_url, /*pending=*/false);
EXPECT_TRUE(IsAllowlistedForMalware(view_source_url));
EXPECT_FALSE(IsAllowlistedForMalware(kBadURL));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
EXPECT_TRUE(IsAllowlistedForMalware(view_source_url));
EXPECT_FALSE(IsAllowlistedForMalware(kBadURL));
}
} // namespace safe_browsing