diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn index 120ee80b0a2df..0f1a647f9d2d8 100644 --- a/chromecast/browser/BUILD.gn +++ b/chromecast/browser/BUILD.gn @@ -565,6 +565,7 @@ cast_source_set("unittests") { "devtools/cast_devtools_manager_delegate_unittest.cc", "lru_renderer_cache_test.cc", "migration/migration_utils_test.cc", + "webui/cast_webui_controller_factory_unittest.cc", ] deps = [ diff --git a/chromecast/browser/cast_web_service.cc b/chromecast/browser/cast_web_service.cc index 5040a37906068..18de68282ef47 100644 --- a/chromecast/browser/cast_web_service.cc +++ b/chromecast/browser/cast_web_service.cc @@ -25,6 +25,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui_controller_factory.h" +#include "content/public/common/url_constants.h" #include "services/network/public/mojom/cookie_manager.mojom.h" namespace chromecast { @@ -117,15 +118,24 @@ void CastWebService::ClearLocalStorage(ClearLocalStorageCallback callback) { } bool CastWebService::IsCastWebUIOrigin(const url::Origin& origin) { - return std::ranges::contains(cast_webui_hosts_, origin.host()); + return origin.scheme() == content::kChromeUIScheme && + std::ranges::contains(cast_webui_hosts_, origin.host()); } void CastWebService::RegisterWebUiClient( mojo::PendingRemote client, const std::vector& hosts) { - cast_webui_hosts_ = hosts; - content::WebUIControllerFactory::RegisterFactory( - new CastWebUiControllerFactory(std::move(client), hosts)); + if (webui_factory_registered_) { + LOG(WARNING) << "Cast Web UI client already registered, ignoring."; + return; + } + webui_factory_registered_ = true; + // The factory restricts the requested hosts to the set of known Cast Web UI + // hosts. It is owned by the global factory registry and lives as long as the + // browser. + auto* factory = new CastWebUiControllerFactory(std::move(client), hosts); + cast_webui_hosts_ = factory->hosts(); + content::WebUIControllerFactory::RegisterFactory(factory); } void CastWebService::DeleteOwnedWebViews() { diff --git a/chromecast/browser/cast_web_service.h b/chromecast/browser/cast_web_service.h index c6cbb1d42ec79..19f3dcb039eaf 100644 --- a/chromecast/browser/cast_web_service.h +++ b/chromecast/browser/cast_web_service.h @@ -116,6 +116,7 @@ class CastWebService : public mojom::CastWebService { const std::unique_ptr overlay_renderer_cache_; bool immediately_delete_webviews_ = false; + bool webui_factory_registered_ = false; std::vector cast_webui_hosts_; diff --git a/chromecast/browser/webui/cast_webui_controller_factory.cc b/chromecast/browser/webui/cast_webui_controller_factory.cc index fb910a48f1d78..8be5ddcac0810 100644 --- a/chromecast/browser/webui/cast_webui_controller_factory.cc +++ b/chromecast/browser/webui/cast_webui_controller_factory.cc @@ -7,12 +7,14 @@ #include #include +#include "base/logging.h" #include "chromecast/browser/webui/cast_resource_data_source.h" #include "chromecast/browser/webui/cast_webui.h" #include "chromecast/browser/webui/constants.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui_controller_factory.h" +#include "content/public/common/url_constants.h" #include "url/gurl.h" namespace chromecast { @@ -20,8 +22,15 @@ namespace chromecast { CastWebUiControllerFactory::CastWebUiControllerFactory( mojo::PendingRemote client, const std::vector& hosts) - : client_(std::move(client)), hosts_(hosts) { + : client_(std::move(client)) { DCHECK(client_); + for (const std::string& host : hosts) { + if (IsKnownCastWebUiHost(host)) { + hosts_.push_back(host); + } else { + LOG(WARNING) << "Ignoring unknown Cast Web UI host '" << host << "'"; + } + } } CastWebUiControllerFactory::~CastWebUiControllerFactory() = default; @@ -29,7 +38,8 @@ CastWebUiControllerFactory::~CastWebUiControllerFactory() = default; content::WebUI::TypeID CastWebUiControllerFactory::GetWebUIType( content::BrowserContext* browser_context, const GURL& url) { - if (std::ranges::contains(hosts_, url.GetHost())) { + if (url.SchemeIs(content::kChromeUIScheme) && + std::ranges::contains(hosts_, url.GetHost())) { return const_cast(this); } return content::WebUI::kNoWebUI; diff --git a/chromecast/browser/webui/cast_webui_controller_factory.h b/chromecast/browser/webui/cast_webui_controller_factory.h index a44f3a125682d..10687b629a33d 100644 --- a/chromecast/browser/webui/cast_webui_controller_factory.h +++ b/chromecast/browser/webui/cast_webui_controller_factory.h @@ -36,9 +36,11 @@ class CastWebUiControllerFactory : public content::WebUIControllerFactory { content::WebUI* web_ui, const GURL& url) override; + const std::vector& hosts() const { return hosts_; } + private: mojo::Remote client_; - const std::vector hosts_; + std::vector hosts_; }; } // namespace chromecast diff --git a/chromecast/browser/webui/cast_webui_controller_factory_unittest.cc b/chromecast/browser/webui/cast_webui_controller_factory_unittest.cc new file mode 100644 index 0000000000000..22db8ead502b8 --- /dev/null +++ b/chromecast/browser/webui/cast_webui_controller_factory_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/browser/webui/cast_webui_controller_factory.h" + +#include +#include + +#include "base/test/task_environment.h" +#include "chromecast/browser/webui/constants.h" +#include "chromecast/browser/webui/mojom/webui.mojom.h" +#include "content/public/browser/web_ui.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace chromecast { +namespace { + +mojo::PendingRemote MakeClient() { + mojo::PendingRemote client; + std::ignore = client.InitWithNewPipeAndPassReceiver(); + return client; +} + +class CastWebUiControllerFactoryTest : public testing::Test { + protected: + base::test::TaskEnvironment task_environment_; +}; + +TEST_F(CastWebUiControllerFactoryTest, MatchesKnownHost) { + CastWebUiControllerFactory factory(MakeClient(), {kCastWebUIHomeHost}); + GURL url(std::string("chrome://") + kCastWebUIHomeHost); + EXPECT_NE(content::WebUI::kNoWebUI, factory.GetWebUIType(nullptr, url)); + EXPECT_TRUE(factory.UseWebUIForURL(nullptr, url)); +} + +TEST_F(CastWebUiControllerFactoryTest, IgnoresUnknownHost) { + CastWebUiControllerFactory factory(MakeClient(), + {kCastWebUIHomeHost, "unknown"}); + EXPECT_EQ(content::WebUI::kNoWebUI, + factory.GetWebUIType(nullptr, GURL("chrome://unknown"))); + EXPECT_FALSE(factory.UseWebUIForURL(nullptr, GURL("chrome://unknown"))); +} + +TEST_F(CastWebUiControllerFactoryTest, IgnoresWrongScheme) { + CastWebUiControllerFactory factory(MakeClient(), {kCastWebUIHomeHost}); + GURL url(std::string("http://") + kCastWebUIHomeHost); + EXPECT_EQ(content::WebUI::kNoWebUI, factory.GetWebUIType(nullptr, url)); + EXPECT_FALSE(factory.UseWebUIForURL(nullptr, url)); +} + +TEST_F(CastWebUiControllerFactoryTest, IgnoresUnrequestedHost) { + CastWebUiControllerFactory factory(MakeClient(), {kCastWebUIHomeHost}); + GURL url(std::string("chrome://") + kCastWebUIForceUpdateHost); + EXPECT_EQ(content::WebUI::kNoWebUI, factory.GetWebUIType(nullptr, url)); +} + +} // namespace +} // namespace chromecast diff --git a/chromecast/browser/webui/constants.cc b/chromecast/browser/webui/constants.cc index a180a670f813e..cdaed07f229ff 100644 --- a/chromecast/browser/webui/constants.cc +++ b/chromecast/browser/webui/constants.cc @@ -10,4 +10,8 @@ const char kCastWebUIForceUpdateHost[] = "force_update"; const char kCastWebUIHomeHost[] = "home"; const char kCastWebUIResourceHost[] = "cast_resources"; +bool IsKnownCastWebUiHost(std::string_view host) { + return host == kCastWebUIForceUpdateHost || host == kCastWebUIHomeHost; +} + } // namespace chromecast diff --git a/chromecast/browser/webui/constants.h b/chromecast/browser/webui/constants.h index 06bfb95aebf03..567295a3f4121 100644 --- a/chromecast/browser/webui/constants.h +++ b/chromecast/browser/webui/constants.h @@ -5,6 +5,8 @@ #ifndef CHROMECAST_BROWSER_WEBUI_CONSTANTS_H_ #define CHROMECAST_BROWSER_WEBUI_CONSTANTS_H_ +#include + namespace chromecast { // Web UI hostnames for Cast. @@ -12,6 +14,9 @@ extern const char kCastWebUIForceUpdateHost[]; extern const char kCastWebUIHomeHost[]; extern const char kCastWebUIResourceHost[]; +// Returns whether `host` is one of the known Cast Web UI hostnames above. +bool IsKnownCastWebUiHost(std::string_view host); + } // namespace chromecast #endif // CHROMECAST_BROWSER_WEBUI_CONSTANTS_H_