diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index c6d47400501..23c3070abd3 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -694,6 +694,9 @@ By installing this extension, you are agreeing to the Google Widevine Terms of U Allow Google login for extensions + + Method to resolve Unstoppable Domains + Method to resolve IPFS resources diff --git a/browser/BUILD.gn b/browser/BUILD.gn index 688213df4c4..edc79926ebe 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -16,6 +16,7 @@ import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/sidebar/buildflags/buildflags.gni") import("//brave/components/speedreader/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") import("//build/buildflag_header.gni") import("//build/config/features.gni") import("//chrome/common/features.gni") @@ -154,6 +155,7 @@ source_set("browser_process") { "//brave/components/sidebar/buildflags", "//brave/components/speedreader:buildflags", "//brave/components/tor/buildflags", + "//brave/components/unstoppable_domains/buildflags", "//brave/components/weekly_storage", "//brave/services/network/public/cpp", "//brave/ui/brave_ads", @@ -289,6 +291,10 @@ source_set("browser_process") { ] } + if (unstoppable_domains_enabled) { + deps += [ "//brave/browser/unstoppable_domains" ] + } + if (ipfs_enabled) { sources += [ "ipfs/content_browser_client_helper.cc", diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index 123c3d79ad6..8fec950fb4f 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -35,6 +35,7 @@ #include "brave/components/ipfs/buildflags/buildflags.h" #include "brave/components/speedreader/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "brave/grit/brave_generated_resources.h" #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" @@ -88,6 +89,10 @@ using extensions::ChromeContentBrowserClientExtensionsPart; #include "brave/components/ipfs/ipfs_navigation_throttle.h" #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/components/unstoppable_domains/unstoppable_domains_navigation_throttle.h" +#endif + #if BUILDFLAG(BRAVE_REWARDS_ENABLED) #include "brave/components/brave_rewards/browser/rewards_protocol_handler.h" #endif @@ -585,7 +590,8 @@ BraveContentBrowserClient::CreateThrottlesForNavigation( std::make_unique(handle)); #endif -#if BUILDFLAG(ENABLE_TOR) ||BUILDFLAG(IPFS_ENABLED) +#if BUILDFLAG(ENABLE_TOR) || BUILDFLAG(IPFS_ENABLED) || \ + BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) content::BrowserContext* context = handle->GetWebContents()->GetBrowserContext(); #endif @@ -618,5 +624,15 @@ BraveContentBrowserClient::CreateThrottlesForNavigation( throttles.push_back(std::move(ipfs_navigation_throttle)); #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + std::unique_ptr + unstoppable_domains_navigation_throttle = unstoppable_domains:: + UnstoppableDomainsNavigationThrottle::MaybeCreateThrottleFor( + handle, g_brave_browser_process->local_state(), + g_brave_browser_process->GetApplicationLocale()); + if (unstoppable_domains_navigation_throttle) + throttles.push_back(std::move(unstoppable_domains_navigation_throttle)); +#endif + return throttles; } diff --git a/browser/brave_local_state_prefs.cc b/browser/brave_local_state_prefs.cc index 42b92a71c26..667ee1120ca 100644 --- a/browser/brave_local_state_prefs.cc +++ b/browser/brave_local_state_prefs.cc @@ -18,6 +18,7 @@ #include "brave/components/p3a/brave_p3a_service.h" #include "brave/components/p3a/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "chrome/common/pref_names.h" #include "components/metrics/metrics_pref_names.h" #include "components/prefs/pref_registry_simple.h" @@ -42,6 +43,10 @@ #include "brave/browser/widevine/widevine_utils.h" #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/components/unstoppable_domains/unstoppable_domains_service.h" +#endif + namespace brave { void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) { @@ -95,6 +100,11 @@ void RegisterLocalStatePrefs(PrefRegistrySimple* registry) { RegisterWidevineLocalstatePrefs(registry); #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + unstoppable_domains::UnstoppableDomainsService::RegisterLocalStatePrefs( + registry); +#endif + RegisterLocalStatePrefsForMigration(registry); } diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index 0a95f5484c9..fe2f08ba484 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -10,6 +10,7 @@ import("//brave/components/gemini/browser/buildflags/buildflags.gni") import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/sidebar/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") import("//build/config/features.gni") import("//components/gcm_driver/config.gni") @@ -67,6 +68,7 @@ source_set("extensions") { "//brave/components/ipfs/buildflags", "//brave/components/sidebar/buildflags", "//brave/components/tor/buildflags", + "//brave/components/unstoppable_domains/buildflags", "//chrome/browser/extensions", "//chrome/common", "//components/gcm_driver:gcm_buildflags", @@ -111,6 +113,10 @@ source_set("extensions") { deps += [ "//brave/components/ipfs" ] } + if (unstoppable_domains_enabled) { + deps += [ "//brave/components/unstoppable_domains" ] + } + if (brave_rewards_enabled) { sources += [ "api/brave_rewards_api.cc", diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index a626ca1a3f6..682c4c2767b 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -14,6 +14,7 @@ #include "brave/components/ntp_background_images/common/pref_names.h" #include "brave/components/sidebar/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "chrome/browser/extensions/api/settings_private/prefs_util.h" #include "chrome/common/extensions/api/settings_private.h" #include "chrome/common/pref_names.h" @@ -45,6 +46,10 @@ #include "brave/components/tor/pref_names.h" #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/components/unstoppable_domains/pref_names.h" +#endif + namespace extensions { using ntp_background_images::prefs::kNewTabPageShowBackgroundImage; @@ -207,6 +212,12 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { (*s_brave_allowlist)[prefs::kWebRTCIPHandlingPolicy] = settings_api::PrefType::PREF_TYPE_STRING; +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + // Unstoppable Domains pref + (*s_brave_allowlist)[unstoppable_domains::kResolveMethod] = + settings_api::PrefType::PREF_TYPE_NUMBER; +#endif + return *s_brave_allowlist; } diff --git a/browser/profiles/BUILD.gn b/browser/profiles/BUILD.gn index b714402d246..43138a45ec7 100644 --- a/browser/profiles/BUILD.gn +++ b/browser/profiles/BUILD.gn @@ -33,11 +33,11 @@ source_set("profiles") { "//brave/components/brave_rewards/browser", "//brave/components/brave_sync:prefs", "//brave/components/brave_wallet/buildflags", - "//brave/components/brave_wallet/buildflags", "//brave/components/content_settings/core/browser", "//brave/components/ipfs/buildflags", "//brave/components/ntp_background_images/common", "//brave/components/tor", + "//brave/components/unstoppable_domains/buildflags", "//brave/content:browser", "//chrome/common", "//components/gcm_driver:gcm_buildflags", diff --git a/browser/profiles/brave_profile_manager.cc b/browser/profiles/brave_profile_manager.cc index 1c6d7a89524..64b7ddb880c 100644 --- a/browser/profiles/brave_profile_manager.cc +++ b/browser/profiles/brave_profile_manager.cc @@ -19,6 +19,7 @@ #include "brave/components/content_settings/core/browser/brave_content_settings_pref_provider.h" #include "brave/components/ipfs/buildflags/buildflags.h" #include "brave/components/tor/tor_constants.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "brave/content/browser/webui/brave_shared_resources_data_source.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" @@ -50,6 +51,10 @@ #include "brave/browser/ipfs/ipfs_service_factory.h" #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/browser/unstoppable_domains/unstoppable_domains_service_factory.h" +#endif + using content::BrowserThread; BraveProfileManager::BraveProfileManager(const base::FilePath& user_data_dir) @@ -108,6 +113,9 @@ void BraveProfileManager::DoFinalInitForServices(Profile* profile, #if BUILDFLAG(IPFS_ENABLED) ipfs::IpfsServiceFactory::GetForContext(profile); #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + unstoppable_domains::UnstoppableDomainsServiceFactory::GetForContext(profile); +#endif #if !BUILDFLAG(USE_GCM_FROM_PLATFORM) gcm::BraveGCMChannelStatus* status = gcm::BraveGCMChannelStatus::GetForProfile(profile); diff --git a/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_browser_proxy.js b/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_browser_proxy.js index c897a37c092..279ff0189f6 100644 --- a/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_browser_proxy.js +++ b/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_browser_proxy.js @@ -67,6 +67,10 @@ cr.define('settings', function () { wasSignInEnabledAtStartup () { return loadTimeData.getBoolean('signInAllowedOnNextStartupInitialValue') } + + isUnstoppableDomainsEnabled () { + return cr.sendWithPromise('isUnstoppableDomainsEnabled') + } } cr.addSingletonGetter(BraveDefaultExtensionsBrowserProxyImpl) diff --git a/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_page.html b/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_page.html index c0d231e44e9..c9bb46c6e39 100644 --- a/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_page.html +++ b/browser/resources/settings/brave_default_extensions_page/brave_default_extensions_page.html @@ -85,6 +85,14 @@ sub-label="$i18n{hangoutsEnabledDesc}" on-settings-boolean-control-change="onHangoutsEnabledChange_"> + { this.widevineEnabled_ = enabled }) + this.browserProxy_.isUnstoppableDomainsEnabled().then(enabled => { + this.unstoppableDomainsEnabled_ = enabled + }); }, onWebTorrentEnabledChange_: function() { diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 33565080809..35bbef45f91 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -7,6 +7,7 @@ import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/sidebar/buildflags/buildflags.gni") import("//brave/components/speedreader/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") import("//build/config/features.gni") import("//chrome/common/features.gni") import("//components/gcm_driver/config.gni") @@ -239,6 +240,7 @@ source_set("ui") { "//brave/components/p3a:buildflags", "//brave/components/sidebar/buildflags", "//brave/components/tor:pref_names", + "//brave/components/unstoppable_domains/buildflags", "//brave/components/vector_icons", "//brave/components/webcompat_reporter/browser", "//brave/components/webcompat_reporter/ui:generated_resources", @@ -374,6 +376,10 @@ source_set("ui") { } } + if (unstoppable_domains_enabled) { + deps += [ "//brave/components/unstoppable_domains" ] + } + if (brave_rewards_enabled) { sources += [ "views/brave_actions/brave_rewards_action_stub_view.cc", diff --git a/browser/ui/webui/settings/brave_default_extensions_handler.cc b/browser/ui/webui/settings/brave_default_extensions_handler.cc index c13845f3935..2719c668686 100644 --- a/browser/ui/webui/settings/brave_default_extensions_handler.cc +++ b/browser/ui/webui/settings/brave_default_extensions_handler.cc @@ -13,6 +13,7 @@ #include "brave/browser/extensions/brave_component_loader.h" #include "brave/common/pref_names.h" #include "brave/components/brave_webtorrent/grit/brave_webtorrent_resources.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "chrome/browser/about_flags.h" #include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_service.h" @@ -47,6 +48,10 @@ #include "brave/browser/widevine/widevine_utils.h" #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/components/unstoppable_domains/utils.h" +#endif + BraveDefaultExtensionsHandler::BraveDefaultExtensionsHandler() : weak_ptr_factory_(this) { #if BUILDFLAG(ENABLE_WIDEVINE) @@ -108,6 +113,11 @@ void BraveDefaultExtensionsHandler::RegisterMessages() { "isWidevineEnabled", base::BindRepeating(&BraveDefaultExtensionsHandler::IsWidevineEnabled, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "isUnstoppableDomainsEnabled", + base::BindRepeating( + &BraveDefaultExtensionsHandler::IsUnstoppableDomainsEnabled, + base::Unretained(this))); // Can't call this in ctor because it needs to access web_ui(). InitializePrefCallbacks(); @@ -394,3 +404,16 @@ void BraveDefaultExtensionsHandler::SetBraveWalletEnabled( } } #endif + +void BraveDefaultExtensionsHandler::IsUnstoppableDomainsEnabled( + const base::ListValue* args) { + CHECK_EQ(args->GetSize(), 1U); + AllowJavascript(); + ResolveJavascriptCallback( + args->GetList()[0], +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + base::Value(unstoppable_domains::IsUnstoppableDomainsEnabled())); +#else + base::Value(false)); +#endif +} diff --git a/browser/ui/webui/settings/brave_default_extensions_handler.h b/browser/ui/webui/settings/brave_default_extensions_handler.h index e2507d66202..9118497ea51 100644 --- a/browser/ui/webui/settings/brave_default_extensions_handler.h +++ b/browser/ui/webui/settings/brave_default_extensions_handler.h @@ -44,6 +44,7 @@ class BraveDefaultExtensionsHandler : public settings::SettingsPageUIHandler { void SetWidevineEnabled(const base::ListValue* args); void IsWidevineEnabled(const base::ListValue* args); void OnWidevineEnabledChanged(); + void IsUnstoppableDomainsEnabled(const base::ListValue* args); void InitializePrefCallbacks(); diff --git a/browser/unstoppable_domains/BUILD.gn b/browser/unstoppable_domains/BUILD.gn new file mode 100644 index 00000000000..b9f021060bd --- /dev/null +++ b/browser/unstoppable_domains/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright (c) 2021 The Brave Authors. All rights reserved. +# 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/. */ + +source_set("unstoppable_domains") { + # See https://github.com/brave/brave-browser/issues/14441 + check_includes = false + + sources = [ + "unstoppable_domains_service_delegate_impl.cc", + "unstoppable_domains_service_delegate_impl.h", + "unstoppable_domains_service_factory.cc", + "unstoppable_domains_service_factory.h", + ] + + deps = [ + "//brave/components/unstoppable_domains", + "//components/keyed_service/content", + ] + + # Below deps are not directly used by unstoppable_domains target. + # This is added due to our include of + # `chrome/browser/net/system_network_context_manager.h` without adding + # //chrome/browser into deps to avoid circulate dependency. Without this, + # we could encounter error due to some files included are not generated yet. + deps += [ "//services/network/public/mojom" ] +} diff --git a/browser/unstoppable_domains/test/BUILD.gn b/browser/unstoppable_domains/test/BUILD.gn new file mode 100644 index 00000000000..ff577f9c609 --- /dev/null +++ b/browser/unstoppable_domains/test/BUILD.gn @@ -0,0 +1,47 @@ +# Copyright (c) 2021 The Brave Authors. All rights reserved. +# 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/. */ + +import("//brave/build/config.gni") +import("//testing/test.gni") + +source_set("browser_tests") { + testonly = true + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + sources = [ + "//brave/browser/unstoppable_domains/test/unstoppable_domains_navigation_throttle_browsertest.cc", + "//brave/browser/unstoppable_domains/test/unstoppable_domains_service_browsertest.cc", + ] + deps = [ + "//base/test:test_support", + "//brave/components/unstoppable_domains", + "//chrome/browser:browser_process", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//components/prefs", + "//components/security_interstitials/content:security_interstitial_page", + "//content/test:test_support", + "//testing/gtest", + ] +} # source_set("browser_tests") { + +source_set("unit_tests") { + testonly = true + sources = [ + "//brave/browser/unstoppable_domains/test/utils_unittest.cc", + "//brave/net/dns/dns_transaction_unittest.cc", + ] + + deps = [ + "//base", + "//base/test:test_support", + "//brave/components/unstoppable_domains", + "//chrome/test:test_support", + "//components/prefs", + "//net", + "//net:test_support", + "//testing/gmock", + "//testing/gtest", + ] +} # source_set("unit_tests") diff --git a/browser/unstoppable_domains/test/unstoppable_domains_navigation_throttle_browsertest.cc b/browser/unstoppable_domains/test/unstoppable_domains_navigation_throttle_browsertest.cc new file mode 100644 index 00000000000..e08a415aebe --- /dev/null +++ b/browser/unstoppable_domains/test/unstoppable_domains_navigation_throttle_browsertest.cc @@ -0,0 +1,134 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "base/test/scoped_feature_list.h" +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/features.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_opt_in_page.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/prefs/pref_service.h" +#include "components/security_interstitials/content/security_interstitial_page.h" +#include "components/security_interstitials/content/security_interstitial_tab_helper.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace { + +security_interstitials::SecurityInterstitialPage* GetCurrentInterstitial( + content::WebContents* web_contents) { + security_interstitials::SecurityInterstitialTabHelper* helper = + security_interstitials::SecurityInterstitialTabHelper::FromWebContents( + web_contents); + if (!helper) { + return nullptr; + } + return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting(); +} + +security_interstitials::SecurityInterstitialPage::TypeID GetInterstitialType( + content::WebContents* web_contents) { + security_interstitials::SecurityInterstitialPage* page = + GetCurrentInterstitial(web_contents); + if (!page) { + return nullptr; + } + return page->GetTypeForTesting(); +} + +void SendInterstitialCommand( + content::WebContents* web_contents, + security_interstitials::SecurityInterstitialCommand command) { + GetCurrentInterstitial(web_contents) + ->CommandReceived(base::NumberToString(command)); +} + +void SendInterstitialCommandSync( + Browser* browser, + security_interstitials::SecurityInterstitialCommand command) { + content::WebContents* web_contents = + browser->tab_strip_model()->GetActiveWebContents(); + + EXPECT_EQ(unstoppable_domains::UnstoppableDomainsOptInPage::kTypeForTesting, + GetInterstitialType(web_contents)); + + content::TestNavigationObserver navigation_observer(web_contents, 1); + SendInterstitialCommand(web_contents, command); + navigation_observer.Wait(); + + EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents)); +} + +} // namespace + +namespace unstoppable_domains { + +class UnstoppableDomainsNavigationThrottleBrowserTest + : public InProcessBrowserTest { + public: + UnstoppableDomainsNavigationThrottleBrowserTest() { + feature_list_.InitAndEnableFeature(features::kUnstoppableDomains); + } + + ~UnstoppableDomainsNavigationThrottleBrowserTest() override = default; + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + } + + PrefService* local_state() { return g_browser_process->local_state(); } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(UnstoppableDomainsNavigationThrottleBrowserTest, + ShowInterstitialAndProceed) { + ui_test_utils::NavigateToURL(browser(), GURL("http://test.crypto")); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + EXPECT_TRUE(WaitForRenderFrameReady(web_contents->GetMainFrame())); + EXPECT_EQ(UnstoppableDomainsOptInPage::kTypeForTesting, + GetInterstitialType(web_contents)); + + EXPECT_EQ(static_cast(ResolveMethodTypes::ASK), + local_state()->GetInteger(kResolveMethod)); + SendInterstitialCommandSync( + browser(), + security_interstitials::SecurityInterstitialCommand::CMD_PROCEED); + EXPECT_EQ(static_cast(ResolveMethodTypes::DNS_OVER_HTTPS), + local_state()->GetInteger(kResolveMethod)); +} + +IN_PROC_BROWSER_TEST_F(UnstoppableDomainsNavigationThrottleBrowserTest, + ShowInterstitialAndReject) { + ui_test_utils::NavigateToURL(browser(), GURL("http://test.crypto")); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + EXPECT_TRUE(WaitForRenderFrameReady(web_contents->GetMainFrame())); + EXPECT_EQ(UnstoppableDomainsOptInPage::kTypeForTesting, + GetInterstitialType(web_contents)); + + EXPECT_EQ(static_cast(ResolveMethodTypes::ASK), + local_state()->GetInteger(kResolveMethod)); + SendInterstitialCommandSync( + browser(), + security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED); + EXPECT_EQ(static_cast(ResolveMethodTypes::DISABLED), + local_state()->GetInteger(kResolveMethod)); +} + +} // namespace unstoppable_domains diff --git a/browser/unstoppable_domains/test/unstoppable_domains_service_browsertest.cc b/browser/unstoppable_domains/test/unstoppable_domains_service_browsertest.cc new file mode 100644 index 00000000000..17fc6da016d --- /dev/null +++ b/browser/unstoppable_domains/test/unstoppable_domains_service_browsertest.cc @@ -0,0 +1,128 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "base/test/scoped_feature_list.h" +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/features.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "brave/net/unstoppable_domains/constants.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/secure_dns_config.h" +#include "chrome/browser/net/stub_resolver_config_reader.h" +#include "chrome/browser/net/system_network_context_manager.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/prefs/pref_service.h" +#include "content/public/test/browser_test.h" +#include "net/dns/public/secure_dns_mode.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace unstoppable_domains { + +class UnstoppableDomainsServiceBrowserTest : public InProcessBrowserTest { + public: + UnstoppableDomainsServiceBrowserTest() { + feature_list_.InitAndEnableFeature(features::kUnstoppableDomains); + } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + stub_config_reader_ = + SystemNetworkContextManager::GetStubResolverConfigReader(); + ASSERT_TRUE(stub_config_reader_); + } + + ~UnstoppableDomainsServiceBrowserTest() override = default; + + PrefService* local_state() { return g_browser_process->local_state(); } + + SecureDnsConfig GetSecureDnsConfiguration( + bool force_check_parental_controls_for_automatic_mode = false) { + return stub_config_reader_->GetSecureDnsConfiguration( + force_check_parental_controls_for_automatic_mode); + } + + private: + base::test::ScopedFeatureList feature_list_; + StubResolverConfigReader* stub_config_reader_; +}; + +IN_PROC_BROWSER_TEST_F(UnstoppableDomainsServiceBrowserTest, + UpdateConfigWhenPrefChanged) { + // Initial state. + EXPECT_EQ(local_state()->GetInteger(kResolveMethod), + static_cast(ResolveMethodTypes::ASK)); + SecureDnsConfig config = GetSecureDnsConfiguration(); + EXPECT_EQ(config.mode(), net::SecureDnsMode::kAutomatic); + EXPECT_EQ(config.servers().size(), 0u); + + // Set resolve method to DoH should update the config. + local_state()->SetInteger( + kResolveMethod, static_cast(ResolveMethodTypes::DNS_OVER_HTTPS)); + config = GetSecureDnsConfiguration(); + std::vector expected_doh_servers = { + {kDoHResolver, true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); + + // Set custom DoH provider should still keep the resolver for UD. + local_state()->SetString(prefs::kDnsOverHttpsTemplates, "https://test.com"); + config = GetSecureDnsConfiguration(); + expected_doh_servers = {{kDoHResolver, true}, {"https://test.com", true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); + + // Set secure mode to off should return empty DoH servers. + local_state()->SetString( + prefs::kDnsOverHttpsMode, + SecureDnsConfig::ModeToString(net::SecureDnsMode::kOff)); + config = GetSecureDnsConfiguration(); + EXPECT_EQ(config.servers().size(), 0u); + + // Turn on secure mode again should get the same result as before. + local_state()->SetString( + prefs::kDnsOverHttpsMode, + SecureDnsConfig::ModeToString(net::SecureDnsMode::kSecure)); + config = GetSecureDnsConfiguration(); + EXPECT_EQ(config.servers(), expected_doh_servers); + + // Set resolve method to disabled should keep user's DoH setting. + local_state()->SetInteger(kResolveMethod, + static_cast(ResolveMethodTypes::DISABLED)); + config = GetSecureDnsConfiguration(); + expected_doh_servers = {{"https://test.com", true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); +} + +IN_PROC_BROWSER_TEST_F(UnstoppableDomainsServiceBrowserTest, + HideUnstoppableDomainsResolvers) { + // Initial state. + EXPECT_EQ(local_state()->GetInteger(kResolveMethod), + static_cast(ResolveMethodTypes::ASK)); + SecureDnsConfig config = GetSecureDnsConfiguration(); + EXPECT_EQ(config.mode(), net::SecureDnsMode::kAutomatic); + EXPECT_EQ(config.servers().size(), 0u); + + // Set resolve method to DoH should update the config. + local_state()->SetInteger( + kResolveMethod, static_cast(ResolveMethodTypes::DNS_OVER_HTTPS)); + config = GetSecureDnsConfiguration(); + std::vector expected_doh_servers = { + {kDoHResolver, true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); + + // Set custom DoH provider should still keep the resolver for UD. + local_state()->SetString(prefs::kDnsOverHttpsTemplates, "https://test.com"); + config = GetSecureDnsConfiguration(); + expected_doh_servers = {{kDoHResolver, true}, {"https://test.com", true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); + + // Should hide unstoppable domains resolver if + // force_check_parental_controls_for_automatic_mode is true, used for hiding + // the special resolver in settings. + config = GetSecureDnsConfiguration(true); + expected_doh_servers = {{"https://test.com", true}}; + EXPECT_EQ(config.servers(), expected_doh_servers); +} + +} // namespace unstoppable_domains diff --git a/browser/unstoppable_domains/test/utils_unittest.cc b/browser/unstoppable_domains/test/utils_unittest.cc new file mode 100644 index 00000000000..c8655df3205 --- /dev/null +++ b/browser/unstoppable_domains/test/utils_unittest.cc @@ -0,0 +1,73 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/utils.h" + +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/features.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "chrome/test/base/scoped_testing_local_state.h" +#include "chrome/test/base/testing_browser_process.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace unstoppable_domains { + +class UtilsUnitTest : public testing::TestWithParam { + public: + UtilsUnitTest() + : local_state_(TestingBrowserProcess::GetGlobal()), + feature_enabled_(GetParam()) {} + ~UtilsUnitTest() override = default; + + void SetUp() override { + if (feature_enabled_) { + feature_list_.InitAndEnableFeature(features::kUnstoppableDomains); + } else { + feature_list_.InitAndDisableFeature(features::kUnstoppableDomains); + } + } + + PrefService* local_state() { return local_state_.Get(); } + bool feature_enabled() { return feature_enabled_; } + + private: + base::test::TaskEnvironment task_environment_; + base::test::ScopedFeatureList feature_list_; + ScopedTestingLocalState local_state_; + bool feature_enabled_; +}; + +TEST_P(UtilsUnitTest, IsUnstoppableDomainsTLD) { + EXPECT_TRUE(IsUnstoppableDomainsTLD(GURL("http://test.crypto"))); + EXPECT_FALSE(IsUnstoppableDomainsTLD(GURL("http://test.com"))); + EXPECT_FALSE(IsUnstoppableDomainsTLD(GURL("http://crypto"))); +} + +TEST_P(UtilsUnitTest, IsUnstoppableDomainsEnabled) { + EXPECT_EQ(feature_enabled(), IsUnstoppableDomainsEnabled()); +} + +TEST_P(UtilsUnitTest, IsResolveMethodAsk) { + EXPECT_EQ(feature_enabled(), IsResolveMethodAsk(local_state())); + + local_state()->SetInteger( + kResolveMethod, static_cast(ResolveMethodTypes::DNS_OVER_HTTPS)); + EXPECT_FALSE(IsResolveMethodAsk(local_state())); +} + +TEST_P(UtilsUnitTest, IsResolveMethodDoH) { + EXPECT_FALSE(IsResolveMethodDoH(local_state())); + + local_state()->SetInteger( + kResolveMethod, static_cast(ResolveMethodTypes::DNS_OVER_HTTPS)); + EXPECT_EQ(feature_enabled(), IsResolveMethodDoH(local_state())); +} + +INSTANTIATE_TEST_SUITE_P(/* no prefix */, UtilsUnitTest, testing::Bool()); + +} // namespace unstoppable_domains diff --git a/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.cc b/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.cc new file mode 100644 index 00000000000..48081e70157 --- /dev/null +++ b/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.cc @@ -0,0 +1,19 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.h" + +#include "chrome/browser/net/stub_resolver_config_reader.h" +#include "chrome/browser/net/system_network_context_manager.h" + +namespace unstoppable_domains { + +void UnstoppableDomainsServiceDelegateImpl::UpdateNetworkService() { + // Trigger a DoH config update in network service. + SystemNetworkContextManager::GetStubResolverConfigReader() + ->UpdateNetworkService(false /* record_metrics */); +} + +} // namespace unstoppable_domains diff --git a/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.h b/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.h new file mode 100644 index 00000000000..aed7638b453 --- /dev/null +++ b/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_IMPL_H_ +#define BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_IMPL_H_ + +#include "brave/components/unstoppable_domains/unstoppable_domains_service_delegate.h" + +namespace unstoppable_domains { + +class UnstoppableDomainsServiceDelegateImpl + : public UnstoppableDomainsServiceDelegate { + public: + UnstoppableDomainsServiceDelegateImpl() = default; + ~UnstoppableDomainsServiceDelegateImpl() override = default; + + UnstoppableDomainsServiceDelegateImpl( + const UnstoppableDomainsServiceDelegateImpl&) = delete; + UnstoppableDomainsServiceDelegateImpl& operator=( + UnstoppableDomainsServiceDelegateImpl&) = delete; + + void UpdateNetworkService() override; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_IMPL_H_ diff --git a/browser/unstoppable_domains/unstoppable_domains_service_factory.cc b/browser/unstoppable_domains/unstoppable_domains_service_factory.cc new file mode 100644 index 00000000000..efe353478c9 --- /dev/null +++ b/browser/unstoppable_domains/unstoppable_domains_service_factory.cc @@ -0,0 +1,49 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/browser/unstoppable_domains/unstoppable_domains_service_factory.h" + +#include + +#include "brave/browser/brave_browser_process_impl.h" +#include "brave/browser/unstoppable_domains/unstoppable_domains_service_delegate_impl.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_service.h" +#include "brave/components/unstoppable_domains/utils.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" + +namespace unstoppable_domains { + +UnstoppableDomainsServiceFactory::UnstoppableDomainsServiceFactory() + : BrowserContextKeyedServiceFactory( + "UnstoppableDomainsService", + BrowserContextDependencyManager::GetInstance()) {} + +UnstoppableDomainsServiceFactory::~UnstoppableDomainsServiceFactory() {} + +// static +UnstoppableDomainsServiceFactory* +UnstoppableDomainsServiceFactory::GetInstance() { + return base::Singleton::get(); +} + +// static +UnstoppableDomainsService* UnstoppableDomainsServiceFactory::GetForContext( + content::BrowserContext* context) { + if (!IsUnstoppableDomainsEnabled()) + return nullptr; + + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +KeyedService* UnstoppableDomainsServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new UnstoppableDomainsService( + std::make_unique(), context, + g_brave_browser_process ? g_brave_browser_process->local_state() + : nullptr); +} + +} // namespace unstoppable_domains diff --git a/browser/unstoppable_domains/unstoppable_domains_service_factory.h b/browser/unstoppable_domains/unstoppable_domains_service_factory.h new file mode 100644 index 00000000000..d64665f916e --- /dev/null +++ b/browser/unstoppable_domains/unstoppable_domains_service_factory.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_FACTORY_H_ +#define BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace unstoppable_domains { + +class UnstoppableDomainsService; + +class UnstoppableDomainsServiceFactory + : public BrowserContextKeyedServiceFactory { + public: + static UnstoppableDomainsService* GetForContext( + content::BrowserContext* context); + static UnstoppableDomainsServiceFactory* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits; + + UnstoppableDomainsServiceFactory(); + ~UnstoppableDomainsServiceFactory() override; + + UnstoppableDomainsServiceFactory(const UnstoppableDomainsServiceFactory&) = + delete; + UnstoppableDomainsServiceFactory& operator=( + const UnstoppableDomainsServiceFactory&) = delete; + + // BrowserContextKeyedServiceFactory overrides: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_BROWSER_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_FACTORY_H_ diff --git a/build/config/brave_build.gni b/build/config/brave_build.gni index c3afdcc4bbc..b2814a0e27a 100644 --- a/build/config/brave_build.gni +++ b/build/config/brave_build.gni @@ -3,6 +3,8 @@ # import("//build/config/chrome_build.gni") in compiler.gni import("//brave/brave_repack_locales.gni") import("//brave/browser/extensions/resources.gni") +import("//brave/browser/resources/extensions/sources.gni") +import("//brave/browser/resources/settings/sources.gni") import("//brave/browser/sources.gni") import("//brave/build/config/compiler.gni") import("//brave/build/features.gni") @@ -13,8 +15,7 @@ import("//brave/components/sync/driver/sources.gni") import("//brave/components/sync/sources.gni") import("//brave/components/sync_device_info/sources.gni") import("//brave/installer/linux/sources.gni") +import("//brave/net/dns/sources.gni") import("//brave/net/sources.gni") import("//brave/renderer/sources.gni") import("//brave/third_party/blink/renderer/includes.gni") -import("//brave/browser/resources/settings/sources.gni") -import("//brave/browser/resources/extensions/sources.gni") diff --git a/chromium_src/chrome/browser/about_flags.cc b/chromium_src/chrome/browser/about_flags.cc index 6cece20930c..2ff3dcc098a 100644 --- a/chromium_src/chrome/browser/about_flags.cc +++ b/chromium_src/chrome/browser/about_flags.cc @@ -16,6 +16,7 @@ #include "brave/components/ntp_background_images/browser/features.h" #include "brave/components/sidebar/buildflags/buildflags.h" #include "brave/components/speedreader/buildflags.h" +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "components/prefs/pref_service.h" @@ -92,6 +93,18 @@ using ntp_background_images::features::kBraveNTPSuperReferralWallpaper; #define BRAVE_NATIVE_WALLET_FEATURE_ENTRIES #endif +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "brave/components/unstoppable_domains/features.h" + +#define BRAVE_UNSTOPPABLE_DOMAINS_FEATURE_ENTRIES \ + {"brave-unstoppable-domains", \ + flag_descriptions::kBraveUnstoppableDomainsName, \ + flag_descriptions::kBraveUnstoppableDomainsDescription, kOsDesktop, \ + FEATURE_VALUE_TYPE(unstoppable_domains::features::kUnstoppableDomains)}, +#else +#define BRAVE_UNSTOPPABLE_DOMAINS_FEATURE_ENTRIES +#endif + #define BRAVE_FEATURE_ENTRIES \ {"use-dev-updater-url", \ flag_descriptions::kUseDevUpdaterUrlName, \ @@ -114,6 +127,7 @@ using ntp_background_images::features::kBraveNTPSuperReferralWallpaper; BRAVE_IPFS_FEATURE_ENTRIES \ BRAVE_NATIVE_WALLET_FEATURE_ENTRIES \ SIDEBAR_FEATURE_ENTRIES \ + BRAVE_UNSTOPPABLE_DOMAINS_FEATURE_ENTRIES \ {"brave-super-referral", \ flag_descriptions::kBraveSuperReferralName, \ flag_descriptions::kBraveSuperReferralDescription, \ diff --git a/chromium_src/chrome/browser/flag_descriptions.cc b/chromium_src/chrome/browser/flag_descriptions.cc index c66a13653e4..47ad4056e5c 100644 --- a/chromium_src/chrome/browser/flag_descriptions.cc +++ b/chromium_src/chrome/browser/flag_descriptions.cc @@ -41,6 +41,9 @@ const char kNativeBraveWalletName[] = "Enable experimental Brave native wallet"; const char kNativeBraveWalletDescription[] = "Experimental native cryptocurrency wallet support without the use of " "extensions"; +const char kBraveUnstoppableDomainsName[] = "Enable unstoppable domains"; +const char kBraveUnstoppableDomainsDescription[] = + "Enable resolution support of unstoppable domains."; const char kBraveSuperReferralName[] = "Enable Brave Super Referral"; const char kBraveSuperReferralDescription[] = "Use custom theme for Brave Super Referral"; diff --git a/chromium_src/chrome/browser/flag_descriptions.h b/chromium_src/chrome/browser/flag_descriptions.h index d1d99499866..fcd12c94b45 100644 --- a/chromium_src/chrome/browser/flag_descriptions.h +++ b/chromium_src/chrome/browser/flag_descriptions.h @@ -27,6 +27,8 @@ extern const char kBraveIpfsName[]; extern const char kBraveIpfsDescription[]; extern const char kNativeBraveWalletName[]; extern const char kNativeBraveWalletDescription[]; +extern const char kBraveUnstoppableDomainsName[]; +extern const char kBraveUnstoppableDomainsDescription[]; extern const char kBraveSuperReferralName[]; extern const char kBraveSuperReferralDescription[]; extern const char kBraveEphemeralStorageName[]; diff --git a/chromium_src/chrome/browser/net/stub_resolver_config_reader.cc b/chromium_src/chrome/browser/net/stub_resolver_config_reader.cc new file mode 100644 index 00000000000..120dd06e6ea --- /dev/null +++ b/chromium_src/chrome/browser/net/stub_resolver_config_reader.cc @@ -0,0 +1,55 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/buildflags/buildflags.h" +#include "components/prefs/pref_service.h" + +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +#include "base/strings/strcat.h" +#include "brave/components/unstoppable_domains/utils.h" +#include "brave/net/unstoppable_domains/constants.h" +#endif + +namespace { + +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) +void AddUnstoppableDomainsResolver(std::string* doh_templates, + PrefService* local_state) { + if (unstoppable_domains::IsResolveMethodDoH(local_state) && + doh_templates->find(unstoppable_domains::kDoHResolver) == + std::string::npos) { + *doh_templates = + base::StrCat({unstoppable_domains::kDoHResolver, " ", *doh_templates}); + } +} +#endif // BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + +// Add DoH servers to support Unstoppable Domains (and ENS in the future PR). +// These servers are controlled using its own prefs and not +// kDnsOverHttpsTemplates pref as the servers we added here are special and +// only applies to certain TLD which is different from user's global DoH +// provider settings. +void AddDoHServers(std::string* doh_templates, + PrefService* local_state, + bool force_check_parental_controls_for_automatic_mode) { + // force_check_parental_controls_for_automatic_mode is only true for + // settings UI which we specifically do not want to display those special + // resolvers we added. + if (force_check_parental_controls_for_automatic_mode) + return; + +#if BUILDFLAG(UNSTOPPABLE_DOMAINS_ENABLED) + AddUnstoppableDomainsResolver(doh_templates, local_state); +#endif +} + +} // namespace + +#define BRAVE_GET_AND_UPDATE_CONFIGURATION \ + AddDoHServers(&doh_templates, local_state_, \ + force_check_parental_controls_for_automatic_mode); + +#include "../../../../../chrome/browser/net/stub_resolver_config_reader.cc" +#undef BRAVE_GET_AND_UPDATE_CONFIGURATION diff --git a/chromium_src/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chromium_src/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc index 4d38033d27d..4a0281ce541 100644 --- a/chromium_src/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chromium_src/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc @@ -219,6 +219,8 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source, IDS_SETTINGS_LOAD_CRYPTO_WALLETS_ON_STARTUP}, {"googleLoginForExtensionsDesc", IDS_SETTINGS_GOOGLE_LOGIN_FOR_EXTENSIONS}, {"hangoutsEnabledDesc", IDS_SETTINGS_HANGOUTS_ENABLED_DESC}, + {"resolveUnstoppableDomainsDesc", + IDS_SETTINGS_RESOLVE_UNSTOPPABLE_DOMAINS_DESC}, {"resolveIPFSURLDesc", IDS_SETTINGS_RESOLVE_IPFS_URLS_DESC}, {"ipfsPublicGatewayDesc", IDS_SETTINGS_IPFS_PUBLIC_GATEWAY_DESC}, {"ipfsChangeGatewayButtonLabel", diff --git a/chromium_src/net/DEPS b/chromium_src/net/DEPS index 518b5f55c96..ac9b5ec33aa 100644 --- a/chromium_src/net/DEPS +++ b/chromium_src/net/DEPS @@ -1,6 +1,7 @@ include_rules = [ "+../../../../net/base", "+../../../../net/cookies", + "+../../../../net/dns", "+../../../../net/log", "+../../../../net/proxy_resolution", "+../../../../net/socket", diff --git a/chromium_src/net/base/lookup_string_in_fixed_set.cc b/chromium_src/net/base/lookup_string_in_fixed_set.cc index 9327fb71d6e..9f6c3e5a9d4 100644 --- a/chromium_src/net/base/lookup_string_in_fixed_set.cc +++ b/chromium_src/net/base/lookup_string_in_fixed_set.cc @@ -10,6 +10,7 @@ #undef LookupSuffixInReversedSet #include "base/strings/string_util.h" +#include "brave/net/unstoppable_domains/constants.h" namespace net { @@ -40,6 +41,15 @@ int LookupSuffixInReversedSet(const unsigned char* graph, return kDafsaFound; } + // Recognize .crypto as a known TLD for unstoppable domains support. With + // this, when users type *.crypto in omnibox, it will be parsed as + // OmniboxInputType::URL input type instead of OmniboxInputType::UNKNOWN, + // The first entry in the autocomplete list will be URL instead of search. + if (base::EndsWith(host, unstoppable_domains::kCryptoDomain)) { + *suffix_length = strlen(unstoppable_domains::kCryptoDomain) - 1; + return kDafsaFound; + } + return LookupSuffixInReversedSet_ChromiumImpl(graph, length, include_private, host, suffix_length); } diff --git a/chromium_src/net/dns/dns_transaction.cc b/chromium_src/net/dns/dns_transaction.cc new file mode 100644 index 00000000000..2bc5e20c487 --- /dev/null +++ b/chromium_src/net/dns/dns_transaction.cc @@ -0,0 +1,41 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "base/strings/string_util.h" +#include "brave/net/unstoppable_domains/constants.h" +#include "net/dns/dns_config.h" +#include "net/dns/dns_server_iterator.h" + +namespace { + +bool GetNextIndex(const std::string& hostname, + const net::DnsConfig& config, + net::DnsServerIterator* dns_server_iterator, + size_t* doh_server_index) { + // Skip unstoppable domains resolver for non-crypto domains. + if (config.dns_over_https_servers[*doh_server_index].server_template == + unstoppable_domains::kDoHResolver && + !base::EndsWith(hostname, unstoppable_domains::kCryptoDomain)) { + // No next available index to attempt. + if (!dns_server_iterator->AttemptAvailable()) { + return false; + } + + *doh_server_index = dns_server_iterator->GetNextAttemptIndex(); + } + + return true; +} + +} // namespace + +#define BRAVE_MAKE_HTTP_ATTEMPT \ + if (!GetNextIndex(hostname_, session_.get()->config(), \ + dns_server_iterator_.get(), &doh_server_index)) { \ + return AttemptResult(ERR_BLOCKED_BY_CLIENT, nullptr); \ + } + +#include "../../../../net/dns/dns_transaction.cc" +#undef BRAVE_MAKE_HTTP_ATTEMPT diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn index 810d3c6c738..ae513bcb7e2 100644 --- a/components/resources/BUILD.gn +++ b/components/resources/BUILD.gn @@ -7,6 +7,7 @@ import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/sidebar/buildflags/buildflags.gni") import("//brave/components/speedreader/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") import("//build/config/locales.gni") import("//extensions/buildflags/buildflags.gni") import("//tools/grit/grit_rule.gni") @@ -42,6 +43,7 @@ grit("static_resources") { "enable_speedreader=$enable_speedreader", "ipfs_enabled=$ipfs_enabled", "crypto_dot_com_enabled=$crypto_dot_com_enabled", + "unstoppable_domains_enabled=$unstoppable_domains_enabled", ] grit_flags = [ @@ -95,6 +97,7 @@ grit("strings") { defines = [ "enable_speedreader=$enable_speedreader", "ipfs_enabled=$ipfs_enabled", + "unstoppable_domains_enabled=$unstoppable_domains_enabled", "crypto_dot_com_enabled=$crypto_dot_com_enabled", "enable_tor=$enable_tor", "enable_sidebar=$enable_sidebar", diff --git a/components/resources/brave_components_resources.grd b/components/resources/brave_components_resources.grd index 89f2c529fa0..2924602c0d6 100644 --- a/components/resources/brave_components_resources.grd +++ b/components/resources/brave_components_resources.grd @@ -63,6 +63,7 @@ + diff --git a/components/resources/brave_components_strings.grd b/components/resources/brave_components_strings.grd index e40b491a635..36c711a6c9f 100644 --- a/components/resources/brave_components_strings.grd +++ b/components/resources/brave_components_strings.grd @@ -1090,6 +1090,7 @@ I understand + diff --git a/components/resources/unstoppable_domains_resources.grdp b/components/resources/unstoppable_domains_resources.grdp new file mode 100644 index 00000000000..79d5a2b704f --- /dev/null +++ b/components/resources/unstoppable_domains_resources.grdp @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/components/resources/unstoppable_domains_strings.grdp b/components/resources/unstoppable_domains_strings.grdp new file mode 100644 index 00000000000..8d1a3b30d14 --- /dev/null +++ b/components/resources/unstoppable_domains_strings.grdp @@ -0,0 +1,20 @@ + + + + + Unstoppable Domains + + + Enable support of Unstoppable Domains in Brave? + + + Brave can enable you to visit .crypto domains by using Cloudflare to resolve .crypto domain name lookup requests using DNS over HTTPS. If you enable this, Cloudflare will see the .crypto domain that you're trying to visit but they will not be able to see other domains. See Cloudflare's $1terms of use$2 and $3privacy policy$4. + + + Proceed using Cloudflare server + + + Disable + + + diff --git a/components/unstoppable_domains/BUILD.gn b/components/unstoppable_domains/BUILD.gn new file mode 100644 index 00000000000..439f42687de --- /dev/null +++ b/components/unstoppable_domains/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright (c) 2021 The Brave Authors. All rights reserved. +# 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/. */ + +source_set("unstoppable_domains") { + sources = [ + "constants.h", + "features.h", + "pref_names.h", + "unstoppable_domains_interstitial_controller_client.cc", + "unstoppable_domains_interstitial_controller_client.h", + "unstoppable_domains_navigation_throttle.cc", + "unstoppable_domains_navigation_throttle.h", + "unstoppable_domains_opt_in_page.cc", + "unstoppable_domains_opt_in_page.h", + "unstoppable_domains_service.cc", + "unstoppable_domains_service.h", + "unstoppable_domains_service_delegate.h", + "utils.cc", + "utils.h", + ] + + deps = [ + "//base", + "//brave/components/resources:static_resources", + "//brave/components/resources:strings", + "//components/keyed_service/core", + "//components/prefs", + "//components/security_interstitials/content:security_interstitial_page", + "//components/security_interstitials/core", + "//components/user_prefs", + "//content/public/browser", + "//net", + "//url", + ] +} diff --git a/components/unstoppable_domains/DEPS b/components/unstoppable_domains/DEPS new file mode 100644 index 00000000000..778791c5d95 --- /dev/null +++ b/components/unstoppable_domains/DEPS @@ -0,0 +1,14 @@ +include_rules = [ + "+base", + "+components/keyed_service/core", + "+components/grit", + "+components/prefs", + "+components/security_interstitials/content", + "+components/security_interstitials/core", + "+components/user_prefs", + "+content/public/browser", + "+content/public/common", + "+net", + "+ui/base", + "+url", +] diff --git a/components/unstoppable_domains/buildflags/BUILD.gn b/components/unstoppable_domains/buildflags/BUILD.gn new file mode 100644 index 00000000000..9df0a982d31 --- /dev/null +++ b/components/unstoppable_domains/buildflags/BUILD.gn @@ -0,0 +1,7 @@ +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") +import("//build/buildflag_header.gni") + +buildflag_header("buildflags") { + header = "buildflags.h" + flags = [ "UNSTOPPABLE_DOMAINS_ENABLED=$unstoppable_domains_enabled" ] +} diff --git a/components/unstoppable_domains/buildflags/buildflags.gni b/components/unstoppable_domains/buildflags/buildflags.gni new file mode 100644 index 00000000000..d86b13dcaa5 --- /dev/null +++ b/components/unstoppable_domains/buildflags/buildflags.gni @@ -0,0 +1,3 @@ +declare_args() { + unstoppable_domains_enabled = is_mac || is_linux || is_win +} diff --git a/components/unstoppable_domains/constants.h b/components/unstoppable_domains/constants.h new file mode 100644 index 00000000000..53b19b782b4 --- /dev/null +++ b/components/unstoppable_domains/constants.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ + +namespace unstoppable_domains { + +enum class ResolveMethodTypes { + ASK, + DISABLED, + DNS_OVER_HTTPS, +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ diff --git a/components/unstoppable_domains/features.h b/components/unstoppable_domains/features.h new file mode 100644 index 00000000000..61be4b02372 --- /dev/null +++ b/components/unstoppable_domains/features.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_FEATURES_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_FEATURES_H_ + +#include "base/feature_list.h" + +namespace unstoppable_domains { +namespace features { + +constexpr base::Feature kUnstoppableDomains{"UnstoppableDomains", + base::FEATURE_DISABLED_BY_DEFAULT}; + +} // namespace features +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_FEATURES_H_ diff --git a/components/unstoppable_domains/pref_names.h b/components/unstoppable_domains/pref_names.h new file mode 100644 index 00000000000..23416806e91 --- /dev/null +++ b/components/unstoppable_domains/pref_names.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_PREF_NAMES_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_PREF_NAMES_H_ + +namespace unstoppable_domains { + +// Used to determine which method should be used to resolve unstoppable +// domains, between: +// Disabled: Disable all unstoppable domains resolution. +// Ask: Ask users if they want to enable support of unstoppable domains. +// DNS Over HTTPS: Resolve domain name using a public DNS over HTTPS server. +constexpr char kResolveMethod[] = "brave.unstoppable_domains.resolve_method"; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_PREF_NAMES_H_ diff --git a/components/unstoppable_domains/resources/unstoppable_domains_interstitial.css b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.css new file mode 100644 index 00000000000..0d0524870d7 --- /dev/null +++ b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.css @@ -0,0 +1,14 @@ +.unstoppable_domains #main-content a { + color: var(--google-blue-700); + text-decoration: none; +} + +.unstoppable_domains button { + background: var(--primary-button-fill-color); +} + +.unstoppable_domains .icon { + background-image: -webkit-image-set( + url(../../../../components/security_interstitials/core/browser/resources/images/1x/info.png) 1x, + url(../../../../components/security_interstitials/core/browser/resources/images/2x/info.png) 2x); +} diff --git a/components/unstoppable_domains/resources/unstoppable_domains_interstitial.html b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.html new file mode 100644 index 00000000000..7206962d899 --- /dev/null +++ b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.html @@ -0,0 +1,31 @@ + + + + + $i18n{tabTitle} + + + + + + + + + +
+
+
+
+

$i18nRaw{heading}

+

$i18nRaw{primaryParagraph}

+
+
+ +
+ diff --git a/components/unstoppable_domains/resources/unstoppable_domains_interstitial.js b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.js new file mode 100644 index 00000000000..08dc2bdd3db --- /dev/null +++ b/components/unstoppable_domains/resources/unstoppable_domains_interstitial.js @@ -0,0 +1,21 @@ +// Copyright 2021 The Brave Authors. All rights reserved. +// 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/. + +function setupEvents() { + $('body').classList.add('unstoppable_domains'); + $('icon').classList.add('icon'); + + $('primary-button').addEventListener('click', function() { + sendCommand(SecurityInterstitialCommandId.CMD_PROCEED); + }); + + $('main-content').classList.remove(HIDDEN_CLASS); + + $('dont-proceed-button').addEventListener('click', function(event) { + sendCommand(SecurityInterstitialCommandId.CMD_DONT_PROCEED); + }); +} + +document.addEventListener('DOMContentLoaded', setupEvents); diff --git a/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.cc b/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.cc new file mode 100644 index 00000000000..4ad97f42235 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.cc @@ -0,0 +1,59 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h" + +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "components/prefs/pref_service.h" +#include "components/security_interstitials/content/settings_page_helper.h" +#include "components/security_interstitials/core/metrics_helper.h" + +namespace unstoppable_domains { + +// static +std::unique_ptr +UnstoppableDomainsInterstitialControllerClient::GetMetricsHelper( + const GURL& url) { + security_interstitials::MetricsHelper::ReportDetails report_details; + report_details.metric_prefix = "UnstoppableDomains"; + + return std::make_unique( + url, report_details, nullptr); +} + +UnstoppableDomainsInterstitialControllerClient:: + UnstoppableDomainsInterstitialControllerClient( + content::WebContents* web_contents, + const GURL& request_url, + PrefService* user_prefs, + PrefService* local_state, + const std::string& locale) + : security_interstitials::SecurityInterstitialControllerClient( + web_contents, + GetMetricsHelper(request_url), + user_prefs, + locale, + GURL("about:blank") /* default_safe_page */, + nullptr /* settings_page_helper */), + request_url_(request_url), + local_state_(local_state) {} + +void UnstoppableDomainsInterstitialControllerClient::Proceed() { + SetResolveMethodAndReload(ResolveMethodTypes::DNS_OVER_HTTPS); +} + +void UnstoppableDomainsInterstitialControllerClient::DontProceed() { + SetResolveMethodAndReload(ResolveMethodTypes::DISABLED); +} + +void UnstoppableDomainsInterstitialControllerClient::SetResolveMethodAndReload( + ResolveMethodTypes type) { + DCHECK(local_state_); + local_state_->SetInteger(kResolveMethod, static_cast(type)); + Reload(); +} + +} // namespace unstoppable_domains diff --git a/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h b/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h new file mode 100644 index 00000000000..93947abbdb2 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_INTERSTITIAL_CONTROLLER_CLIENT_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_INTERSTITIAL_CONTROLLER_CLIENT_H_ + +#include +#include + +#include "components/security_interstitials/content/security_interstitial_controller_client.h" +#include "url/gurl.h" + +namespace content { +class WebContents; +} // namespace content + +namespace security_interstitials { +class MetricsHelper; +} // namespace security_interstitials + +class PrefService; + +namespace unstoppable_domains { +enum class ResolveMethodTypes; + +class UnstoppableDomainsInterstitialControllerClient + : public security_interstitials::SecurityInterstitialControllerClient { + public: + static std::unique_ptr + GetMetricsHelper(const GURL& url); + + UnstoppableDomainsInterstitialControllerClient( + content::WebContents* web_contents, + const GURL& request_url, + PrefService* user_prefs, + PrefService* local_state, + const std::string& locale); + ~UnstoppableDomainsInterstitialControllerClient() override = default; + + UnstoppableDomainsInterstitialControllerClient( + const UnstoppableDomainsInterstitialControllerClient&) = delete; + UnstoppableDomainsInterstitialControllerClient& operator=( + const UnstoppableDomainsInterstitialControllerClient&) = delete; + + // security_interstitials::SecurityInterstitialControllerClient: + void Proceed() override; + void DontProceed(); + + private: + void SetResolveMethodAndReload(ResolveMethodTypes type); + + const GURL request_url_; + PrefService* local_state_; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_INTERSTITIAL_CONTROLLER_CLIENT_H_ diff --git a/components/unstoppable_domains/unstoppable_domains_navigation_throttle.cc b/components/unstoppable_domains/unstoppable_domains_navigation_throttle.cc new file mode 100644 index 00000000000..59a2ddb18c6 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_navigation_throttle.cc @@ -0,0 +1,93 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/unstoppable_domains_navigation_throttle.h" + +#include + +#include "base/bind.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_opt_in_page.h" +#include "brave/components/unstoppable_domains/utils.h" +#include "components/prefs/pref_service.h" +#include "components/security_interstitials/content/security_interstitial_tab_helper.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/web_contents.h" +#include "net/base/net_errors.h" + +namespace unstoppable_domains { + +// static +std::unique_ptr +UnstoppableDomainsNavigationThrottle::MaybeCreateThrottleFor( + content::NavigationHandle* navigation_handle, + PrefService* local_state, + const std::string& locale) { + if (!IsUnstoppableDomainsEnabled()) + return nullptr; + + return std::make_unique( + navigation_handle, local_state, locale); +} + +UnstoppableDomainsNavigationThrottle::UnstoppableDomainsNavigationThrottle( + content::NavigationHandle* navigation_handle, + PrefService* local_state, + const std::string& locale) + : content::NavigationThrottle(navigation_handle), + local_state_(local_state), + locale_(locale) { + content::BrowserContext* context = + navigation_handle->GetWebContents()->GetBrowserContext(); + user_prefs_ = user_prefs::UserPrefs::Get(context); +} + +UnstoppableDomainsNavigationThrottle::~UnstoppableDomainsNavigationThrottle() = + default; + +content::NavigationThrottle::ThrottleCheckResult +UnstoppableDomainsNavigationThrottle::WillStartRequest() { + GURL url = navigation_handle()->GetURL(); + if (!IsUnstoppableDomainsTLD(url) || !IsResolveMethodAsk(local_state_)) { + return content::NavigationThrottle::PROCEED; + } + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&UnstoppableDomainsNavigationThrottle::ShowInterstitial, + weak_ptr_factory_.GetWeakPtr())); + return content::NavigationThrottle::DEFER; +} + +void UnstoppableDomainsNavigationThrottle::ShowInterstitial() { + content::NavigationHandle* handle = navigation_handle(); + content::WebContents* web_contents = handle->GetWebContents(); + const GURL& request_url = handle->GetURL(); + + auto controller_client = + std::make_unique( + web_contents, request_url, user_prefs_, local_state_, locale_); + auto page = std::make_unique( + web_contents, handle->GetURL(), std::move(controller_client)); + + // Get the page content before giving up ownership of |page|. + std::string page_content = page->GetHTMLContents(); + + security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage( + web_contents, handle->GetNavigationId(), std::move(page)); + + CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult( + content::NavigationThrottle::CANCEL, net::ERR_BLOCKED_BY_CLIENT, + page_content)); +} + +const char* UnstoppableDomainsNavigationThrottle::GetNameForLogging() { + return "UnstoppableDomainsNavigationThrottle"; +} + +} // namespace unstoppable_domains diff --git a/components/unstoppable_domains/unstoppable_domains_navigation_throttle.h b/components/unstoppable_domains/unstoppable_domains_navigation_throttle.h new file mode 100644 index 00000000000..42b6c754277 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_navigation_throttle.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_NAVIGATION_THROTTLE_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_NAVIGATION_THROTTLE_H_ + +#include +#include + +#include "base/memory/weak_ptr.h" +#include "content/public/browser/navigation_throttle.h" + +namespace content { +class NavigationHandle; +} // namespace content + +class PrefService; + +namespace unstoppable_domains { + +class UnstoppableDomainsNavigationThrottle + : public content::NavigationThrottle { + public: + explicit UnstoppableDomainsNavigationThrottle( + content::NavigationHandle* navigation_handle, + PrefService* local_state, + const std::string& locale); + ~UnstoppableDomainsNavigationThrottle() override; + + UnstoppableDomainsNavigationThrottle( + const UnstoppableDomainsNavigationThrottle&) = delete; + UnstoppableDomainsNavigationThrottle& operator=( + const UnstoppableDomainsNavigationThrottle&) = delete; + + static std::unique_ptr + MaybeCreateThrottleFor(content::NavigationHandle* navigation_handle, + PrefService* local_state, + const std::string& locale); + + // content::NavigationThrottle implementation: + ThrottleCheckResult WillStartRequest() override; + const char* GetNameForLogging() override; + + private: + void ShowInterstitial(); + + PrefService* user_prefs_ = nullptr; + PrefService* local_state_ = nullptr; + std::string locale_; + base::WeakPtrFactory weak_ptr_factory_{ + this}; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_NAVIGATION_THROTTLE_H_ diff --git a/components/unstoppable_domains/unstoppable_domains_opt_in_page.cc b/components/unstoppable_domains/unstoppable_domains_opt_in_page.cc new file mode 100644 index 00000000000..69940d54c56 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_opt_in_page.cc @@ -0,0 +1,111 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/unstoppable_domains_opt_in_page.h" + +#include +#include + +#include "base/notreached.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_interstitial_controller_client.h" +#include "components/grit/brave_components_resources.h" +#include "components/grit/brave_components_strings.h" +#include "components/security_interstitials/content/security_interstitial_controller_client.h" +#include "ui/base/l10n/l10n_util.h" + +namespace unstoppable_domains { + +// static +const security_interstitials::SecurityInterstitialPage::TypeID + UnstoppableDomainsOptInPage::kTypeForTesting = + &UnstoppableDomainsOptInPage::kTypeForTesting; + +UnstoppableDomainsOptInPage::UnstoppableDomainsOptInPage( + content::WebContents* web_contents, + const GURL& request_url, + std::unique_ptr< + security_interstitials::SecurityInterstitialControllerClient> + controller) + : security_interstitials::SecurityInterstitialPage(web_contents, + request_url, + std::move(controller)) {} + +UnstoppableDomainsOptInPage::~UnstoppableDomainsOptInPage() = default; + +void UnstoppableDomainsOptInPage::CommandReceived(const std::string& command) { + if (command == "\"pageLoadComplete\"") { + // content::WaitForRenderFrameReady sends this message when the page + // load completes. Ignore it. + return; + } + + int cmd = 0; + bool retval = base::StringToInt(command, &cmd); + DCHECK(retval); + + switch (cmd) { + case security_interstitials::CMD_DONT_PROCEED: + static_cast(controller()) + ->DontProceed(); + break; + case security_interstitials::CMD_PROCEED: + controller()->Proceed(); + break; + default: + NOTREACHED() << "Unsupported command: " << command; + } +} + +void UnstoppableDomainsOptInPage::PopulateInterstitialStrings( + base::DictionaryValue* load_time_data) { + load_time_data->SetString( + "tabTitle", + l10n_util::GetStringUTF16(IDS_UNSTOPPABLE_DOMAINS_OPT_IN_TITLE)); + load_time_data->SetString( + "heading", + l10n_util::GetStringUTF16(IDS_UNSTOPPABLE_DOMAINS_OPT_IN_HEADING)); + + const std::vector message_params = { + base::ASCIIToUTF16(""), + base::ASCIIToUTF16(""), + base::ASCIIToUTF16( + ""), + base::ASCIIToUTF16(""), + }; + load_time_data->SetString( + "primaryParagraph", + base::ReplaceStringPlaceholders( + l10n_util::GetStringUTF16( + IDS_UNSTOPPABLE_DOMAINS_OPT_IN_PRIMARY_PARAGRAPH), + message_params, nullptr)); + + load_time_data->SetString( + "primaryButtonText", + l10n_util::GetStringUTF16(IDS_UNSTOPPABLE_DOMAINS_OPT_IN_PRIMARY_BUTTON)); + load_time_data->SetString( + "dontProceedButtonText", + l10n_util::GetStringUTF16( + IDS_UNSTOPPABLE_DOMAINS_OPT_IN_DONT_PROCEED_BUTTON)); + load_time_data->SetString("finalParagraph", base::string16()); +} + +int UnstoppableDomainsOptInPage::GetHTMLTemplateId() { + return IDR_UNSTOPPABLE_DOMAINS_INTERSTITIAL_HTML; +} + +security_interstitials::SecurityInterstitialPage::TypeID +UnstoppableDomainsOptInPage::GetTypeForTesting() { + return UnstoppableDomainsOptInPage::kTypeForTesting; +} + +} // namespace unstoppable_domains diff --git a/components/unstoppable_domains/unstoppable_domains_opt_in_page.h b/components/unstoppable_domains/unstoppable_domains_opt_in_page.h new file mode 100644 index 00000000000..9f427b5a088 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_opt_in_page.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_OPT_IN_PAGE_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_OPT_IN_PAGE_H_ + +#include +#include + +#include "components/security_interstitials/content/security_interstitial_page.h" + +namespace content { +class WebContents; +} // namespace content + +namespace security_interstitials { +class SecurityInterstitialControllerClient; +} // namespace security_interstitials + +namespace unstoppable_domains { + +class UnstoppableDomainsOptInPage + : public security_interstitials::SecurityInterstitialPage { + public: + // Interstitial type, used in tests. + static const security_interstitials::SecurityInterstitialPage::TypeID + kTypeForTesting; + + UnstoppableDomainsOptInPage( + content::WebContents* web_contents, + const GURL& request_url, + std::unique_ptr< + security_interstitials::SecurityInterstitialControllerClient> + controller); + ~UnstoppableDomainsOptInPage() override; + + UnstoppableDomainsOptInPage(const UnstoppableDomainsOptInPage&) = delete; + UnstoppableDomainsOptInPage& operator=(const UnstoppableDomainsOptInPage&) = + delete; + + // SecurityInterstitialPage:: + void OnInterstitialClosing() override {} + void CommandReceived(const std::string& command) override; + security_interstitials::SecurityInterstitialPage::TypeID GetTypeForTesting() + override; + + protected: + // SecurityInterstitialPage:: + void PopulateInterstitialStrings( + base::DictionaryValue* load_time_data) override; + int GetHTMLTemplateId() override; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_OPT_IN_PAGE_H_ diff --git a/components/unstoppable_domains/unstoppable_domains_service.cc b/components/unstoppable_domains/unstoppable_domains_service.cc new file mode 100644 index 00000000000..71f3c93c5e3 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_service.cc @@ -0,0 +1,45 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/unstoppable_domains_service.h" + +#include + +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "brave/components/unstoppable_domains/unstoppable_domains_service_delegate.h" +#include "components/prefs/pref_change_registrar.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace unstoppable_domains { + +UnstoppableDomainsService::UnstoppableDomainsService( + std::unique_ptr delegate, + content::BrowserContext* context, + PrefService* local_state) + : delegate_(std::move(delegate)) { + pref_change_registrar_ = std::make_unique(); + pref_change_registrar_->Init(local_state); + pref_change_registrar_->Add( + kResolveMethod, + base::Bind(&UnstoppableDomainsService::OnPreferenceChanged, + base::Unretained(this))); +} + +UnstoppableDomainsService::~UnstoppableDomainsService() = default; + +// static +void UnstoppableDomainsService::RegisterLocalStatePrefs( + PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(kResolveMethod, + static_cast(ResolveMethodTypes::ASK)); +} + +void UnstoppableDomainsService::OnPreferenceChanged() { + delegate_->UpdateNetworkService(); +} + +} // namespace unstoppable_domains diff --git a/components/unstoppable_domains/unstoppable_domains_service.h b/components/unstoppable_domains/unstoppable_domains_service.h new file mode 100644 index 00000000000..7a6e29fca9b --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_service.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_H_ + +#include + +#include "components/keyed_service/core/keyed_service.h" + +namespace content { +class BrowserContext; +} // namespace content + +class PrefChangeRegistrar; +class PrefRegistrySimple; +class PrefService; + +namespace unstoppable_domains { + +class UnstoppableDomainsServiceDelegate; + +class UnstoppableDomainsService : public KeyedService { + public: + UnstoppableDomainsService( + std::unique_ptr delegate, + content::BrowserContext* context, + PrefService* local_state); + ~UnstoppableDomainsService() override; + + UnstoppableDomainsService(const UnstoppableDomainsService&) = delete; + UnstoppableDomainsService& operator=(const UnstoppableDomainsService&) = + delete; + + static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); + + private: + void OnPreferenceChanged(); + + std::unique_ptr pref_change_registrar_; + std::unique_ptr delegate_; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_H_ diff --git a/components/unstoppable_domains/unstoppable_domains_service_delegate.h b/components/unstoppable_domains/unstoppable_domains_service_delegate.h new file mode 100644 index 00000000000..91f2726ffb9 --- /dev/null +++ b/components/unstoppable_domains/unstoppable_domains_service_delegate.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_H_ + +namespace unstoppable_domains { + +class UnstoppableDomainsServiceDelegate { + public: + UnstoppableDomainsServiceDelegate() = default; + virtual ~UnstoppableDomainsServiceDelegate() = default; + virtual void UpdateNetworkService() = 0; +}; + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UNSTOPPABLE_DOMAINS_SERVICE_DELEGATE_H_ diff --git a/components/unstoppable_domains/utils.cc b/components/unstoppable_domains/utils.cc new file mode 100644 index 00000000000..84e2cc4b173 --- /dev/null +++ b/components/unstoppable_domains/utils.cc @@ -0,0 +1,45 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "brave/components/unstoppable_domains/utils.h" + +#include "base/feature_list.h" +#include "base/strings/string_util.h" +#include "brave/components/unstoppable_domains/constants.h" +#include "brave/components/unstoppable_domains/features.h" +#include "brave/components/unstoppable_domains/pref_names.h" +#include "brave/net/unstoppable_domains/constants.h" +#include "components/prefs/pref_service.h" +#include "url/gurl.h" + +namespace unstoppable_domains { + +bool IsUnstoppableDomainsTLD(const GURL& url) { + return base::EndsWith(url.host_piece(), kCryptoDomain); +} + +bool IsUnstoppableDomainsEnabled() { + return base::FeatureList::IsEnabled(features::kUnstoppableDomains); +} + +bool IsResolveMethodAsk(PrefService* local_state) { + if (!local_state || !IsUnstoppableDomainsEnabled()) { + return false; // Treat it as disabled. + } + + return local_state->GetInteger(kResolveMethod) == + static_cast(ResolveMethodTypes::ASK); +} + +bool IsResolveMethodDoH(PrefService* local_state) { + if (!local_state || !IsUnstoppableDomainsEnabled()) { + return false; // Treat it as disabled. + } + + return local_state->GetInteger(kResolveMethod) == + static_cast(ResolveMethodTypes::DNS_OVER_HTTPS); +} + +} // namespace unstoppable_domains diff --git a/components/unstoppable_domains/utils.h b/components/unstoppable_domains/utils.h new file mode 100644 index 00000000000..724bc573e99 --- /dev/null +++ b/components/unstoppable_domains/utils.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UTILS_H_ +#define BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UTILS_H_ + +class GURL; +class PrefService; + +namespace unstoppable_domains { + +bool IsUnstoppableDomainsTLD(const GURL& url); +bool IsUnstoppableDomainsEnabled(); +bool IsResolveMethodAsk(PrefService* local_state); +bool IsResolveMethodDoH(PrefService* local_state); + +} // namespace unstoppable_domains + +#endif // BRAVE_COMPONENTS_UNSTOPPABLE_DOMAINS_UTILS_H_ diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc new file mode 100644 index 00000000000..34697f2f515 --- /dev/null +++ b/net/dns/dns_transaction_unittest.cc @@ -0,0 +1,1023 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#include "net/dns/dns_transaction.h" + +#include + +#include +#include +#include + +#include "base/base64url.h" +#include "base/bind.h" +#include "base/containers/circular_deque.h" +#include "base/optional.h" +#include "base/rand_util.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/sys_byteorder.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "base/values.h" +#include "brave/net/unstoppable_domains/constants.h" +#include "net/base/ip_address.h" +#include "net/base/port_util.h" +#include "net/base/upload_bytes_element_reader.h" +#include "net/base/url_util.h" +#include "net/cookies/cookie_access_result.h" +#include "net/cookies/cookie_util.h" +#include "net/dns/dns_config.h" +#include "net/dns/dns_query.h" +#include "net/dns/dns_response.h" +#include "net/dns/dns_server_iterator.h" +#include "net/dns/dns_session.h" +#include "net/dns/dns_socket_allocator.h" +#include "net/dns/dns_test_util.h" +#include "net/dns/dns_util.h" +#include "net/dns/public/dns_over_https_server_config.h" +#include "net/dns/public/dns_protocol.h" +#include "net/dns/resolve_context.h" +#include "net/log/net_log.h" +#include "net/log/net_log_capture_mode.h" +#include "net/log/net_log_with_source.h" +#include "net/log/test_net_log.h" +#include "net/proxy_resolution/proxy_config_service_fixed.h" +#include "net/socket/socket_test_util.h" +#include "net/test/gtest_util.h" +#include "net/test/test_with_task_environment.h" +#include "net/test/url_request/url_request_failed_job.h" +#include "net/third_party/uri_template/uri_template.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_interceptor.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsOk; + +namespace net { + +namespace { + +base::TimeDelta kFallbackPeriod = base::TimeDelta::FromSeconds(1); + +const char kMockHostname[] = "mock.http"; + +std::string DomainFromDot(const base::StringPiece& dotted) { + std::string out; + EXPECT_TRUE(DNSDomainFromDot(dotted, &out)); + return out; +} + +enum class Transport { UDP, TCP, HTTPS }; + +// A SocketDataProvider builder. +class DnsSocketData { + public: + // The ctor takes parameters for the DnsQuery. + DnsSocketData(uint16_t id, + const char* dotted_name, + uint16_t qtype, + IoMode mode, + Transport transport, + const OptRecordRdata* opt_rdata = nullptr, + DnsQuery::PaddingStrategy padding_strategy = + DnsQuery::PaddingStrategy::NONE) + : query_(new DnsQuery(id, + DomainFromDot(dotted_name), + qtype, + opt_rdata, + padding_strategy)), + transport_(transport) { + if (Transport::TCP == transport_) { + std::unique_ptr length(new uint16_t); + *length = base::HostToNet16(query_->io_buffer()->size()); + writes_.push_back(MockWrite(mode, + reinterpret_cast(length.get()), + sizeof(uint16_t), num_reads_and_writes())); + lengths_.push_back(std::move(length)); + } + writes_.push_back(MockWrite(mode, query_->io_buffer()->data(), + query_->io_buffer()->size(), + num_reads_and_writes())); + } + ~DnsSocketData() = default; + + // All responses must be added before GetProvider. + + // Adds pre-built DnsResponse. |tcp_length| will be used in TCP mode only. + void AddResponseWithLength(std::unique_ptr response, + IoMode mode, + uint16_t tcp_length) { + CHECK(!provider_.get()); + if (Transport::TCP == transport_) { + std::unique_ptr length(new uint16_t); + *length = base::HostToNet16(tcp_length); + reads_.push_back(MockRead(mode, + reinterpret_cast(length.get()), + sizeof(uint16_t), num_reads_and_writes())); + lengths_.push_back(std::move(length)); + } + reads_.push_back(MockRead(mode, response->io_buffer()->data(), + response->io_buffer_size(), + num_reads_and_writes())); + responses_.push_back(std::move(response)); + } + + // Adds pre-built DnsResponse. + void AddResponse(std::unique_ptr response, IoMode mode) { + uint16_t tcp_length = response->io_buffer_size(); + AddResponseWithLength(std::move(response), mode, tcp_length); + } + + // Adds pre-built response from |data| buffer. + void AddResponseData(const uint8_t* data, size_t length, IoMode mode) { + CHECK(!provider_.get()); + AddResponse(std::make_unique( + reinterpret_cast(data), length, 0), + mode); + } + + // Adds pre-built response from |data| buffer. + void AddResponseData(const uint8_t* data, + size_t length, + int offset, + IoMode mode) { + CHECK(!provider_.get()); + AddResponse( + std::make_unique(reinterpret_cast(data), + length - offset, offset), + mode); + } + + // Add no-answer (RCODE only) response matching the query. + void AddRcode(int rcode, IoMode mode) { + std::unique_ptr response(new DnsResponse( + query_->io_buffer()->data(), query_->io_buffer()->size(), 0)); + dns_protocol::Header* header = + reinterpret_cast(response->io_buffer()->data()); + header->flags |= base::HostToNet16(dns_protocol::kFlagResponse | rcode); + AddResponse(std::move(response), mode); + } + + // Add error response. + void AddReadError(int error, IoMode mode) { + reads_.push_back(MockRead(mode, error, num_reads_and_writes())); + } + + // Build, if needed, and return the SocketDataProvider. No new responses + // should be added afterwards. + SequencedSocketData* GetProvider() { + if (provider_.get()) + return provider_.get(); + // Terminate the reads with ERR_IO_PENDING to prevent overrun and default to + // timeout. + if (transport_ != Transport::HTTPS) { + reads_.push_back(MockRead(SYNCHRONOUS, ERR_IO_PENDING, + writes_.size() + reads_.size())); + } + provider_.reset(new SequencedSocketData(reads_, writes_)); + if (Transport::TCP == transport_ || Transport::HTTPS == transport_) { + provider_->set_connect_data(MockConnect(reads_[0].mode, OK)); + } + return provider_.get(); + } + + uint16_t query_id() const { return query_->id(); } + + IOBufferWithSize* query_buffer() { return query_->io_buffer(); } + + private: + size_t num_reads_and_writes() const { return reads_.size() + writes_.size(); } + + std::unique_ptr query_; + Transport transport_; + std::vector> lengths_; + std::vector> responses_; + std::vector writes_; + std::vector reads_; + std::unique_ptr provider_; + + DISALLOW_COPY_AND_ASSIGN(DnsSocketData); +}; + +class TestSocketFactory; + +// A variant of MockUDPClientSocket which always fails to Connect. +class FailingUDPClientSocket : public MockUDPClientSocket { + public: + FailingUDPClientSocket(SocketDataProvider* data, net::NetLog* net_log) + : MockUDPClientSocket(data, net_log) {} + ~FailingUDPClientSocket() override = default; + int Connect(const IPEndPoint& endpoint) override { + return ERR_CONNECTION_REFUSED; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FailingUDPClientSocket); +}; + +// A variant of MockUDPClientSocket which notifies the factory OnConnect. +class TestUDPClientSocket : public MockUDPClientSocket { + public: + TestUDPClientSocket(TestSocketFactory* factory, + SocketDataProvider* data, + net::NetLog* net_log) + : MockUDPClientSocket(data, net_log), factory_(factory) {} + ~TestUDPClientSocket() override = default; + int Connect(const IPEndPoint& endpoint) override; + + private: + TestSocketFactory* factory_; + + DISALLOW_COPY_AND_ASSIGN(TestUDPClientSocket); +}; + +// Creates TestUDPClientSockets and keeps endpoints reported via OnConnect. +class TestSocketFactory : public MockClientSocketFactory { + public: + TestSocketFactory() = default; + ~TestSocketFactory() override = default; + + std::unique_ptr CreateDatagramClientSocket( + DatagramSocket::BindType bind_type, + NetLog* net_log, + const NetLogSource& source) override { + if (fail_next_socket_) { + fail_next_socket_ = false; + return std::unique_ptr( + new FailingUDPClientSocket(&empty_data_, net_log)); + } + + SocketDataProvider* data_provider = mock_data().GetNext(); + auto socket = + std::make_unique(this, data_provider, net_log); + + // Even using DEFAULT_BIND, actual sockets have been measured to very rarely + // repeat the same source port multiple times in a row. Need to mimic that + // functionality here, so DnsUdpTracker doesn't misdiagnose repeated port + // as low entropy. + if (diverse_source_ports_) + socket->set_source_port(next_source_port_++); + + return socket; + } + + void OnConnect(const IPEndPoint& endpoint) { + remote_endpoints_.emplace_back(endpoint); + } + + struct RemoteNameserver { + explicit RemoteNameserver(IPEndPoint insecure_nameserver) + : insecure_nameserver(insecure_nameserver) {} + explicit RemoteNameserver(DnsOverHttpsServerConfig secure_nameserver) + : secure_nameserver(secure_nameserver) {} + + base::Optional insecure_nameserver; + base::Optional secure_nameserver; + }; + + std::vector remote_endpoints_; + bool fail_next_socket_ = false; + bool diverse_source_ports_ = true; + + private: + StaticSocketDataProvider empty_data_; + uint16_t next_source_port_ = 123; +}; + +int TestUDPClientSocket::Connect(const IPEndPoint& endpoint) { + factory_->OnConnect(endpoint); + return MockUDPClientSocket::Connect(endpoint); +} + +// Helper class that holds a DnsTransaction and handles OnTransactionComplete. +class TransactionHelper { + public: + // If |expected_answer_count| < 0 then it is the expected net error. + explicit TransactionHelper(int expected_answer_count) + : expected_answer_count_(expected_answer_count) {} + + // Mark that the transaction shall be destroyed immediately upon callback. + void set_cancel_in_callback() { cancel_in_callback_ = true; } + + void StartTransaction(DnsTransactionFactory* factory, + const char* hostname, + uint16_t qtype, + bool secure, + ResolveContext* context) { + std::unique_ptr transaction = factory->CreateTransaction( + hostname, qtype, CompletionCallback(), + NetLogWithSource::Make(&net_log_, net::NetLogSourceType::NONE), secure, + factory->GetSecureDnsModeForTest(), context, true /* fast_timeout */); + transaction->SetRequestPriority(DEFAULT_PRIORITY); + EXPECT_EQ(qtype, transaction->GetType()); + StartTransaction(std::move(transaction)); + } + + void StartTransaction(std::unique_ptr transaction) { + EXPECT_FALSE(transaction_); + transaction_ = std::move(transaction); + qtype_ = transaction_->GetType(); + transaction_->Start(); + } + + void Cancel() { + ASSERT_TRUE(transaction_.get() != nullptr); + transaction_.reset(nullptr); + } + + DnsTransactionFactory::CallbackType CompletionCallback() { + return base::BindOnce(&TransactionHelper::OnTransactionComplete, + base::Unretained(this)); + } + + void OnTransactionComplete(DnsTransaction* t, + int rv, + const DnsResponse* response, + base::Optional doh_provider_id) { + EXPECT_FALSE(completed_); + EXPECT_EQ(transaction_.get(), t); + + completed_ = true; + response_ = response; + + transaction_complete_run_loop_.Quit(); + + if (cancel_in_callback_) { + Cancel(); + return; + } + + if (response) + EXPECT_TRUE(response->IsValid()); + + if (expected_answer_count_ >= 0) { + ASSERT_THAT(rv, IsOk()); + ASSERT_TRUE(response != nullptr); + EXPECT_EQ(static_cast(expected_answer_count_), + response->answer_count()); + EXPECT_EQ(qtype_, response->qtype()); + + DnsRecordParser parser = response->Parser(); + DnsResourceRecord record; + for (int i = 0; i < expected_answer_count_; ++i) { + EXPECT_TRUE(parser.ReadRecord(&record)); + } + } else { + EXPECT_EQ(expected_answer_count_, rv); + } + } + + bool has_completed() const { return completed_; } + const DnsResponse* response() const { return response_; } + NetLog* net_log() { return &net_log_; } + + // Runs until the completion callback is called. Transaction must have already + // been started or this will never complete. + void RunUntilComplete() { + DCHECK(transaction_); + DCHECK(!transaction_complete_run_loop_.running()); + transaction_complete_run_loop_.Run(); + DCHECK(has_completed()); + } + + private: + uint16_t qtype_ = 0; + std::unique_ptr transaction_; + const DnsResponse* response_ = nullptr; + int expected_answer_count_; + bool cancel_in_callback_ = false; + base::RunLoop transaction_complete_run_loop_; + bool completed_ = false; + TestNetLog net_log_; +}; + +// Callback that allows a test to modify HttpResponseinfo +// before the response is sent to the requester. This allows +// response headers to be changed. +typedef base::RepeatingCallback + ResponseModifierCallback; + +// Callback that allows the test to substitute its own implementation +// of URLRequestJob to handle the request. +typedef base::RepeatingCallback( + URLRequest* request, + SocketDataProvider* data_provider)> + DohJobMakerCallback; + +// Subclass of URLRequestJob which takes a SocketDataProvider with data +// representing both a DNS over HTTPS query and response. +class URLRequestMockDohJob : public URLRequestJob, public AsyncSocket { + public: + URLRequestMockDohJob( + URLRequest* request, + SocketDataProvider* data_provider, + ResponseModifierCallback response_modifier = ResponseModifierCallback()) + : URLRequestJob(request), + content_length_(0), + leftover_data_len_(0), + data_provider_(data_provider), + response_modifier_(response_modifier) { + data_provider_->Initialize(this); + MatchQueryData(request, data_provider); + } + + // Compare the query contained in either the POST body or the body + // parameter of the GET query to the write data of the SocketDataProvider. + static void MatchQueryData(URLRequest* request, + SocketDataProvider* data_provider) { + std::string decoded_query; + if (request->method() == "GET") { + std::string encoded_query; + EXPECT_TRUE(GetValueForKeyInQuery(request->url(), "dns", &encoded_query)); + EXPECT_GT(encoded_query.size(), 0ul); + + EXPECT_TRUE(base::Base64UrlDecode( + encoded_query, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &decoded_query)); + } else if (request->method() == "POST") { + const UploadDataStream* stream = request->get_upload_for_testing(); + auto* readers = stream->GetElementReaders(); + EXPECT_TRUE(readers); + EXPECT_FALSE(readers->empty()); + for (auto& reader : *readers) { + const UploadBytesElementReader* byte_reader = reader->AsBytesReader(); + decoded_query += + std::string(byte_reader->bytes(), byte_reader->length()); + } + } + + std::string query(decoded_query); + MockWriteResult result(SYNCHRONOUS, 1); + while (result.result > 0 && query.length() > 0) { + result = data_provider->OnWrite(query); + if (result.result > 0) + query = query.substr(result.result); + } + } + + static std::string GetMockHttpsUrl(const std::string& path) { + return "https://" + (kMockHostname + ("/" + path)); + } + + // URLRequestJob implementation: + void Start() override { + // Start reading asynchronously so that all error reporting and data + // callbacks happen as they would for network requests. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&URLRequestMockDohJob::StartAsync, + weak_factory_.GetWeakPtr())); + } + + ~URLRequestMockDohJob() override { + if (data_provider_) + data_provider_->DetachSocket(); + } + + int ReadRawData(IOBuffer* buf, int buf_size) override { + if (!data_provider_) + return ERR_FAILED; + if (leftover_data_len_ > 0) { + int rv = DoBufferCopy(leftover_data_, leftover_data_len_, buf, buf_size); + return rv; + } + + if (data_provider_->AllReadDataConsumed()) + return 0; + + MockRead read = data_provider_->OnRead(); + + if (read.result < ERR_IO_PENDING) + return read.result; + + if (read.result == ERR_IO_PENDING) { + pending_buf_ = buf; + pending_buf_size_ = buf_size; + return ERR_IO_PENDING; + } + return DoBufferCopy(read.data, read.data_len, buf, buf_size); + } + + void GetResponseInfo(HttpResponseInfo* info) override { + // Send back mock headers. + std::string raw_headers; + raw_headers.append( + "HTTP/1.1 200 OK\n" + "Content-type: application/dns-message\n"); + if (content_length_ > 0) { + raw_headers.append(base::StringPrintf("Content-Length: %1d\n", + static_cast(content_length_))); + } + info->headers = base::MakeRefCounted( + HttpUtil::AssembleRawHeaders(raw_headers)); + if (response_modifier_) + response_modifier_.Run(request(), info); + } + + // AsyncSocket implementation: + void OnReadComplete(const MockRead& data) override { + EXPECT_NE(data.result, ERR_IO_PENDING); + if (data.result < 0) + return ReadRawDataComplete(data.result); + ReadRawDataComplete(DoBufferCopy(data.data, data.data_len, pending_buf_, + pending_buf_size_)); + } + void OnWriteComplete(int rv) override {} + void OnConnectComplete(const MockConnect& data) override {} + void OnDataProviderDestroyed() override { data_provider_ = nullptr; } + + private: + void StartAsync() { + if (!request_) + return; + if (content_length_) + set_expected_content_size(content_length_); + NotifyHeadersComplete(); + } + + int DoBufferCopy(const char* data, + int data_len, + IOBuffer* buf, + int buf_size) { + if (data_len > buf_size) { + memcpy(buf->data(), data, buf_size); + leftover_data_ = data + buf_size; + leftover_data_len_ = data_len - buf_size; + return buf_size; + } + memcpy(buf->data(), data, data_len); + return data_len; + } + + const int content_length_; + const char* leftover_data_; + int leftover_data_len_; + SocketDataProvider* data_provider_; + const ResponseModifierCallback response_modifier_; + IOBuffer* pending_buf_; + int pending_buf_size_; + + base::WeakPtrFactory weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(URLRequestMockDohJob); +}; + +class DnsTransactionTestBase : public testing::Test { + public: + DnsTransactionTestBase() = default; + + ~DnsTransactionTestBase() override { + // All queued transaction IDs should be used by a transaction calling + // GetNextId(). + CHECK(transaction_ids_.empty()); + } + + // Generates |nameservers| for DnsConfig. + void ConfigureNumServers(size_t num_servers) { + CHECK_LE(num_servers, 255u); + config_.nameservers.clear(); + for (size_t i = 0; i < num_servers; ++i) { + config_.nameservers.push_back( + IPEndPoint(IPAddress(192, 168, 1, i), dns_protocol::kDefaultPort)); + } + } + + // Configures the DnsConfig DNS-over-HTTPS server(s), which either + // accept GET or POST requests based on use_post. If a + // ResponseModifierCallback is provided it will be called to construct the + // HTTPResponse. + void ConfigureDohServers(bool use_post, + size_t num_doh_servers = 1, + bool make_available = true) { + GURL url(URLRequestMockDohJob::GetMockHttpsUrl("doh_test")); + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->AddHostnameInterceptor(url.scheme(), url.host(), + std::make_unique(this)); + CHECK_LE(num_doh_servers, 255u); + for (size_t i = 0; i < num_doh_servers; ++i) { + std::string server_template(URLRequestMockDohJob::GetMockHttpsUrl( + base::StringPrintf("doh_test_%zu", i)) + + "{?dns}"); + config_.dns_over_https_servers.push_back( + DnsOverHttpsServerConfig(server_template, use_post)); + } + ConfigureFactory(); + + if (make_available) { + for (size_t server_index = 0; server_index < num_doh_servers; + ++server_index) { + resolve_context_->RecordServerSuccess( + server_index, true /* is_doh_server */, session_.get()); + } + } + } + + // Called after fully configuring |config|. + void ConfigureFactory() { + socket_factory_.reset(new TestSocketFactory()); + session_ = new DnsSession( + config_, + std::make_unique( + socket_factory_.get(), config_.nameservers, nullptr /* net_log */), + base::BindRepeating(&DnsTransactionTestBase::GetNextId, + base::Unretained(this)), + nullptr /* NetLog */); + resolve_context_->InvalidateCachesAndPerSessionData( + session_.get(), false /* network_change */); + transaction_factory_ = DnsTransactionFactory::CreateFactory(session_.get()); + } + + void AddSocketData(std::unique_ptr data, + bool enqueue_transaction_id = true) { + CHECK(socket_factory_.get()); + if (enqueue_transaction_id) + transaction_ids_.push_back(data->query_id()); + socket_factory_->AddSocketDataProvider(data->GetProvider()); + socket_data_.push_back(std::move(data)); + } + + // Add expected query for |dotted_name| and |qtype| with |id| and response + // taken verbatim from |data| of |data_length| bytes. The transaction id in + // |data| should equal |id|, unless testing mismatched response. + void AddQueryAndResponse(uint16_t id, + const char* dotted_name, + uint16_t qtype, + const uint8_t* response_data, + size_t response_length, + IoMode mode, + Transport transport, + const OptRecordRdata* opt_rdata = nullptr, + DnsQuery::PaddingStrategy padding_strategy = + DnsQuery::PaddingStrategy::NONE, + bool enqueue_transaction_id = true) { + CHECK(socket_factory_.get()); + std::unique_ptr data(new DnsSocketData( + id, dotted_name, qtype, mode, transport, opt_rdata, padding_strategy)); + data->AddResponseData(response_data, response_length, mode); + AddSocketData(std::move(data), enqueue_transaction_id); + } + + void AddQueryAndErrorResponse(uint16_t id, + const char* dotted_name, + uint16_t qtype, + int error, + IoMode mode, + Transport transport, + const OptRecordRdata* opt_rdata = nullptr, + DnsQuery::PaddingStrategy padding_strategy = + DnsQuery::PaddingStrategy::NONE, + bool enqueue_transaction_id = true) { + CHECK(socket_factory_.get()); + std::unique_ptr data(new DnsSocketData( + id, dotted_name, qtype, mode, transport, opt_rdata, padding_strategy)); + data->AddReadError(error, mode); + AddSocketData(std::move(data), enqueue_transaction_id); + } + + void AddAsyncQueryAndResponse(uint16_t id, + const char* dotted_name, + uint16_t qtype, + const uint8_t* data, + size_t data_length, + const OptRecordRdata* opt_rdata = nullptr) { + AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC, + Transport::UDP, opt_rdata); + } + + void AddSyncQueryAndResponse(uint16_t id, + const char* dotted_name, + uint16_t qtype, + const uint8_t* data, + size_t data_length, + const OptRecordRdata* opt_rdata = nullptr) { + AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS, + Transport::UDP, opt_rdata); + } + + // Add expected query of |dotted_name| and |qtype| and no response. + void AddHangingQuery( + const char* dotted_name, + uint16_t qtype, + DnsQuery::PaddingStrategy padding_strategy = + DnsQuery::PaddingStrategy::NONE, + uint16_t id = base::RandInt(0, std::numeric_limits::max()), + bool enqueue_transaction_id = true) { + std::unique_ptr data( + new DnsSocketData(id, dotted_name, qtype, ASYNC, Transport::UDP, + nullptr /* opt_rdata */, padding_strategy)); + AddSocketData(std::move(data), enqueue_transaction_id); + } + + // Add expected query of |dotted_name| and |qtype| and matching response with + // no answer and RCODE set to |rcode|. The id will be generated randomly. + void AddQueryAndRcode( + const char* dotted_name, + uint16_t qtype, + int rcode, + IoMode mode, + Transport trans, + DnsQuery::PaddingStrategy padding_strategy = + DnsQuery::PaddingStrategy::NONE, + uint16_t id = base::RandInt(0, std::numeric_limits::max()), + bool enqueue_transaction_id = true) { + CHECK_NE(dns_protocol::kRcodeNOERROR, rcode); + std::unique_ptr data( + new DnsSocketData(id, dotted_name, qtype, mode, trans, + nullptr /* opt_rdata */, padding_strategy)); + data->AddRcode(rcode, mode); + AddSocketData(std::move(data), enqueue_transaction_id); + } + + void AddAsyncQueryAndRcode(const char* dotted_name, + uint16_t qtype, + int rcode) { + AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, Transport::UDP); + } + + void AddSyncQueryAndRcode(const char* dotted_name, + uint16_t qtype, + int rcode) { + AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, Transport::UDP); + } + + // Checks if the sockets were connected in the order matching the indices in + // |servers|. + void CheckServerOrder(const size_t* servers, size_t num_attempts) { + ASSERT_EQ(num_attempts, socket_factory_->remote_endpoints_.size()); + auto num_insecure_nameservers = session_->config().nameservers.size(); + for (size_t i = 0; i < num_attempts; ++i) { + if (servers[i] < num_insecure_nameservers) { + // Check insecure server match. + EXPECT_EQ( + socket_factory_->remote_endpoints_[i].insecure_nameserver.value(), + session_->config().nameservers[servers[i]]); + } else { + // Check secure server match. + EXPECT_EQ( + socket_factory_->remote_endpoints_[i].secure_nameserver.value(), + session_->config() + .dns_over_https_servers[servers[i] - num_insecure_nameservers]); + } + } + } + + std::unique_ptr MaybeInterceptRequest(URLRequest* request) { + // If the path indicates a redirect, skip checking the list of + // configured servers, because it won't be there and we still want + // to handle it. + bool server_found = request->url().path() == "/redirect-destination"; + for (auto server : config_.dns_over_https_servers) { + if (server_found) + break; + std::string url_base = + GetURLFromTemplateWithoutParameters(server.server_template); + if (server.use_post && request->method() == "POST") { + if (url_base == request->url().spec()) { + server_found = true; + socket_factory_->remote_endpoints_.emplace_back(server); + } + } else if (!server.use_post && request->method() == "GET") { + std::string prefix = url_base + "?dns="; + auto mispair = std::mismatch(prefix.begin(), prefix.end(), + request->url().spec().begin()); + if (mispair.first == prefix.end()) { + server_found = true; + socket_factory_->remote_endpoints_.emplace_back(server); + } + } + } + EXPECT_TRUE(server_found); + + EXPECT_TRUE( + request->isolation_info().network_isolation_key().IsTransient()); + + // All DoH requests for the same ResolveContext should use the same + // IsolationInfo, so network objects like sockets can be reused between + // requests. + if (!expect_multiple_isolation_infos_) { + if (!isolation_info_) { + isolation_info_ = + std::make_unique(request->isolation_info()); + } else { + EXPECT_TRUE( + isolation_info_->IsEqualForTesting(request->isolation_info())); + } + } + + EXPECT_FALSE(request->allow_credentials()); + EXPECT_TRUE(request->disable_secure_dns()); + + std::string accept; + EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept", &accept)); + EXPECT_EQ(accept, "application/dns-message"); + + std::string language; + EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept-Language", + &language)); + EXPECT_EQ(language, "*"); + + std::string user_agent; + EXPECT_TRUE( + request->extra_request_headers().GetHeader("User-Agent", &user_agent)); + EXPECT_EQ(user_agent, "Chrome"); + + SocketDataProvider* provider = socket_factory_->mock_data().GetNext(); + + if (doh_job_maker_) + return doh_job_maker_.Run(request, provider); + + return std::make_unique(request, provider, + response_modifier_); + } + + class DohJobInterceptor : public URLRequestInterceptor { + public: + explicit DohJobInterceptor(DnsTransactionTestBase* test) : test_(test) {} + ~DohJobInterceptor() override {} + + // URLRequestInterceptor implementation: + std::unique_ptr MaybeInterceptRequest( + URLRequest* request) const override { + return test_->MaybeInterceptRequest(request); + } + + private: + DnsTransactionTestBase* test_; + + DISALLOW_COPY_AND_ASSIGN(DohJobInterceptor); + }; + + void SetResponseModifierCallback(ResponseModifierCallback response_modifier) { + response_modifier_ = response_modifier; + } + + void SetDohJobMakerCallback(DohJobMakerCallback doh_job_maker) { + doh_job_maker_ = doh_job_maker; + } + + void SetUp() override { + // By default set one server, + ConfigureNumServers(1); + // and no retransmissions, + config_.attempts = 1; + // and an arbitrary fallback period. + config_.fallback_period = kFallbackPeriod; + + request_context_ = std::make_unique(); + resolve_context_ = std::make_unique( + request_context_.get(), false /* enable_caching */); + + ConfigureFactory(); + } + + void TearDown() override { + // Check that all socket data was at least written to. + for (size_t i = 0; i < socket_data_.size(); ++i) { + EXPECT_TRUE(socket_data_[i]->GetProvider()->AllWriteDataConsumed()) << i; + } + + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->ClearHandlers(); + } + + void set_expect_multiple_isolation_infos( + bool expect_multiple_isolation_infos) { + expect_multiple_isolation_infos_ = expect_multiple_isolation_infos; + } + + protected: + int GetNextId(int min, int max) { + EXPECT_FALSE(transaction_ids_.empty()); + int id = transaction_ids_.front(); + transaction_ids_.pop_front(); + EXPECT_GE(id, min); + EXPECT_LE(id, max); + return id; + } + + DnsConfig config_; + + std::vector> socket_data_; + + base::circular_deque transaction_ids_; + std::unique_ptr socket_factory_; + std::unique_ptr request_context_; + std::unique_ptr resolve_context_; + scoped_refptr session_; + std::unique_ptr transaction_factory_; + ResponseModifierCallback response_modifier_; + DohJobMakerCallback doh_job_maker_; + + // Whether multiple IsolationInfos should be expected (due to there being + // multiple RequestContexts in use). + bool expect_multiple_isolation_infos_ = false; + + // IsolationInfo used by DoH requests. Populated on first DoH request, and + // compared to IsolationInfo used by all subsequent requests, unless + // |expect_multiple_isolation_infos_| is true. + std::unique_ptr isolation_info_; +}; + +static const char kTestCryptoHostName[] = "test.crypto"; + +// Response contains IP address: 142.250.72.196 for test.crypto. +static const uint8_t kTestCryptoResponseDatagram[] = { + 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x74, 0x65, 0x73, 0x74, 0x06, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, + 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xa2, 0x00, 0x04, 0x8e, 0xfa, 0x48, 0xc4}; + +class BraveDnsTransactionTest : public DnsTransactionTestBase, + public WithTaskEnvironment { + public: + BraveDnsTransactionTest() = default; + + ~BraveDnsTransactionTest() override = default; + + void BraveConfigureDohServers(bool multiple_server) { + GURL url(unstoppable_domains::kDoHResolver); + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->AddHostnameInterceptor(url.scheme(), url.host(), + std::make_unique(this)); + config_.dns_over_https_servers.push_back( + {unstoppable_domains::kDoHResolver, true}); + + if (multiple_server) { + GURL url2("https://test.com/dns-query"); + filter->AddHostnameInterceptor(url2.scheme(), url2.host(), + std::make_unique(this)); + config_.dns_over_https_servers.push_back({url2.spec(), true}); + } + + ConfigureFactory(); + for (size_t server_index = 0; + server_index < config_.dns_over_https_servers.size(); ++server_index) { + resolve_context_->RecordServerSuccess( + server_index, true /* is_doh_server */, session_.get()); + } + } +}; + +TEST_F(BraveDnsTransactionTest, SkipUDResolverForNonCryptoDomainsSingleServer) { + BraveConfigureDohServers(false); + EXPECT_TRUE(resolve_context_->GetDohServerAvailability( + 0u /* doh_server_index */, session_.get())); + TransactionHelper helper0(ERR_BLOCKED_BY_CLIENT); + helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype, + true /* secure */, resolve_context_.get()); + helper0.RunUntilComplete(); +} + +TEST_F(BraveDnsTransactionTest, + SkipUDResolverForNonCryptoDomainsMultipleServers) { + BraveConfigureDohServers(true); + AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, + base::size(kT0ResponseDatagram), SYNCHRONOUS, + Transport::HTTPS, nullptr /* opt_rdata */, + DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, + false /* enqueue_transaction_id */); + TransactionHelper helper0(kT0RecordCount); + helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype, + true /* secure */, resolve_context_.get()); + helper0.RunUntilComplete(); +} + +TEST_F(BraveDnsTransactionTest, UseUDResolverForCryptoDomainsSingleServer) { + BraveConfigureDohServers(false); + AddQueryAndResponse( + 0, kTestCryptoHostName, dns_protocol::kTypeA, kTestCryptoResponseDatagram, + base::size(kTestCryptoResponseDatagram), SYNCHRONOUS, Transport::HTTPS, + nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, + false /* enqueue_transaction_id */); + TransactionHelper helper0(1); + helper0.StartTransaction(transaction_factory_.get(), kTestCryptoHostName, + dns_protocol::kTypeA, true /* secure */, + resolve_context_.get()); + helper0.RunUntilComplete(); +} + +TEST_F(BraveDnsTransactionTest, UseUDResolverForCryptoDomainsMultipleServer) { + BraveConfigureDohServers(true); + AddQueryAndResponse( + 0, kTestCryptoHostName, dns_protocol::kTypeA, kTestCryptoResponseDatagram, + base::size(kTestCryptoResponseDatagram), SYNCHRONOUS, Transport::HTTPS, + nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, + false /* enqueue_transaction_id */); + TransactionHelper helper0(1); + helper0.StartTransaction(transaction_factory_.get(), kTestCryptoHostName, + dns_protocol::kTypeA, true /* secure */, + resolve_context_.get()); + helper0.RunUntilComplete(); +} + +} // namespace + +} // namespace net diff --git a/net/dns/sources.gni b/net/dns/sources.gni new file mode 100644 index 00000000000..8d6c6c53bbf --- /dev/null +++ b/net/dns/sources.gni @@ -0,0 +1,6 @@ +# Copyright (c) 2021 The Brave Authors. All rights reserved. +# 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/. + +brave_dns_friend = [ "//brave/browser/unstoppable_domains/test:unit_tests" ] diff --git a/net/sources.gni b/net/sources.gni index 784143f6efc..13dd035d6a1 100644 --- a/net/sources.gni +++ b/net/sources.gni @@ -3,7 +3,10 @@ # 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/. +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") + brave_net_sources = [ "//brave/net/proxy_resolution/proxy_config_service_tor.cc", "//brave/net/proxy_resolution/proxy_config_service_tor.h", + "//brave/net/unstoppable_domains/constants.h", ] diff --git a/net/unstoppable_domains/constants.h b/net/unstoppable_domains/constants.h new file mode 100644 index 00000000000..f4ab9c7d6e5 --- /dev/null +++ b/net/unstoppable_domains/constants.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_NET_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ +#define BRAVE_NET_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ + +namespace unstoppable_domains { + +constexpr char kCryptoDomain[] = ".crypto"; +constexpr char kDoHResolver[] = + "https://resolver.unstoppable.io/dns-query{?brave_UD}"; + +} // namespace unstoppable_domains + +#endif // BRAVE_NET_UNSTOPPABLE_DOMAINS_CONSTANTS_H_ diff --git a/patches/chrome-browser-net-stub_resolver_config_reader.cc.patch b/patches/chrome-browser-net-stub_resolver_config_reader.cc.patch new file mode 100644 index 00000000000..5310997be15 --- /dev/null +++ b/patches/chrome-browser-net-stub_resolver_config_reader.cc.patch @@ -0,0 +1,12 @@ +diff --git a/chrome/browser/net/stub_resolver_config_reader.cc b/chrome/browser/net/stub_resolver_config_reader.cc +index 677f6c50fc602a1c5ba6ca52f4ea6496d7d60e7f..d736b6e06fcbee8fb17e3397cee86f3dbe09ad8e 100644 +--- a/chrome/browser/net/stub_resolver_config_reader.cc ++++ b/chrome/browser/net/stub_resolver_config_reader.cc +@@ -353,6 +353,7 @@ SecureDnsConfig StubResolverConfigReader::GetAndUpdateConfiguration( + + std::string doh_templates = + local_state_->GetString(prefs::kDnsOverHttpsTemplates); ++ BRAVE_GET_AND_UPDATE_CONFIGURATION + std::string server_method; + std::vector dns_over_https_servers; + base::Optional> diff --git a/patches/net-dns-BUILD.gn.patch b/patches/net-dns-BUILD.gn.patch new file mode 100644 index 00000000000..96ddf756886 --- /dev/null +++ b/patches/net-dns-BUILD.gn.patch @@ -0,0 +1,28 @@ +diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn +index ea7c29d285af754ee3c1ca0537645b05602d55f4..90a40ae96ca2de9048eab86fd2f45e3e3555b6b4 100644 +--- a/net/dns/BUILD.gn ++++ b/net/dns/BUILD.gn +@@ -23,6 +23,7 @@ source_set("dns") { + "//net/*", + "//services/network/*", + ] ++ friend += brave_dns_friend + + public = [] + sources = [ +@@ -205,6 +206,7 @@ source_set("host_resolver") { + # wrapper. + "//services/proxy_resolver/*", + ] ++ friend += brave_dns_friend + + sources = [] + public = [] +@@ -309,6 +311,7 @@ source_set("dns_client") { + "//net/*", + "//services/network/*", + ] ++ friend += brave_dns_friend + + sources = [] + public = [] diff --git a/patches/net-dns-dns_transaction.cc.patch b/patches/net-dns-dns_transaction.cc.patch new file mode 100644 index 00000000000..db1e6f6f25a --- /dev/null +++ b/patches/net-dns-dns_transaction.cc.patch @@ -0,0 +1,12 @@ +diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc +index 9bfd4cace384ca502716efa191eaa6ccfd6a54a8..9123e8cb94b566ef1a25cbc498dfc859e4165e73 100644 +--- a/net/dns/dns_transaction.cc ++++ b/net/dns/dns_transaction.cc +@@ -1299,6 +1299,7 @@ class DnsTransactionImpl : public DnsTransaction, + DCHECK(secure_); + + size_t doh_server_index = dns_server_iterator_->GetNextAttemptIndex(); ++ BRAVE_MAKE_HTTP_ATTEMPT + + unsigned attempt_number = attempts_.size(); + ConstructDnsHTTPAttempt( diff --git a/test/BUILD.gn b/test/BUILD.gn index ac7a8c1f693..c0031464648 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -17,6 +17,7 @@ import("//brave/components/greaselion/browser/buildflags/buildflags.gni") import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/speedreader/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/unstoppable_domains/buildflags/buildflags.gni") import("//brave/test/testing.gni") import("//chrome/common/features.gni") import("//components/gcm_driver/config.gni") @@ -200,6 +201,10 @@ test("brave_unit_tests") { "//services/preferences/public/cpp", ] + if (unstoppable_domains_enabled) { + deps += [ "//brave/browser/unstoppable_domains/test:unit_tests" ] + } + if (toolkit_views) { deps += [ "//chrome/browser/ui/views" ] } @@ -629,6 +634,10 @@ if (!is_android) { "//testing/gmock", ] + if (unstoppable_domains_enabled) { + deps += [ "//brave/browser/unstoppable_domains/test:browser_tests" ] + } + if (enable_brave_perf_predictor) { sources += [ "//brave/components/brave_perf_predictor/browser/perf_predictor_tab_helper_browsertest.cc" ]