blob: 604df4cc2efee3080e7215c8d179a95e4df0e71d [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/external_protocol/external_protocol_handler.h"
#include <memory>
#include <utility>
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_android.h"
#include "components/navigation_interception/intercept_navigation_delegate.h"
#else
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#endif // BUILDFLAG(IS_ANDROID)
class FakeExternalProtocolHandlerWorker
: public shell_integration::DefaultSchemeClientWorker {
public:
FakeExternalProtocolHandlerWorker(
const GURL& url,
shell_integration::DefaultWebClientState os_state,
const std::u16string& program_name)
: shell_integration::DefaultSchemeClientWorker(url),
os_state_(os_state),
program_name_(program_name) {}
private:
~FakeExternalProtocolHandlerWorker() override = default;
shell_integration::DefaultWebClientState CheckIsDefaultImpl() override {
return os_state_;
}
std::u16string GetDefaultClientNameImpl() override { return program_name_; }
void SetAsDefaultImpl(base::OnceClosure on_finished_callback) override {
std::move(on_finished_callback).Run();
}
shell_integration::DefaultWebClientState os_state_;
std::u16string program_name_;
};
class FakeExternalProtocolHandlerDelegate
: public ExternalProtocolHandler::Delegate {
public:
explicit FakeExternalProtocolHandlerDelegate(base::OnceClosure on_complete)
: block_state_(ExternalProtocolHandler::BLOCK),
os_state_(shell_integration::UNKNOWN_DEFAULT),
complete_on_launch_(false),
has_launched_(false),
has_prompted_(false),
has_blocked_(false),
on_complete_(std::move(on_complete)),
program_name_(u"") {}
scoped_refptr<shell_integration::DefaultSchemeClientWorker> CreateShellWorker(
const GURL& url) override {
return new FakeExternalProtocolHandlerWorker(url, os_state_, program_name_);
}
ExternalProtocolHandler::BlockState GetBlockState(const std::string& scheme,
Profile* profile) override {
return block_state_;
}
void BlockRequest() override {
EXPECT_TRUE(block_state_ == ExternalProtocolHandler::BLOCK ||
os_state_ == shell_integration::IS_DEFAULT);
has_blocked_ = true;
if (on_complete_)
std::move(on_complete_).Run();
}
void RunExternalProtocolDialog(
const GURL& url,
content::WebContents* web_contents,
ui::PageTransition page_transition,
bool has_user_gesture,
const std::optional<url::Origin>& initiating_origin,
const std::u16string& program_name) override {
EXPECT_EQ(block_state_, ExternalProtocolHandler::UNKNOWN);
EXPECT_NE(os_state_, shell_integration::IS_DEFAULT);
EXPECT_EQ(program_name_, program_name);
has_prompted_ = true;
launch_or_prompt_url_ = url;
initiating_origin_ = initiating_origin;
}
void LaunchUrlWithoutSecurityCheck(
const GURL& url,
content::WebContents* web_contents) override {
EXPECT_EQ(block_state_, ExternalProtocolHandler::DONT_BLOCK);
EXPECT_NE(os_state_, shell_integration::IS_DEFAULT);
has_launched_ = true;
launch_or_prompt_url_ = url;
if (complete_on_launch_ && on_complete_)
std::move(on_complete_).Run();
}
void FinishedProcessingCheck() override {
if (on_complete_)
std::move(on_complete_).Run();
}
void set_os_state(shell_integration::DefaultWebClientState value) {
os_state_ = value;
}
void set_program_name(const std::u16string& value) { program_name_ = value; }
void set_block_state(ExternalProtocolHandler::BlockState value) {
block_state_ = value;
}
// Set this to true if you need the test to be completed upon calling into
// LaunchUrlWithoutSecurityCheck which is the case when testing
// ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck.
void set_complete_on_launch(bool value) { complete_on_launch_ = value; }
bool has_launched() { return has_launched_; }
bool has_prompted() { return has_prompted_; }
bool has_blocked() { return has_blocked_; }
const std::optional<url::Origin>& initiating_origin() {
return initiating_origin_;
}
const std::string& launch_or_prompt_url() {
return launch_or_prompt_url_.spec();
}
private:
ExternalProtocolHandler::BlockState block_state_;
shell_integration::DefaultWebClientState os_state_;
bool complete_on_launch_;
bool has_launched_;
bool has_prompted_;
bool has_blocked_;
GURL launch_or_prompt_url_;
std::optional<url::Origin> initiating_origin_;
base::OnceClosure on_complete_;
std::u16string program_name_;
};
class ExternalProtocolHandlerTest : public testing::Test {
public:
content::WebContents* GetWebContents() const { return web_contents_.get(); }
protected:
ExternalProtocolHandlerTest() : delegate_(run_loop_.QuitClosure()) {}
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
rvh_test_enabler_ = std::make_unique<content::RenderViewHostTestEnabler>();
web_contents_ = content::WebContentsTester::CreateTestWebContents(
profile_.get(), nullptr);
#if !BUILDFLAG(IS_ANDROID)
web_modal::WebContentsModalDialogManager::CreateForWebContents(
web_contents_.get());
#endif // !BUILDFLAG(IS_ANDROID)
}
void TearDown() override {
// Ensure that g_accept_requests gets set back to true after test execution.
ExternalProtocolHandler::PermitLaunchUrl();
TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
}
enum class Action { PROMPT, LAUNCH, BLOCK, NONE };
void DoTest(ExternalProtocolHandler::BlockState block_state,
shell_integration::DefaultWebClientState os_state,
Action expected_action) {
DoTest(block_state, os_state, expected_action, GURL("mailto:test@test.com"),
url::Origin::Create(GURL("https://example.test")),
url::Origin::Create(GURL("https://precursor.test")), u"TestApp");
}
// Launches |url| in the current WebContents and checks that the
// ExternalProtocolHandler's delegate is called with the correct action (as
// given in |expected_action|). |initiating_origin| is passed to the
// ExternalProtocolHandler to attribute the request to launch the URL to a
// particular site. If |initiating_origin| is opaque (in production, an
// example would be a sandboxed iframe), then the delegate should be passed
// the origin's precursor origin. The precursor origin is the origin that
// created |initiating_origin|, and the expected precursor origin, if any, is
// provided in |expected_initiating_precursor_origin|.
void DoTest(ExternalProtocolHandler::BlockState block_state,
shell_integration::DefaultWebClientState os_state,
Action expected_action,
const GURL& url,
const url::Origin& initiating_origin,
const url::Origin& expected_initiating_precursor_origin,
const std::u16string& program_name) {
EXPECT_FALSE(delegate_.has_prompted());
EXPECT_FALSE(delegate_.has_launched());
EXPECT_FALSE(delegate_.has_blocked());
ExternalProtocolHandler::SetDelegateForTesting(&delegate_);
delegate_.set_block_state(block_state);
delegate_.set_os_state(os_state);
delegate_.set_program_name(program_name);
ExternalProtocolHandler::LaunchUrl(
url,
base::BindRepeating(&ExternalProtocolHandlerTest::GetWebContents,
base::Unretained(this)),
ui::PAGE_TRANSITION_LINK, /*has_user_gesture=*/true,
/*is_in_fenced_frame_tree=*/false, initiating_origin,
content::WeakDocumentPtr()
#if BUILDFLAG(IS_ANDROID)
,
nullptr
#endif
);
run_loop_.Run();
ExternalProtocolHandler::SetDelegateForTesting(nullptr);
EXPECT_EQ(expected_action == Action::PROMPT, delegate_.has_prompted());
EXPECT_EQ(expected_action == Action::LAUNCH, delegate_.has_launched());
EXPECT_EQ(expected_action == Action::BLOCK, delegate_.has_blocked());
if (expected_action == Action::PROMPT) {
ASSERT_TRUE(delegate_.initiating_origin().has_value());
if (initiating_origin.opaque()) {
EXPECT_EQ(expected_initiating_precursor_origin,
delegate_.initiating_origin().value());
} else {
EXPECT_EQ(initiating_origin, delegate_.initiating_origin().value());
}
} else {
EXPECT_FALSE(delegate_.initiating_origin().has_value());
}
}
content::BrowserTaskEnvironment task_environment_;
base::RunLoop run_loop_;
FakeExternalProtocolHandlerDelegate delegate_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
std::unique_ptr<content::WebContents> web_contents_;
};
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeDefault) {
DoTest(ExternalProtocolHandler::BLOCK, shell_integration::IS_DEFAULT,
Action::BLOCK);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeNotDefault) {
DoTest(ExternalProtocolHandler::BLOCK, shell_integration::NOT_DEFAULT,
Action::BLOCK);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeUnknown) {
DoTest(ExternalProtocolHandler::BLOCK, shell_integration::UNKNOWN_DEFAULT,
Action::BLOCK);
}
TEST_F(ExternalProtocolHandlerTest,
TestLaunchSchemeBlockedChromeOtherModeDefault) {
DoTest(ExternalProtocolHandler::BLOCK,
shell_integration::OTHER_MODE_IS_DEFAULT, Action::BLOCK);
}
// Android doesn't use the external protocol dialog.
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeDefault) {
DoTest(ExternalProtocolHandler::DONT_BLOCK, shell_integration::IS_DEFAULT,
Action::BLOCK);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeNotDefault) {
DoTest(ExternalProtocolHandler::DONT_BLOCK, shell_integration::NOT_DEFAULT,
Action::LAUNCH);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeUnknown) {
DoTest(ExternalProtocolHandler::DONT_BLOCK,
shell_integration::UNKNOWN_DEFAULT, Action::LAUNCH);
}
TEST_F(ExternalProtocolHandlerTest,
TestLaunchSchemeUnBlockedChromeOtherModeDefault) {
DoTest(ExternalProtocolHandler::DONT_BLOCK,
shell_integration::OTHER_MODE_IS_DEFAULT, Action::LAUNCH);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeDefault) {
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::IS_DEFAULT,
Action::BLOCK);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeNotDefault) {
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::NOT_DEFAULT,
Action::PROMPT);
}
TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeUnknown) {
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::UNKNOWN_DEFAULT,
Action::PROMPT);
}
TEST_F(ExternalProtocolHandlerTest,
TestLaunchSchemeUnknownChromeOtherModeDefault) {
DoTest(ExternalProtocolHandler::UNKNOWN,
shell_integration::OTHER_MODE_IS_DEFAULT, Action::PROMPT);
}
TEST_F(ExternalProtocolHandlerTest, TestUrlEscape) {
GURL url("alert:test message\" --bad%2B\r\n 文本 \"file");
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::NOT_DEFAULT,
Action::PROMPT, url, url::Origin::Create(GURL("https://example.test")),
url::Origin::Create(GURL("https://precursor.test")), u"TestApp");
// Expect that the "\r\n" has been removed, and all other illegal URL
// characters have been escaped.
EXPECT_EQ("alert:test%20message%22%20--bad%2B%20%E6%96%87%E6%9C%AC%20%22file",
delegate_.launch_or_prompt_url());
}
TEST_F(ExternalProtocolHandlerTest, TestNoDialogWithoutManager) {
// WebContents without a dialog manager should not prompt crbug.com/40064553.
GetWebContents()->SetUserData(
web_modal::WebContentsModalDialogManager::UserDataKey(), nullptr);
EXPECT_EQ(nullptr, web_modal::WebContentsModalDialogManager::FromWebContents(
GetWebContents()));
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::UNKNOWN_DEFAULT,
Action::NONE);
}
#else // if !BUILDFLAG(IS_ANDROID)
class MockInterceptNavigationDelegate
: public navigation_interception::InterceptNavigationDelegate {
public:
MockInterceptNavigationDelegate()
: InterceptNavigationDelegate(base::android::AttachCurrentThread(),
nullptr) {}
MOCK_METHOD5(HandleSubframeExternalProtocol,
void(const GURL&,
ui::PageTransition,
bool,
const std::optional<url::Origin>&,
mojo::PendingRemote<network::mojom::URLLoaderFactory>*));
};
TEST_F(ExternalProtocolHandlerTest, TestUrlEscape_Android) {
GURL url("alert:test message\" --bad%2B\r\n 文本 \"file");
GURL escaped(
"alert:test%20message%22%20--bad%2B%20%E6%96%87%E6%9C%AC%20%22file");
auto delegate = std::make_unique<MockInterceptNavigationDelegate>();
url::Origin precursor_origin =
url::Origin::Create(GURL("https://precursor.test"));
url::Origin opaque_origin =
url::Origin::Resolve(GURL("data:text/html,hi"), precursor_origin);
EXPECT_CALL(*delegate.get(),
HandleSubframeExternalProtocol(testing::Eq(escaped), testing::_,
true, testing::Eq(opaque_origin),
testing::Eq(nullptr)));
navigation_interception::InterceptNavigationDelegate::Associate(
web_contents_.get(), std::move(delegate));
ExternalProtocolHandler::LaunchUrl(
url,
base::BindRepeating(&ExternalProtocolHandlerTest::GetWebContents,
base::Unretained(this)),
ui::PAGE_TRANSITION_LINK, /*has_user_gesture=*/true,
/*is_in_fenced_frame_tree=*/false, opaque_origin,
content::WeakDocumentPtr(), nullptr);
}
#endif // if !BUILDFLAG(IS_ANDROID)
TEST_F(ExternalProtocolHandlerTest, TestUrlEscapeNoChecks) {
GURL url("alert:test message\" --bad%2B\r\n 文本 \"file");
EXPECT_FALSE(delegate_.has_prompted());
EXPECT_FALSE(delegate_.has_launched());
EXPECT_FALSE(delegate_.has_blocked());
ExternalProtocolHandler::SetDelegateForTesting(&delegate_);
delegate_.set_block_state(ExternalProtocolHandler::DONT_BLOCK);
delegate_.set_os_state(shell_integration::NOT_DEFAULT);
delegate_.set_complete_on_launch(true);
ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
url, web_contents_.get(), content::WeakDocumentPtr());
run_loop_.Run();
ExternalProtocolHandler::SetDelegateForTesting(nullptr);
EXPECT_FALSE(delegate_.has_prompted());
EXPECT_TRUE(delegate_.has_launched());
EXPECT_FALSE(delegate_.has_blocked());
EXPECT_FALSE(delegate_.initiating_origin().has_value());
// Expect that the "\r\n" has been removed, and all other illegal URL
// characters have been escaped.
EXPECT_EQ("alert:test%20message%22%20--bad%2B%20%E6%96%87%E6%9C%AC%20%22file",
delegate_.launch_or_prompt_url());
}
TEST_F(ExternalProtocolHandlerTest, TestGetBlockStateUnknown) {
base::HistogramTester histogram_tester;
ExternalProtocolHandler::BlockState block_state =
ExternalProtocolHandler::GetBlockState("tel", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state =
ExternalProtocolHandler::GetBlockState("news", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state =
ExternalProtocolHandler::GetBlockState("snews", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kPrompt, 3);
}
TEST_F(ExternalProtocolHandlerTest, TestGetBlockStateDefaultBlock) {
base::HistogramTester histogram_tester;
ExternalProtocolHandler::BlockState block_state =
ExternalProtocolHandler::GetBlockState("afp", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::BLOCK, block_state);
block_state =
ExternalProtocolHandler::GetBlockState("res", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::BLOCK, block_state);
block_state = ExternalProtocolHandler::GetBlockState("ie.http", nullptr,
profile_.get());
EXPECT_EQ(ExternalProtocolHandler::BLOCK, block_state);
EXPECT_EQ("mk", GURL("mk:@FooBar:ie.http:res://foo.bar/baz").scheme());
block_state =
ExternalProtocolHandler::GetBlockState("mk", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::BLOCK, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kDeniedDefault, 4);
}
TEST_F(ExternalProtocolHandlerTest, TestGetBlockStateDefaultDontBlock) {
base::HistogramTester histogram_tester;
ExternalProtocolHandler::BlockState block_state =
ExternalProtocolHandler::GetBlockState("mailto", nullptr, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::DONT_BLOCK, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kAllowedDefaultMail, 1);
}
TEST_F(ExternalProtocolHandlerTest, TestSetBlockState) {
base::HistogramTester histogram_tester;
const char kScheme_1[] = "custom1";
const char kScheme_2[] = "custom2";
url::Origin example_origin_1 =
url::Origin::Create(GURL("https://example.test"));
url::Origin example_origin_2 =
url::Origin::Create(GURL("https://example2.test"));
ExternalProtocolHandler::BlockState block_state =
ExternalProtocolHandler::GetBlockState(kScheme_1, &example_origin_1,
profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kPrompt, 4);
// Set to DONT_BLOCK for {kScheme_1, example_origin_1}, and make sure it is
// written to prefs.
ExternalProtocolHandler::SetBlockState(kScheme_1, example_origin_1,
ExternalProtocolHandler::DONT_BLOCK,
profile_.get());
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::DONT_BLOCK, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kAllowedByPreference, 1);
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kPrompt, 7);
// Set to DONT_BLOCK for {kScheme_2, example_origin_2}, and make sure it is
// written to prefs independently of {kScheme_1, example_origin_1}.
ExternalProtocolHandler::SetBlockState(kScheme_2, example_origin_2,
ExternalProtocolHandler::DONT_BLOCK,
profile_.get());
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::DONT_BLOCK, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::DONT_BLOCK, block_state);
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kAllowedByPreference, 3);
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kPrompt, 9);
const base::Value::Dict& protocol_origin_pairs =
profile_->GetPrefs()->GetDict(
prefs::kProtocolHandlerPerOriginAllowedProtocols);
base::Value::Dict expected_allowed_protocols_for_example_origin_1;
expected_allowed_protocols_for_example_origin_1.Set(kScheme_1, true);
const base::Value::Dict* allowed_protocols_for_example_origin_1 =
protocol_origin_pairs.FindDict(example_origin_1.Serialize());
EXPECT_EQ(expected_allowed_protocols_for_example_origin_1,
*allowed_protocols_for_example_origin_1);
base::Value::Dict expected_allowed_protocols_for_example_origin_2;
expected_allowed_protocols_for_example_origin_2.Set(kScheme_2, true);
const base::Value::Dict* allowed_protocols_for_example_origin_2 =
protocol_origin_pairs.FindDict(example_origin_2.Serialize());
EXPECT_EQ(expected_allowed_protocols_for_example_origin_2,
*allowed_protocols_for_example_origin_2);
// Note: BLOCK is no longer supported (it triggers a DCHECK in SetBlockState;
// see https://crbug.com/724919).
// Set back to UNKNOWN, and make sure this results in an empty dictionary.
ExternalProtocolHandler::SetBlockState(kScheme_1, example_origin_1,
ExternalProtocolHandler::UNKNOWN,
profile_.get());
ExternalProtocolHandler::SetBlockState(kScheme_2, example_origin_2,
ExternalProtocolHandler::UNKNOWN,
profile_.get());
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_1, &example_origin_1, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
block_state = ExternalProtocolHandler::GetBlockState(
kScheme_2, &example_origin_2, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kAllowedByPreference, 3);
histogram_tester.ExpectBucketCount(
ExternalProtocolHandler::kBlockStateMetric,
ExternalProtocolHandler::BlockStateMetric::kPrompt, 11);
}
TEST_F(ExternalProtocolHandlerTest, TestSetBlockStateWithUntrustowrthyOrigin) {
const char kScheme[] = "custom";
// This origin is untrustworthy because it is "http://"
url::Origin untrustworthy_origin =
url::Origin::Create(GURL("http://example.test"));
ExternalProtocolHandler::BlockState block_state =
ExternalProtocolHandler::GetBlockState(kScheme, &untrustworthy_origin,
profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
// Set to DONT_BLOCK for {kScheme, untrustworthy_origin}, and make sure it is
// not written to prefs. Calling SetBlockState with a non-trustworthy origin
// should not persist any state to prefs.
ExternalProtocolHandler::SetBlockState(kScheme, untrustworthy_origin,
ExternalProtocolHandler::DONT_BLOCK,
profile_.get());
block_state = ExternalProtocolHandler::GetBlockState(
kScheme, &untrustworthy_origin, profile_.get());
EXPECT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
EXPECT_TRUE(profile_->GetPrefs()
->GetDict(prefs::kProtocolHandlerPerOriginAllowedProtocols)
.empty());
}
#if !BUILDFLAG(IS_ANDROID)
// Test that an opaque initiating origin gets transformed to its precursor
// origin when the dialog is shown.
TEST_F(ExternalProtocolHandlerTest, TestOpaqueInitiatingOrigin) {
url::Origin precursor_origin =
url::Origin::Create(GURL("https://precursor.test"));
url::Origin opaque_origin =
url::Origin::Resolve(GURL("data:text/html,hi"), precursor_origin);
DoTest(ExternalProtocolHandler::UNKNOWN, shell_integration::NOT_DEFAULT,
Action::PROMPT, GURL("mailto:test@test.test"), opaque_origin,
precursor_origin, u"TestApp");
}
#endif